Dawn Framework 1.0
Universal data acquisition framework for embedded systems
wakaama.cxx
1// dawn/src/proto/wakaama/wakaama.cxx
2//
3// SPDX-License-Identifier: Apache-2.0
4//
5
6#include "dawn/proto/wakaama/wakaama.hxx"
7#include "client.hxx"
8#include "internal.hxx"
9#include "object_binding.hxx"
10#include "transport.hxx"
11
12#include <cerrno>
13#include <cctype>
14#include <cstdio>
15#include <cstring>
16#include <new>
17
18using namespace dawn;
19using namespace dawn::wakaama_internal;
20
21namespace
22{
23void assignDescriptorString(std::string &dst,
25 size_t wordOffset,
26 size_t wordCount);
27int hexNibble(char ch);
28bool parseHexKey(const char *hex, size_t len, std::vector<uint8_t> &key);
29} // namespace
30
31#ifdef CONFIG_DAWN_PROTO_WAKAAMA_QUEUE_MODE
32# define WAKAAMA_QUEUE_MODE_DEFAULT (true)
33#else
34# define WAKAAMA_QUEUE_MODE_DEFAULT (false)
35#endif
36
37CProtoWakaama::CProtoWakaama(CDescObject &desc)
38 : CProtoCommon(desc)
39 , runtime(nullptr)
40 , transport(nullptr)
41 , endpoint(CONFIG_DAWN_PROTO_WAKAAMA_ENDPOINT)
42 , serverHost(CONFIG_DAWN_PROTO_WAKAAMA_SERVER_HOST)
43 , manufacturer(CONFIG_DAWN_PROTO_WAKAAMA_DEVICE_MANUFACTURER)
44 , modelNumber(CONFIG_DAWN_PROTO_WAKAAMA_DEVICE_MODEL_NUMBER)
45 , serialNumber(CONFIG_DAWN_PROTO_WAKAAMA_DEVICE_SERIAL_NUMBER)
46 , firmwareVersion(CONFIG_DAWN_PROTO_WAKAAMA_DEVICE_FIRMWARE_VERSION)
47 , serverPort(CONFIG_DAWN_PROTO_WAKAAMA_SERVER_PORT)
48 , localPort(CONFIG_DAWN_PROTO_WAKAAMA_LOCAL_PORT)
49 , lifetime(CONFIG_DAWN_PROTO_WAKAAMA_LIFETIME)
50 , shortServerId(CONFIG_DAWN_PROTO_WAKAAMA_SHORT_SERVER_ID)
51 , queueMode(WAKAAMA_QUEUE_MODE_DEFAULT)
52#ifdef CONFIG_DAWN_IO_NOTIFY
53 , changedResourcesCapacity(0)
54 , acceptChangedResources(false)
55#endif
56{
57}
58
59CProtoWakaama::~CProtoWakaama()
60{
61 deinit();
62
63 for (ObjectBinding *obj : objects)
64 {
65 delete obj;
66 }
67
68 objects.clear();
69}
70
71int CProtoWakaama::configureDesc(const CDescObject &desc)
72{
74 size_t offset = 0;
75
76 for (size_t i = 0; i < desc.getSize(); i++)
77 {
78 item = desc.objectCfgItemNext(offset);
79
81 {
82 DAWNERR("Unsupported Wakaama config 0x%08" PRIx32 "\n", item->cfgid.v);
83 return -EINVAL;
84 }
85
86 switch (item->cfgid.s.id)
87 {
88 case PROTO_WAKAAMA_CFG_ENDPOINT:
89 {
90 assignDescriptorString(endpoint, item, 0, item->cfgid.s.size);
91 break;
92 }
93
94 case PROTO_WAKAAMA_CFG_SERVER_HOST:
95 {
96 assignDescriptorString(serverHost, item, 0, item->cfgid.s.size);
97 break;
98 }
99
100 case PROTO_WAKAAMA_CFG_SERVER_PORT:
101 {
102 serverPort = static_cast<uint16_t>(item->data[0]);
103 break;
104 }
105
106 case PROTO_WAKAAMA_CFG_LOCAL_PORT:
107 {
108 localPort = static_cast<uint16_t>(item->data[0]);
109 break;
110 }
111
112 case PROTO_WAKAAMA_CFG_LIFETIME:
113 {
114 lifetime = item->data[0];
115 break;
116 }
117
118 case PROTO_WAKAAMA_CFG_QUEUE_MODE:
119 {
120 queueMode = (item->data[0] != 0);
121 break;
122 }
123
124 case PROTO_WAKAAMA_CFG_IOBIND:
125 {
126 ObjectBinding *obj = new (std::nothrow) ObjectBinding(item, this);
127 int ret;
128
129 if (obj == nullptr)
130 {
131 return -ENOMEM;
132 }
133
134 ret = obj->configureStatus();
135 if (ret < 0)
136 {
137 delete obj;
138 return ret;
139 }
140
141#ifdef CONFIG_DAWN_IO_NOTIFY
142 changedResourcesCapacity +=
143 item->cfgid.s.size / (sizeof(SProtoWakaamaIOBind) / sizeof(uint32_t));
144#endif
145 objects.push_back(obj);
146 break;
147 }
148
149 case PROTO_WAKAAMA_CFG_DEVICE_MANUFACTURER:
150 {
151 assignDescriptorString(manufacturer, item, 0, item->cfgid.s.size);
152 break;
153 }
154
155 case PROTO_WAKAAMA_CFG_DEVICE_MODEL_NUMBER:
156 {
157 assignDescriptorString(modelNumber, item, 0, item->cfgid.s.size);
158 break;
159 }
160
161 case PROTO_WAKAAMA_CFG_DEVICE_SERIAL_NUMBER:
162 {
163 assignDescriptorString(serialNumber, item, 0, item->cfgid.s.size);
164 break;
165 }
166
167 case PROTO_WAKAAMA_CFG_DEVICE_FIRMWARE_VERSION:
168 {
169 assignDescriptorString(firmwareVersion, item, 0, item->cfgid.s.size);
170 break;
171 }
172
173 case PROTO_WAKAAMA_CFG_DEVICE_BATTERY_VOLTAGE:
174 {
175 setDeviceBatteryBind(WAKAAMA_DEVICE_RESOURCE_POWER_SOURCE_VOLTAGE, item->data[0]);
176 break;
177 }
178
179 case PROTO_WAKAAMA_CFG_DEVICE_BATTERY_LEVEL:
180 {
181 setDeviceBatteryBind(WAKAAMA_DEVICE_RESOURCE_BATTERY_LEVEL, item->data[0]);
182 break;
183 }
184
185 case PROTO_WAKAAMA_CFG_DEVICE_BATTERY_STATUS:
186 {
187 setDeviceBatteryBind(WAKAAMA_DEVICE_RESOURCE_BATTERY_STATUS, item->data[0]);
188 break;
189 }
190
191 case PROTO_WAKAAMA_CFG_SERVER:
192 {
193 int ret = configureServer(item);
194 if (ret < 0)
195 {
196 return ret;
197 }
198
199 break;
200 }
201
202 default:
203 {
204 DAWNERR("Unsupported Wakaama config 0x%08" PRIx32 "\n", item->cfgid.v);
205 return -EINVAL;
206 }
207 }
208 }
209
210 return OK;
211}
212
214{
215 int ret;
216
217 ret = configureDesc(getDesc());
218 if (ret != OK)
219 {
220 DAWNERR("Wakaama configure failed (error %d)\n", ret);
221 return ret;
222 }
223
224 if (servers.empty())
225 {
226 addDefaultServer();
227 }
228
229 for (const ServerConfig &server : servers)
230 {
231 if (server.scheme == WAKAAMA_SERVER_SCHEME_COAPS ||
232 server.securityMode == LWM2M_SECURITY_MODE_PRE_SHARED_KEY)
233 {
234#ifndef CONFIG_DAWN_PROTO_WAKAAMA_DTLS_PSK
235 DAWNERR("Wakaama coaps/PSK server requires CONFIG_DAWN_PROTO_WAKAAMA_DTLS_PSK\n");
236 return -ENOTSUP;
237#endif
238 }
239
240 if (server.bootstrap)
241 {
242#ifndef CONFIG_WAKAAMA_BOOTSTRAP
243 DAWNERR("Wakaama bootstrap server requires CONFIG_WAKAAMA_BOOTSTRAP\n");
244 return -ENOTSUP;
245#endif
246 }
247 }
248
249 return OK;
250}
251
252int CProtoWakaama::configureServer(const SObjectCfg::SObjectCfgItem *item)
253{
254 ServerConfig server;
255 size_t pos;
256 uint32_t flags;
257
258 if (item == nullptr || item->cfgid.s.size < 3)
259 {
260 return -EINVAL;
261 }
262
263 server.host = serverHost;
264 server.pskIdentity.clear();
265 server.pskKey.clear();
266 server.port = static_cast<uint16_t>(item->data[1] & 0xffff);
267 server.lifetime = item->data[2];
268 server.shortServerId = static_cast<uint16_t>((item->data[1] >> 16) & 0xffff);
269 server.securityInstanceId = static_cast<uint16_t>((item->data[0] >> 16) & 0xffff);
270 server.serverInstanceId = static_cast<uint16_t>(item->data[0] & 0xffff);
271 server.holdoff = 10;
272 server.bootstrapTimeout = 0;
273 server.scheme = WAKAAMA_SERVER_SCHEME_COAP;
274 server.securityMode = LWM2M_SECURITY_MODE_NONE;
275 server.binding = queueMode ? BINDING_UQ : BINDING_U;
276 server.bootstrap = false;
277
278 if (item->cfgid.s.size > 3 && item->data[3] == WAKAAMA_SERVER_EXT_MAGIC)
279 {
280 size_t identityWords;
281 size_t keyWords;
282 size_t hostWords;
283 const char *identity;
284 const char *key;
285
286 if (item->cfgid.s.size < 9)
287 {
288 return -EINVAL;
289 }
290
291 flags = item->data[4];
292 server.scheme = (flags & WAKAAMA_SERVER_FLAG_COAPS) != 0 ? WAKAAMA_SERVER_SCHEME_COAPS
293 : WAKAAMA_SERVER_SCHEME_COAP;
294 server.securityMode = static_cast<uint8_t>((flags >> WAKAAMA_SERVER_FLAG_SECURITY_SHIFT) &
295 WAKAAMA_SERVER_FLAG_SECURITY_MASK);
296 server.bootstrap = (flags & WAKAAMA_SERVER_FLAG_BOOTSTRAP) != 0;
297 server.holdoff = item->data[5];
298 server.bootstrapTimeout = item->data[6];
299 identityWords = item->data[7];
300 keyWords = item->data[8];
301 hostWords = item->cfgid.s.size > 9 ? item->data[9] : 0;
302 pos = 10;
303
304 if (item->cfgid.s.size < pos + identityWords + keyWords + hostWords)
305 {
306 return -EINVAL;
307 }
308
309 identity = reinterpret_cast<const char *>(&item->data[pos]);
310 server.pskIdentity.assign(identity, identityWords * sizeof(uint32_t));
311 server.pskIdentity = server.pskIdentity.c_str();
312 pos += identityWords;
313
314 key = reinterpret_cast<const char *>(&item->data[pos]);
315 if (!parseHexKey(key, keyWords * sizeof(uint32_t), server.pskKey))
316 {
317 return -EINVAL;
318 }
319 pos += keyWords;
320
321 if (hostWords > 0)
322 {
323 assignDescriptorString(server.host, item, pos, hostWords);
324 }
325 }
326 else if (item->cfgid.s.size > 3)
327 {
328 assignDescriptorString(server.host, item, 3, item->cfgid.s.size - 3);
329 }
330
331 if (server.securityMode == LWM2M_SECURITY_MODE_PRE_SHARED_KEY &&
332 (server.pskIdentity.empty() || server.pskKey.empty()))
333 {
334 return -EINVAL;
335 }
336
337 servers.push_back(server);
338 return OK;
339}
340
341void CProtoWakaama::addDefaultServer()
342{
343 ServerConfig server;
344
345 server.host = serverHost;
346 server.pskIdentity.clear();
347 server.pskKey.clear();
348 server.port = serverPort;
349 server.lifetime = lifetime;
350 server.shortServerId = shortServerId;
351 server.securityInstanceId = 0;
352 server.serverInstanceId = 0;
353 server.holdoff = 10;
354 server.bootstrapTimeout = 0;
355 server.scheme = WAKAAMA_SERVER_SCHEME_COAP;
356 server.securityMode = LWM2M_SECURITY_MODE_NONE;
357 server.binding = queueMode ? BINDING_UQ : BINDING_U;
358 server.bootstrap = false;
359 servers.push_back(server);
360}
361
362size_t CProtoWakaama::serverPoolCapacity() const
363{
364 size_t capacity = servers.size();
365
366 for (const ServerConfig &server : servers)
367 {
368 if (server.bootstrap)
369 {
370 capacity++;
371 }
372 }
373
374 return capacity;
375}
376
377std::string CProtoWakaama::serverUri(const ServerConfig &server) const
378{
379 return std::string(server.scheme == WAKAAMA_SERVER_SCHEME_COAPS ? "coaps://" : "coap://") +
380 server.host + ":" + std::to_string(server.port);
381}
382
383const CProtoWakaama::ServerConfig *CProtoWakaama::findServer(uint16_t securityInstanceId) const
384{
385 for (const ServerConfig &server : servers)
386 {
387 if (server.securityInstanceId == securityInstanceId)
388 {
389 return &server;
390 }
391 }
392
393 return nullptr;
394}
395
396#ifdef CONFIG_DAWN_IO_NOTIFY
397void CProtoWakaama::queueResourceChanged(uint16_t objectId,
398 uint16_t instanceId,
399 uint16_t resourceId)
400{
401 std::lock_guard<std::mutex> lock(changedResourcesMutex);
402
403 if (!acceptChangedResources.load())
404 {
405 return;
406 }
407
408 for (const ChangedResource &res : changedResources)
409 {
410 if (res.objectId == objectId && res.instanceId == instanceId && res.resourceId == resourceId)
411 {
412 return;
413 }
414 }
415
416 if (changedResources.size() >= changedResources.capacity())
417 {
418 DAWNERR("Wakaama changed resource queue full\n");
419 return;
420 }
421
422 changedResources.push_back({objectId, instanceId, resourceId});
423}
424
425void CProtoWakaama::processChangedResources()
426{
427 if (runtime == nullptr || runtime->context() == nullptr || !acceptChangedResources.load())
428 {
429 return;
430 }
431
432 std::lock_guard<std::mutex> lock(changedResourcesMutex);
433
434 for (const ChangedResource &res : changedResources)
435 {
436 lwm2m_uri_t uri;
437
438 LWM2M_URI_RESET(&uri);
439 uri.objectId = res.objectId;
440 uri.instanceId = res.instanceId;
441 uri.resourceId = res.resourceId;
442 lwm2m_resource_value_changed(runtime->context(), &uri);
443 }
444
445 changedResources.clear();
446}
447#endif
448
450{
451 switch (resourceId)
452 {
453 case WAKAAMA_DEVICE_RESOURCE_POWER_SOURCE_VOLTAGE:
454 return &devBattVoltage;
455 case WAKAAMA_DEVICE_RESOURCE_BATTERY_LEVEL:
456 return &devBattLevel;
457 case WAKAAMA_DEVICE_RESOURCE_BATTERY_STATUS:
458 return &devBattStatus;
459 default:
460 return nullptr;
461 }
462}
463
465{
466 SDeviceIoBind *bind = deviceBatteryBind(resourceId);
467 if (bind == nullptr)
468 {
469 return;
470 }
471
472 bind->objid = objid;
473 regObject(objid);
474}
475
476int CProtoWakaama::buildObjects()
477{
478 runtime = new (std::nothrow) ClientRuntime(*this);
479 if (runtime == nullptr)
480 {
481 return -ENOMEM;
482 }
483
484 return runtime->build(objects);
485}
486
487void CProtoWakaama::destroyObjects()
488{
489#ifdef CONFIG_DAWN_IO_NOTIFY
490 acceptChangedResources.store(false);
491#endif
492
493 if (runtime != nullptr)
494 {
495 runtime->destroy(objects);
496 }
497
498 destroyDtls();
499
500#ifdef CONFIG_DAWN_IO_NOTIFY
501 {
502 std::lock_guard<std::mutex> lock(changedResourcesMutex);
503 changedResources.clear();
504 }
505#endif
506
507 delete runtime;
508 runtime = nullptr;
509}
510
512{
513 int ret;
514
515 ret = buildObjects();
516 if (ret < 0)
517 {
518 deinit();
519 return ret;
520 }
521
522 ret = initConnectionPool();
523 if (ret < 0)
524 {
525 deinit();
526 return ret;
527 }
528
529 ret = openSocket();
530 if (ret < 0)
531 {
532 deinit();
533 return ret;
534 }
535
536 ret = runtime->openContext(transport);
537 if (ret < 0)
538 {
539 deinit();
540 return ret;
541 }
542
543 ret = initDtls();
544 if (ret < 0)
545 {
546 deinit();
547 return ret;
548 }
549
550 ret = runtime->configure(endpoint.c_str());
551 if (ret != 0)
552 {
553 DAWNERR("lwm2m_configure failed: %d\n", ret);
554 deinit();
555 return -EIO;
556 }
557
558#ifdef CONFIG_DAWN_IO_NOTIFY
559 changedResources.reserve(changedResourcesCapacity);
560#endif
561
562#ifdef CONFIG_DAWN_IO_NOTIFY
563 acceptChangedResources.store(true);
564#endif
565
566 return OK;
567}
568
570{
571 stop();
572 destroyObjects();
573 destroyConnectionPool();
574
575 return OK;
576}
577
579{
580 int ret;
581
582 /* The Wakaama thread keeps a full RX_BUFFER_SIZE packet buffer on its
583 * stack and runs the LwM2M/CoAP step, so it needs more than the default
584 * pthread stack.
585 */
586
587 setThreadStackSize(CONFIG_DAWN_PROTO_WAKAAMA_STACKSIZE);
588
589 ret = startWorkerThread([this]() { thread(); });
590 if (ret < 0)
591 {
592 DAWNERR("failed to start Wakaama thread %d\n", ret);
593 return ret;
594 }
595
596 return OK;
597}
598
600{
602
603 return OK;
604}
605
607{
608 return workerThreadRunning();
609}
610
611namespace
612{
613void assignDescriptorString(std::string &dst,
614 const SObjectCfg::SObjectCfgItem *item,
615 size_t wordOffset,
616 size_t wordCount)
617{
618 dst.assign(reinterpret_cast<const char *>(&item->data[wordOffset]), wordCount * sizeof(uint32_t));
619 size_t nul = dst.find('\0');
620 if (nul != std::string::npos)
621 {
622 dst.resize(nul);
623 }
624}
625
626int hexNibble(char ch)
627{
628 if (ch >= '0' && ch <= '9')
629 {
630 return ch - '0';
631 }
632
633 ch = static_cast<char>(std::tolower(static_cast<unsigned char>(ch)));
634 if (ch >= 'a' && ch <= 'f')
635 {
636 return ch - 'a' + 10;
637 }
638
639 return -1;
640}
641
642bool parseHexKey(const char *hex, size_t len, std::vector<uint8_t> &key)
643{
644 while (len > 0 && hex[len - 1] == '\0')
645 {
646 len--;
647 }
648
649 if (len == 0)
650 {
651 key.clear();
652 return true;
653 }
654
655 if ((len % 2) != 0)
656 {
657 return false;
658 }
659
660 key.clear();
661 key.reserve(len / 2);
662 for (size_t i = 0; i < len; i += 2)
663 {
664 int hi = hexNibble(hex[i]);
665 int lo = hexNibble(hex[i + 1]);
666
667 if (hi < 0 || lo < 0)
668 {
669 key.clear();
670 return false;
671 }
672
673 key.push_back(static_cast<uint8_t>((hi << 4) | lo));
674 }
675
676 return true;
677}
678
679} // namespace
Descriptor wrapper for individual object configuration.
size_t getSize() const
Get number of configuration items for this object.
SObjectCfg::SObjectCfgItem * objectCfgItemNext(size_t &offset) const
Get config item at current offset and advance past it.
int stop()
Stop object.
Definition object.hxx:145
CDescObject & getDesc()
Get descriptor object for this object.
Definition object.cxx:190
Base class for all protocol implementations.
Definition common.hxx:23
@ PROTO_CLASS_WAKAAMA
LwM2M protocol using Eclipse Wakaama.
Definition common.hxx:87
void setDeviceBatteryBind(uint16_t resourceId, SObjectId::ObjectId objid)
Record a battery IO binding for a Device resource (7/9/20).
Definition wakaama.cxx:464
int doStop()
Stop implementation hook.
Definition wakaama.cxx:599
int deinit()
De-initialize object.
Definition wakaama.cxx:569
int doStart()
Start implementation hook.
Definition wakaama.cxx:578
int init()
One-time initialize object after bindings are resolved.
Definition wakaama.cxx:511
SDeviceIoBind * deviceBatteryBind(uint16_t resourceId)
Return the battery binding for a Device resource id, or nullptr.
Definition wakaama.cxx:449
int configure()
Configure object from descriptor data.
Definition wakaama.cxx:213
bool hasThread() const
Check if a background thread is active.
Definition wakaama.cxx:606
void setThreadStackSize(size_t stackSize)
Configure worker thread stack size.
Definition thread.hxx:129
bool workerThreadRunning() const
Check if the worker thread is running.
Definition thread.hxx:269
int stopWorkerThread()
Stop the worker thread.
Definition thread.hxx:258
int startWorkerThread(Func &&func)
Start the worker thread with a given function.
Definition thread.hxx:246
Wakaama client context and object registry runtime.
Definition client.hxx:23
int configure(const char *endpoint)
Configure the Wakaama client endpoint and registered objects.
Definition client.cxx:254
int build(const std::vector< ObjectBinding * > &objects)
Build built-in and descriptor-backed LwM2M objects.
Definition client.cxx:31
void destroy(const std::vector< ObjectBinding * > &objects)
Release built-in and descriptor-backed LwM2M objects.
Definition client.cxx:199
lwm2m_context_t * context() const
Return the underlying Wakaama client context.
Definition client.hxx:75
int openContext(void *userdata)
Open the Wakaama client context with transport callback userdata.
Definition client.cxx:247
Runtime binding between one LwM2M object and Dawn IO resources.
int configureStatus() const
Return descriptor parsing status captured by the constructor.
Out-of-tree user-extension hooks for Dawn.
Definition bindable.hxx:13
Device-object resource backed by a descriptor IO.
Definition wakaama.hxx:248
Single configuration item within object.
ObjectCfgData_t data[]
Configuration data array (flexible, size from cfgid.s.size).
UObjectCfgId cfgid
Configuration ID header (type, class, id, size, rw, dtype).
uint32_t ObjectId
ObjectID type - single 32-bit value.
Definition objectid.hxx:44
Descriptor binding for one LwM2M resource.
Definition wakaama.hxx:34
ObjectCfgId v
Raw 32-bit ConfigID value (for storage, comparison).
Definition objectcfg.hxx:82
uint32_t cls
Object class (bits 21-29, max 511).
uint32_t id
Configuration identifier (bits 0-4, max 31).
Definition objectcfg.hxx:94
uint32_t size
Configuration data size in 32-bit words (bits 5-14, max 1023).
struct dawn::SObjectCfg::UObjectCfgId::@10 s
Bit-field structure for named member access.