1 /* CoAP server for first ETSI CoAP plugtest, March 2012
2 *
3 * Copyright (C) 2012--2013 Olaf Bergmann <bergmann@tzi.org>
4 *
5 * SPDX-License-Identifier: BSD-2-Clause
6 *
7 * This file is part of the CoAP library libcoap. Please see
8 * README for terms of use.
9 */
10
11 #include <string.h>
12 #include <stdlib.h>
13 #include <unistd.h>
14 #include <stdio.h>
15 #include <ctype.h>
16 #include <sys/select.h>
17 #include <sys/types.h>
18 #include <sys/socket.h>
19 #include <netinet/in.h>
20 #include <arpa/inet.h>
21 #include <netdb.h>
22 #include <sys/stat.h>
23 #include <dirent.h>
24 #include <errno.h>
25 #include <signal.h>
26
27 #include <coap3/coap.h>
28
29 #define COAP_RESOURCE_CHECK_TIME_SEC 1
30
31 #ifndef min
32 #define min(a,b) ((a) < (b) ? (a) : (b))
33 #endif
34
35 /* temporary storage for dynamic resource representations */
36 static int quit = 0;
37
38 #define COAP_OPT_BLOCK_SZX_MAX 6 /**< allowed maximum for block szx value */
39
40 #define REQUIRE_ETAG 0x01 /* flag for coap_payload_t: require ETag option */
41 typedef struct {
42 unsigned int flags; /* some flags to control behavior */
43 size_t max_data; /* maximum size allocated for @p data */
44 uint16_t media_type; /* media type for this object */
45 size_t length; /* length of data */
46 unsigned char data[]; /* the actual contents */
47 } coap_payload_t;
48
49 /* SIGINT handler: set quit to 1 for graceful termination */
50 static void
handle_sigint(int signum COAP_UNUSED)51 handle_sigint(int signum COAP_UNUSED) {
52 quit = 1;
53 }
54
55 #define INDEX "libcoap server for ETSI CoAP Plugtest, March 2012, Paris\n" \
56 "Copyright (C) 2012 Olaf Bergmann <bergmann@tzi.org>\n\n"
57
58 static coap_payload_t *
coap_new_payload(size_t size)59 coap_new_payload(size_t size) {
60 coap_payload_t *p;
61 p = (coap_payload_t *)coap_malloc(sizeof(coap_payload_t) + size);
62 if (p) {
63 memset(p, 0, sizeof(coap_payload_t));
64 p->max_data = size;
65 }
66
67 return p;
68 }
69
70 static inline coap_payload_t *
coap_find_payload(coap_resource_t * resource)71 coap_find_payload(coap_resource_t *resource) {
72 return coap_resource_get_userdata(resource);
73 }
74
75 static void
coap_add_payload(coap_resource_t * resource,coap_payload_t * payload)76 coap_add_payload(coap_resource_t *resource, coap_payload_t *payload){
77 assert(payload);
78
79 coap_resource_set_userdata(resource, payload);
80 }
81
82 static inline void
coap_delete_payload(coap_resource_t * resource)83 coap_delete_payload(coap_resource_t *resource) {
84 coap_free(coap_resource_get_userdata(resource));
85 coap_resource_set_userdata(resource, NULL);
86 }
87
88 static void
coap_free_userdata(void * data)89 coap_free_userdata(void *data) {
90 coap_free(data);
91 }
92
93 #if 0
94 static void
95 hnd_get_index(coap_resource_t *resource COAP_UNUSED,
96 coap_session_t *session COAP_UNUSED,
97 coap_pdu_t *request COAP_UNUSED,
98 coap_string_t *query COAP_UNUSED,
99 coap_pdu_t *response) {
100 unsigned char buf[3];
101
102 coap_pdu_set_code(response, COAP_RESPONSE_CODE_CONTENT);
103
104 coap_add_option(response, COAP_OPTION_CONTENT_TYPE,
105 coap_encode_var_safe(buf, sizeof(buf),
106 COAP_MEDIATYPE_TEXT_PLAIN),
107 buf);
108
109 coap_add_option(response, COAP_OPTION_MAXAGE,
110 coap_encode_var_safe(buf, sizeof(buf), 0x2ffff), buf);
111
112 coap_add_data(response, strlen(INDEX), (const uint8_t *)INDEX);
113 }
114 #endif
115
116 static void
hnd_get_resource(coap_resource_t * resource,coap_session_t * session COAP_UNUSED,const coap_pdu_t * request,const coap_string_t * query COAP_UNUSED,coap_pdu_t * response)117 hnd_get_resource(coap_resource_t *resource,
118 coap_session_t *session COAP_UNUSED,
119 const coap_pdu_t *request,
120 const coap_string_t *query COAP_UNUSED,
121 coap_pdu_t *response) {
122 coap_payload_t *test_payload;
123
124 test_payload = coap_find_payload(resource);
125 if (!test_payload) {
126 coap_pdu_set_code(response, COAP_RESPONSE_CODE_INTERNAL_ERROR);
127
128 return;
129 }
130
131 coap_pdu_set_code(response, COAP_RESPONSE_CODE_CONTENT);
132
133 coap_add_data_blocked_response(request, response,
134 test_payload->media_type, -1,
135 test_payload->length,
136 test_payload->data);
137 return;
138 }
139
140 /* DELETE handler for dynamic resources created by POST /test */
141 static void
hnd_delete_resource(coap_resource_t * resource,coap_session_t * session COAP_UNUSED,const coap_pdu_t * request COAP_UNUSED,const coap_string_t * query COAP_UNUSED,coap_pdu_t * response)142 hnd_delete_resource(coap_resource_t *resource,
143 coap_session_t *session COAP_UNUSED,
144 const coap_pdu_t *request COAP_UNUSED,
145 const coap_string_t *query COAP_UNUSED,
146 coap_pdu_t *response) {
147 coap_payload_t *payload;
148
149 payload = coap_find_payload(resource);
150
151 if (payload)
152 coap_delete_payload(resource);
153
154 coap_delete_resource(coap_session_get_context(session), resource);
155
156 coap_pdu_set_code(response, COAP_RESPONSE_CODE_DELETED);
157 }
158
159 static void
hnd_post_test(coap_resource_t * resource COAP_UNUSED,coap_session_t * session COAP_UNUSED,const coap_pdu_t * request,const coap_string_t * query COAP_UNUSED,coap_pdu_t * response)160 hnd_post_test(coap_resource_t *resource COAP_UNUSED,
161 coap_session_t *session COAP_UNUSED,
162 const coap_pdu_t *request,
163 const coap_string_t *query COAP_UNUSED,
164 coap_pdu_t *response) {
165 coap_opt_iterator_t opt_iter;
166 coap_opt_t *option;
167 coap_payload_t *test_payload;
168 size_t len;
169 coap_str_const_t *uri;
170 const uint8_t *data;
171
172 #define BUFSIZE 20
173 int res;
174 unsigned char _buf[BUFSIZE];
175 unsigned char *buf = _buf;
176 size_t buflen = BUFSIZE;
177
178 coap_get_data(request, &len, &data);
179
180 /* allocate storage for resource and to hold URI */
181 test_payload = coap_new_payload(len);
182 snprintf((char *)buf, buflen, "test/%p", (void *)test_payload);
183 uri = coap_new_str_const(buf, strlen((char *)buf));
184 if (!(test_payload && uri)) {
185 coap_log(LOG_CRIT, "cannot allocate new resource under /test");
186 coap_pdu_set_code(response, COAP_RESPONSE_CODE_INTERNAL_ERROR);
187 coap_free(test_payload);
188 coap_free(uri);
189 } else {
190 coap_resource_t *r;
191
192 test_payload->length = len;
193
194 memcpy(test_payload->data, data, len);
195
196 r = coap_resource_init(uri, COAP_RESOURCE_FLAGS_RELEASE_URI);
197 coap_register_handler(r, COAP_REQUEST_GET, hnd_get_resource);
198 coap_register_handler(r, COAP_REQUEST_DELETE, hnd_delete_resource);
199
200 /* set media_type if available */
201 option = coap_check_option(request, COAP_OPTION_CONTENT_TYPE, &opt_iter);
202 if (option) {
203 test_payload->media_type =
204 coap_decode_var_bytes(coap_opt_value(option), coap_opt_length(option));
205 }
206
207 coap_add_resource(coap_session_get_context(session), r);
208 coap_add_payload(r, test_payload);
209
210 /* add Location-Path */
211 res = coap_split_path(uri->s, uri->length, buf, &buflen);
212
213 while (res--) {
214 coap_add_option(response, COAP_OPTION_LOCATION_PATH,
215 coap_opt_length(buf), coap_opt_value(buf));
216
217 buf += coap_opt_size(buf);
218 }
219
220 coap_pdu_set_code(response, COAP_RESPONSE_CODE_CREATED);
221 }
222
223 }
224
225 static void
hnd_put_test(coap_resource_t * resource,coap_session_t * session COAP_UNUSED,const coap_pdu_t * request,const coap_string_t * query COAP_UNUSED,coap_pdu_t * response)226 hnd_put_test(coap_resource_t *resource,
227 coap_session_t *session COAP_UNUSED,
228 const coap_pdu_t *request,
229 const coap_string_t *query COAP_UNUSED,
230 coap_pdu_t *response) {
231 coap_opt_iterator_t opt_iter;
232 coap_opt_t *option;
233 coap_payload_t *payload;
234 size_t len;
235 const uint8_t *data;
236
237 coap_pdu_set_code(response, COAP_RESPONSE_CODE_CHANGED);
238
239 coap_get_data(request, &len, &data);
240
241 payload = coap_find_payload(resource);
242 if (payload && payload->max_data < len) { /* need more storage */
243 coap_delete_payload(resource);
244 payload = NULL;
245 /* bug: when subsequent coap_new_payload() fails, our old contents
246 is gone */
247 }
248
249 if (!payload) { /* create new payload */
250 payload = coap_new_payload(len);
251 if (!payload)
252 goto error;
253
254 coap_add_payload(resource, payload);
255 }
256 payload->length = len;
257 memcpy(payload->data, data, len);
258
259 option = coap_check_option(request, COAP_OPTION_CONTENT_TYPE, &opt_iter);
260 if (option) {
261 /* set media type given in request */
262 payload->media_type =
263 coap_decode_var_bytes(coap_opt_value(option), coap_opt_length(option));
264 } else {
265 /* set default value */
266 payload->media_type = COAP_MEDIATYPE_TEXT_PLAIN;
267 }
268 /* FIXME: need to change attribute ct of resource.
269 To do so, we need dynamic management of the attribute value
270 */
271
272 return;
273 error:
274 coap_log(LOG_WARNING, "cannot modify resource\n");
275 coap_pdu_set_code(response, COAP_RESPONSE_CODE_INTERNAL_ERROR);
276 }
277
278 static void
hnd_delete_test(coap_resource_t * resource COAP_UNUSED,coap_session_t * session COAP_UNUSED,const coap_pdu_t * request COAP_UNUSED,const coap_string_t * query COAP_UNUSED,coap_pdu_t * response)279 hnd_delete_test(coap_resource_t *resource COAP_UNUSED,
280 coap_session_t *session COAP_UNUSED,
281 const coap_pdu_t *request COAP_UNUSED,
282 const coap_string_t *query COAP_UNUSED,
283 coap_pdu_t *response) {
284 /* the ETSI validation tool does not like empty resources... */
285 #if 0
286 coap_payload_t *payload;
287 payload = coap_find_payload(resource);
288
289 if (payload)
290 payload->length = 0;
291 #endif
292
293 coap_pdu_set_code(response, COAP_RESPONSE_CODE_DELETED);
294 }
295
296 static void
hnd_get_query(coap_resource_t * resource COAP_UNUSED,coap_session_t * session COAP_UNUSED,const coap_pdu_t * request,const coap_string_t * query COAP_UNUSED,coap_pdu_t * response)297 hnd_get_query(coap_resource_t *resource COAP_UNUSED,
298 coap_session_t *session COAP_UNUSED,
299 const coap_pdu_t *request,
300 const coap_string_t *query COAP_UNUSED,
301 coap_pdu_t *response) {
302 coap_opt_iterator_t opt_iter;
303 coap_opt_filter_t f;
304 coap_opt_t *q;
305 size_t len, L;
306 unsigned char buf[70];
307
308 coap_pdu_set_code(response, COAP_RESPONSE_CODE_CONTENT);
309
310 coap_add_option(response, COAP_OPTION_CONTENT_TYPE,
311 coap_encode_var_safe(buf, sizeof(buf),
312 COAP_MEDIATYPE_TEXT_PLAIN),
313 buf);
314
315 coap_option_filter_clear(&f);
316 coap_option_filter_set(&f, COAP_OPTION_URI_QUERY);
317
318 coap_option_iterator_init(request, &opt_iter, &f);
319
320 len = 0;
321 while ((len < sizeof(buf)) && (q = coap_option_next(&opt_iter))) {
322 L = min(sizeof(buf) - len, 11);
323 memcpy(buf + len, "Uri-Query: ", L);
324 len += L;
325
326 L = min(sizeof(buf) - len, coap_opt_length(q));
327 memcpy(buf + len, coap_opt_value(q), L);
328 len += L;
329
330 if (len < sizeof(buf))
331 buf[len++] = '\n';
332 }
333
334 coap_add_data(response, len, buf);
335 }
336
337 /* handler for TD_COAP_CORE_16 */
338 static void
hnd_get_separate(coap_resource_t * resource COAP_UNUSED,coap_session_t * session,const coap_pdu_t * request,const coap_string_t * query COAP_UNUSED,coap_pdu_t * response)339 hnd_get_separate(coap_resource_t *resource COAP_UNUSED,
340 coap_session_t *session,
341 const coap_pdu_t *request,
342 const coap_string_t *query COAP_UNUSED,
343 coap_pdu_t *response) {
344 coap_opt_iterator_t opt_iter;
345 coap_opt_t *option;
346 coap_opt_filter_t f;
347 unsigned long delay = 5;
348
349 if (request) {
350 coap_async_t *async;
351 coap_bin_const_t token = coap_pdu_get_token(request);
352
353 async = coap_find_async(session, token);
354
355 if (!async) {
356 /* Set up an async request to trigger delay in the future */
357
358 /* search for option delay in query list */
359 coap_option_filter_clear(&f);
360 coap_option_filter_set(&f, COAP_OPTION_URI_QUERY);
361
362 coap_option_iterator_init(request, &opt_iter, &f);
363
364 while ((option = coap_option_next(&opt_iter))) {
365 if (strncmp("delay=", (const char *)coap_opt_value(option), 6) == 0) {
366 unsigned int i;
367 unsigned long d = 0;
368
369 for (i = 6; i < coap_opt_length(option); ++i)
370 d = d * 10 + coap_opt_value(option)[i] - '0';
371
372 /* don't allow delay to be less than COAP_RESOURCE_CHECK_TIME*/
373 delay = d < COAP_RESOURCE_CHECK_TIME_SEC
374 ? COAP_RESOURCE_CHECK_TIME_SEC
375 : d;
376 coap_log(LOG_DEBUG, "set delay to %lu\n", delay);
377 break;
378 }
379 }
380 async = coap_register_async(session,
381 request,
382 COAP_TICKS_PER_SECOND * delay);
383 if (async == NULL) {
384 coap_pdu_set_code(response, COAP_RESPONSE_CODE_SERVICE_UNAVAILABLE);
385 return;
386 }
387 /* Not setting response code will cause empty ACK to be sent
388 if Confirmable */
389 return;
390 }
391 }
392
393 /* no request (observe) or async set up, so this is the delayed request */
394 coap_add_data(response, 4, (const uint8_t *)"done");
395 coap_pdu_set_code(response, COAP_RESPONSE_CODE_CONTENT);
396
397 /* async is automatically removed by libcoap */
398 }
399
400 static coap_payload_t *
make_large(const char * filename)401 make_large(const char *filename) {
402 coap_payload_t *payload;
403 FILE *inputfile = NULL;
404 struct stat statbuf;
405
406 if (!filename)
407 return NULL;
408
409 /* read from specified input file */
410 if (stat(filename, &statbuf) < 0) {
411 coap_log(LOG_WARNING, "cannot stat file %s\n", filename);
412 return NULL;
413 }
414
415 payload = coap_new_payload(statbuf.st_size);
416 if (!payload)
417 return NULL;
418
419 inputfile = fopen(filename, "r");
420 if ( !inputfile ) {
421 coap_log(LOG_WARNING, "cannot read file %s\n", filename);
422 coap_free(payload);
423 return NULL;
424 }
425
426 payload->length = fread(payload->data, 1, statbuf.st_size, inputfile);
427 payload->media_type = 41;
428
429 fclose(inputfile);
430
431 return payload;
432 }
433
434 static void
init_resources(coap_context_t * ctx)435 init_resources(coap_context_t *ctx) {
436 coap_resource_t *r;
437 coap_payload_t *test_payload;
438
439 test_payload = coap_new_payload(200);
440 if (!test_payload)
441 coap_log(LOG_CRIT, "cannot allocate resource /test");
442 else {
443 test_payload->length = 13;
444 memcpy(test_payload->data, "put data here", test_payload->length);
445 /* test_payload->media_type is 0 anyway */
446
447 r = coap_resource_init(coap_make_str_const("test"), 0);
448 coap_register_handler(r, COAP_REQUEST_GET, hnd_get_resource);
449 coap_register_handler(r, COAP_REQUEST_POST, hnd_post_test);
450 coap_register_handler(r, COAP_REQUEST_PUT, hnd_put_test);
451 coap_register_handler(r, COAP_REQUEST_DELETE, hnd_delete_test);
452
453 coap_add_attr(r, coap_make_str_const("ct"), coap_make_str_const("0"), 0);
454 coap_add_attr(r, coap_make_str_const("rt"), coap_make_str_const("test"), 0);
455 coap_add_attr(r, coap_make_str_const("if"), coap_make_str_const("core#b"), 0);
456 #if 0
457 coap_add_attr(r, coap_make_str_const("obs"), NULL, 0);
458 #endif
459 coap_add_resource(ctx, r);
460 coap_resource_release_userdata_handler(ctx, coap_free_userdata);
461 coap_add_payload(r, test_payload);
462 }
463
464 /* TD_COAP_BLOCK_01
465 * TD_COAP_BLOCK_02 */
466 test_payload = make_large("etsi_iot_01_largedata.txt");
467 if (!test_payload)
468 coap_log(LOG_CRIT, "cannot allocate resource /large\n");
469 else {
470 r = coap_resource_init(coap_make_str_const("large"), 0);
471 coap_register_handler(r, COAP_REQUEST_GET, hnd_get_resource);
472
473 coap_add_attr(r, coap_make_str_const("ct"), coap_make_str_const("41"), 0);
474 coap_add_attr(r, coap_make_str_const("rt"), coap_make_str_const("large"), 0);
475 coap_add_resource(ctx, r);
476
477 test_payload->flags |= REQUIRE_ETAG;
478
479 coap_add_payload(r, test_payload);
480 }
481
482 /* For TD_COAP_CORE_12 */
483 test_payload = coap_new_payload(20);
484 if (!test_payload)
485 coap_log(LOG_CRIT, "cannot allocate resource /seg1/seg2/seg3\n");
486 else {
487 test_payload->length = 10;
488 memcpy(test_payload->data, "segsegseg!", test_payload->length);
489 /* test_payload->media_type is 0 anyway */
490
491 r = coap_resource_init(coap_make_str_const("seg1/seg2/seg3"), 0);
492 coap_register_handler(r, COAP_REQUEST_GET, hnd_get_resource);
493
494 coap_add_attr(r, coap_make_str_const("ct"), coap_make_str_const("0"), 0);
495 coap_add_resource(ctx, r);
496
497 coap_add_payload(r, test_payload);
498 }
499
500 /* For TD_COAP_CORE_13 */
501 r = coap_resource_init(coap_make_str_const("query"), 0);
502 coap_register_handler(r, COAP_REQUEST_GET, hnd_get_query);
503
504 coap_add_attr(r, coap_make_str_const("ct"), coap_make_str_const("0"), 0);
505 coap_add_resource(ctx, r);
506
507 /* For TD_COAP_CORE_16 */
508 r = coap_resource_init(coap_make_str_const("separate"), 0);
509 coap_register_handler(r, COAP_REQUEST_GET, hnd_get_separate);
510
511 coap_add_attr(r, coap_make_str_const("ct"), coap_make_str_const("0"), 0);
512 coap_add_attr(r, coap_make_str_const("rt"), coap_make_str_const("seperate"), 0);
513 coap_add_resource(ctx, r);
514 }
515
516 static void
usage(const char * program,const char * version)517 usage( const char *program, const char *version) {
518 const char *p;
519
520 p = strrchr( program, '/' );
521 if ( p )
522 program = ++p;
523
524 fprintf( stderr, "%s v%s -- ETSI CoAP plugtest server\n"
525 "(c) 2012 Olaf Bergmann <bergmann@tzi.org>\n\n"
526 "usage: %s [-A address] [-p port]\n\n"
527 "\t-A address\tinterface address to bind to\n"
528 "\t-p port\t\tlisten on specified port\n"
529 "\t-v num\t\tverbosity level (default: 3)\n",
530 program, version, program );
531 }
532
533 static coap_context_t *
get_context(const char * node,const char * port)534 get_context(const char *node, const char *port) {
535 coap_context_t *ctx = NULL;
536 int s;
537 struct addrinfo hints;
538 struct addrinfo *result, *rp;
539
540 memset(&hints, 0, sizeof(struct addrinfo));
541 hints.ai_family = AF_UNSPEC; /* Allow IPv4 or IPv6 */
542 hints.ai_socktype = SOCK_DGRAM; /* Coap uses UDP */
543 hints.ai_flags = AI_PASSIVE | AI_NUMERICHOST;
544
545 s = getaddrinfo(node, port, &hints, &result);
546 if ( s != 0 ) {
547 fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(s));
548 return NULL;
549 }
550
551 /* iterate through results until success */
552 for (rp = result; rp != NULL; rp = rp->ai_next) {
553 coap_address_t addr;
554
555 if (rp->ai_addrlen <= (socklen_t)sizeof(addr.addr)) {
556 coap_address_init(&addr);
557 addr.size = rp->ai_addrlen;
558 memcpy(&addr.addr, rp->ai_addr, rp->ai_addrlen);
559
560 ctx = coap_new_context(&addr);
561 if (ctx) {
562 /* TODO: output address:port for successful binding */
563 goto finish;
564 }
565 }
566 }
567
568 fprintf(stderr, "no context available for interface '%s'\n", node);
569
570 finish:
571 freeaddrinfo(result);
572 return ctx;
573 }
574
575 int
main(int argc,char ** argv)576 main(int argc, char **argv) {
577 coap_context_t *ctx;
578 int result;
579 char addr_str[NI_MAXHOST] = "::";
580 char port_str[NI_MAXSERV] = "5683";
581 int opt;
582 coap_log_t log_level = LOG_WARNING;
583 struct sigaction sa;
584
585 while ((opt = getopt(argc, argv, "A:p:v:")) != -1) {
586 switch (opt) {
587 case 'A' :
588 strncpy(addr_str, optarg, NI_MAXHOST-1);
589 addr_str[NI_MAXHOST - 1] = '\0';
590 break;
591 case 'p' :
592 strncpy(port_str, optarg, NI_MAXSERV-1);
593 port_str[NI_MAXSERV - 1] = '\0';
594 break;
595 case 'v' :
596 log_level = strtol(optarg, NULL, 10);
597 break;
598 default:
599 usage( argv[0], LIBCOAP_PACKAGE_VERSION );
600 exit( 1 );
601 }
602 }
603
604 coap_set_log_level(log_level);
605
606 ctx = get_context(addr_str, port_str);
607 if (!ctx)
608 return -1;
609
610 init_resources(ctx);
611
612 memset (&sa, 0, sizeof(sa));
613 sigemptyset(&sa.sa_mask);
614 sa.sa_handler = handle_sigint;
615 sa.sa_flags = 0;
616 sigaction (SIGINT, &sa, NULL);
617 sigaction (SIGTERM, &sa, NULL);
618 /* So we do not exit on a SIGPIPE */
619 sa.sa_handler = SIG_IGN;
620 sigaction (SIGPIPE, &sa, NULL);
621
622 while ( !quit ) {
623 result = coap_io_process( ctx, COAP_RESOURCE_CHECK_TIME * 1000 );
624 if ( result >= 0 ) {
625 /* coap_check_resource_list( ctx ); */
626 }
627 }
628
629 coap_free_context( ctx );
630 coap_cleanup();
631
632 return 0;
633 }
634