/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 * -*- */ /* coap-client -- simple CoAP client * * Copyright (C) 2010--2015 Olaf Bergmann * * This file is part of the CoAP library libcoap. Please see README for terms of * use. */ #include "coap_config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "coap.h" #include "coap_list.h" int flags = 0; static unsigned char _token_data[8]; str the_token = { 0, _token_data }; #define FLAGS_BLOCK 0x01 static coap_list_t *optlist = NULL; /* Request URI. * TODO: associate the resources with transaction id and make it expireable */ static coap_uri_t uri; static str proxy = { 0, NULL }; static unsigned short proxy_port = COAP_DEFAULT_PORT; /* reading is done when this flag is set */ static int ready = 0; static str output_file = { 0, NULL }; /* output file name */ static FILE *file = NULL; /* output file stream */ static str payload = { 0, NULL }; /* optional payload to send */ unsigned char msgtype = COAP_MESSAGE_CON; /* usually, requests are sent confirmable */ typedef unsigned char method_t; method_t method = 1; /* the method we are using in our requests */ coap_block_t block = { .num = 0, .m = 0, .szx = 6 }; unsigned int wait_seconds = 90; /* default timeout in seconds */ coap_tick_t max_wait; /* global timeout (changed by set_timeout()) */ unsigned int obs_seconds = 30; /* default observe time */ coap_tick_t obs_wait = 0; /* timeout for current subscription */ #define min(a,b) ((a) < (b) ? (a) : (b)) #ifdef __GNUC__ #define UNUSED_PARAM __attribute__ ((unused)) #else /* not a GCC */ #define UNUSED_PARAM #endif /* GCC */ static inline void set_timeout(coap_tick_t *timer, const unsigned int seconds) { coap_ticks(timer); *timer += seconds * COAP_TICKS_PER_SECOND; } static int append_to_output(const unsigned char *data, size_t len) { size_t written; if (!file) { if (!output_file.s || (output_file.length && output_file.s[0] == '-')) file = stdout; else { if (!(file = fopen((char *)output_file.s, "w"))) { perror("fopen"); return -1; } } } do { written = fwrite(data, 1, len, file); len -= written; data += written; } while ( written && len ); fflush(file); return 0; } static void close_output(void) { if (file) { /* add a newline before closing in case were writing to stdout */ if (!output_file.s || (output_file.length && output_file.s[0] == '-')) fwrite("\n", 1, 1, file); fflush(file); fclose(file); } } static int order_opts(void *a, void *b) { coap_option *o1, *o2; if (!a || !b) return a < b ? -1 : 1; o1 = (coap_option *)(((coap_list_t *)a)->data); o2 = (coap_option *)(((coap_list_t *)b)->data); return (COAP_OPTION_KEY(*o1) < COAP_OPTION_KEY(*o2)) ? -1 : (COAP_OPTION_KEY(*o1) != COAP_OPTION_KEY(*o2)); } static coap_pdu_t * coap_new_request(coap_context_t *ctx, method_t m, coap_list_t **options, unsigned char *data, size_t length) { coap_pdu_t *pdu; coap_list_t *opt; if ( ! ( pdu = coap_new_pdu() ) ) return NULL; pdu->hdr->type = msgtype; pdu->hdr->id = coap_new_message_id(ctx); pdu->hdr->code = m; pdu->hdr->token_length = the_token.length; if ( !coap_add_token(pdu, the_token.length, the_token.s)) { debug("cannot add token to request\n"); } coap_show_pdu(pdu); if (options) { /* sort options for delta encoding */ LL_SORT((*options), order_opts); LL_FOREACH((*options), opt) { coap_option *o = (coap_option *)(opt->data); coap_add_option(pdu, COAP_OPTION_KEY(*o), COAP_OPTION_LENGTH(*o), COAP_OPTION_DATA(*o)); } } if (length) { if ((flags & FLAGS_BLOCK) == 0) coap_add_data(pdu, length, data); else coap_add_block(pdu, length, data, block.num, block.szx); } return pdu; } static coap_tid_t clear_obs(coap_context_t *ctx, const coap_endpoint_t *local_interface, const coap_address_t *remote) { coap_pdu_t *pdu; coap_list_t *option; coap_tid_t tid = COAP_INVALID_TID; unsigned char buf[2]; /* create bare PDU w/o any option */ pdu = coap_pdu_init(msgtype, COAP_REQUEST_GET, coap_new_message_id(ctx), COAP_MAX_PDU_SIZE); if (!pdu) { return tid; } if (!coap_add_token(pdu, the_token.length, the_token.s)) { coap_log(LOG_CRIT, "cannot add token"); goto error; } for (option = optlist; option; option = option->next ) { coap_option *o = (coap_option *)(option->data); if (COAP_OPTION_KEY(*o) == COAP_OPTION_URI_HOST) { if (!coap_add_option(pdu, COAP_OPTION_KEY(*o), COAP_OPTION_LENGTH(*o), COAP_OPTION_DATA(*o))) { goto error; } break; } } if (!coap_add_option(pdu, COAP_OPTION_OBSERVE, coap_encode_var_bytes(buf, COAP_OBSERVE_CANCEL), buf)) { coap_log(LOG_CRIT, "cannot add option Observe: %u", COAP_OBSERVE_CANCEL); goto error; } for (option = optlist; option; option = option->next ) { coap_option *o = (coap_option *)(option->data); switch (COAP_OPTION_KEY(*o)) { case COAP_OPTION_URI_PORT : case COAP_OPTION_URI_PATH : case COAP_OPTION_URI_QUERY : if (!coap_add_option (pdu, COAP_OPTION_KEY(*o), COAP_OPTION_LENGTH(*o), COAP_OPTION_DATA(*o))) { goto error; } break; default: ; } } coap_show_pdu(pdu); if (pdu->hdr->type == COAP_MESSAGE_CON) tid = coap_send_confirmed(ctx, local_interface, remote, pdu); else tid = coap_send(ctx, local_interface, remote, pdu); if (tid == COAP_INVALID_TID) { debug("clear_obs: error sending new request"); coap_delete_pdu(pdu); } else if (pdu->hdr->type != COAP_MESSAGE_CON) coap_delete_pdu(pdu); return tid; error: coap_delete_pdu(pdu); return tid; } static int resolve_address(const str *server, struct sockaddr *dst) { struct addrinfo *res, *ainfo; struct addrinfo hints; static char addrstr[256]; int error, len=-1; memset(addrstr, 0, sizeof(addrstr)); if (server->length) memcpy(addrstr, server->s, server->length); else memcpy(addrstr, "localhost", 9); memset ((char *)&hints, 0, sizeof(hints)); hints.ai_socktype = SOCK_DGRAM; hints.ai_family = AF_UNSPEC; error = getaddrinfo(addrstr, NULL, &hints, &res); if (error != 0) { fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(error)); return error; } for (ainfo = res; ainfo != NULL; ainfo = ainfo->ai_next) { switch (ainfo->ai_family) { case AF_INET6: case AF_INET: len = ainfo->ai_addrlen; memcpy(dst, ainfo->ai_addr, len); goto finish; default: ; } } finish: freeaddrinfo(res); return len; } #define HANDLE_BLOCK1(Pdu) \ ((method == COAP_REQUEST_PUT || method == COAP_REQUEST_POST) && \ ((flags & FLAGS_BLOCK) == 0) && \ ((Pdu)->hdr->code == COAP_RESPONSE_CODE(201) || \ (Pdu)->hdr->code == COAP_RESPONSE_CODE(204))) static inline int check_token(coap_pdu_t *received) { return received->hdr->token_length == the_token.length && memcmp(received->hdr->token, the_token.s, the_token.length) == 0; } static void message_handler(struct coap_context_t *ctx, const coap_endpoint_t *local_interface, const coap_address_t *remote, coap_pdu_t *sent, coap_pdu_t *received, const coap_tid_t id UNUSED_PARAM) { coap_pdu_t *pdu = NULL; coap_opt_t *block_opt; coap_opt_iterator_t opt_iter; unsigned char buf[4]; coap_list_t *option; size_t len; unsigned char *databuf; coap_tid_t tid; #ifndef NDEBUG if (LOG_DEBUG <= coap_get_log_level()) { debug("** process incoming %d.%02d response:\n", (received->hdr->code >> 5), received->hdr->code & 0x1F); coap_show_pdu(received); } #endif /* check if this is a response to our original request */ if (!check_token(received)) { /* drop if this was just some message, or send RST in case of notification */ if (!sent && (received->hdr->type == COAP_MESSAGE_CON || received->hdr->type == COAP_MESSAGE_NON)) coap_send_rst(ctx, local_interface, remote, received); return; } if (received->hdr->type == COAP_MESSAGE_RST) { info("got RST\n"); return; } /* output the received data, if any */ if (COAP_RESPONSE_CLASS(received->hdr->code) == 2) { /* set obs timer if we have successfully subscribed a resource */ if (sent && coap_check_option(received, COAP_OPTION_SUBSCRIPTION, &opt_iter)) { debug("observation relationship established, set timeout to %d\n", obs_seconds); set_timeout(&obs_wait, obs_seconds); } /* Got some data, check if block option is set. Behavior is undefined if * both, Block1 and Block2 are present. */ block_opt = coap_check_option(received, COAP_OPTION_BLOCK2, &opt_iter); if (block_opt) { /* handle Block2 */ unsigned short blktype = opt_iter.type; /* TODO: check if we are looking at the correct block number */ if (coap_get_data(received, &len, &databuf)) append_to_output(databuf, len); if(COAP_OPT_BLOCK_MORE(block_opt)) { /* more bit is set */ debug("found the M bit, block size is %u, block nr. %u\n", COAP_OPT_BLOCK_SZX(block_opt), coap_opt_block_num(block_opt)); /* create pdu with request for next block */ pdu = coap_new_request(ctx, method, NULL, NULL, 0); /* first, create bare PDU w/o any option */ if ( pdu ) { /* add URI components from optlist */ for (option = optlist; option; option = option->next ) { coap_option *o = (coap_option *)(option->data); switch (COAP_OPTION_KEY(*o)) { case COAP_OPTION_URI_HOST : case COAP_OPTION_URI_PORT : case COAP_OPTION_URI_PATH : case COAP_OPTION_URI_QUERY : coap_add_option (pdu, COAP_OPTION_KEY(*o), COAP_OPTION_LENGTH(*o), COAP_OPTION_DATA(*o)); break; default: ; /* skip other options */ } } /* finally add updated block option from response, clear M bit */ /* blocknr = (blocknr & 0xfffffff7) + 0x10; */ debug("query block %d\n", (coap_opt_block_num(block_opt) + 1)); coap_add_option(pdu, blktype, coap_encode_var_bytes(buf, ((coap_opt_block_num(block_opt) + 1) << 4) | COAP_OPT_BLOCK_SZX(block_opt)), buf); if (pdu->hdr->type == COAP_MESSAGE_CON) tid = coap_send_confirmed(ctx, local_interface, remote, pdu); else tid = coap_send(ctx, local_interface, remote, pdu); if (tid == COAP_INVALID_TID) { debug("message_handler: error sending new request"); coap_delete_pdu(pdu); } else { set_timeout(&max_wait, wait_seconds); if (pdu->hdr->type != COAP_MESSAGE_CON) coap_delete_pdu(pdu); } return; } } } else { /* no Block2 option */ block_opt = coap_check_option(received, COAP_OPTION_BLOCK1, &opt_iter); if (block_opt) { /* handle Block1 */ block.szx = COAP_OPT_BLOCK_SZX(block_opt); block.num = coap_opt_block_num(block_opt); debug("found Block1, block size is %u, block nr. %u\n", block.szx, block.num); if (payload.length <= (block.num+1) * (1 << (block.szx + 4))) { debug("upload ready\n"); ready = 1; return; } /* create pdu with request for next block */ pdu = coap_new_request(ctx, method, NULL, NULL, 0); /* first, create bare PDU w/o any option */ if (pdu) { /* add URI components from optlist */ for (option = optlist; option; option = option->next ) { coap_option *o = (coap_option *)(option->data); switch (COAP_OPTION_KEY(*o)) { case COAP_OPTION_URI_HOST : case COAP_OPTION_URI_PORT : case COAP_OPTION_URI_PATH : case COAP_OPTION_CONTENT_FORMAT : case COAP_OPTION_URI_QUERY : coap_add_option (pdu, COAP_OPTION_KEY(*o), COAP_OPTION_LENGTH(*o), COAP_OPTION_DATA(*o)); break; default: ; /* skip other options */ } } /* finally add updated block option from response, clear M bit */ /* blocknr = (blocknr & 0xfffffff7) + 0x10; */ block.num++; block.m = ((block.num+1) * (1 << (block.szx + 4)) < payload.length); debug("send block %d\n", block.num); coap_add_option(pdu, COAP_OPTION_BLOCK1, coap_encode_var_bytes(buf, (block.num << 4) | (block.m << 3) | block.szx), buf); coap_add_block(pdu, payload.length, payload.s, block.num, block.szx); coap_show_pdu(pdu); if (pdu->hdr->type == COAP_MESSAGE_CON) tid = coap_send_confirmed(ctx, local_interface, remote, pdu); else tid = coap_send(ctx, local_interface, remote, pdu); if (tid == COAP_INVALID_TID) { debug("message_handler: error sending new request"); coap_delete_pdu(pdu); } else { set_timeout(&max_wait, wait_seconds); if (pdu->hdr->type != COAP_MESSAGE_CON) coap_delete_pdu(pdu); } return; } } else { /* There is no block option set, just read the data and we are done. */ if (coap_get_data(received, &len, &databuf)) append_to_output(databuf, len); } } } else { /* no 2.05 */ /* check if an error was signaled and output payload if so */ if (COAP_RESPONSE_CLASS(received->hdr->code) >= 4) { fprintf(stderr, "%d.%02d", (received->hdr->code >> 5), received->hdr->code & 0x1F); if (coap_get_data(received, &len, &databuf)) { fprintf(stderr, " "); while(len--) fprintf(stderr, "%c", *databuf++); } fprintf(stderr, "\n"); } } /* finally send new request, if needed */ if (pdu && coap_send(ctx, local_interface, remote, pdu) == COAP_INVALID_TID) { debug("message_handler: error sending response"); } coap_delete_pdu(pdu); /* our job is done, we can exit at any time */ ready = coap_check_option(received, COAP_OPTION_SUBSCRIPTION, &opt_iter) == NULL; } static void usage( const char *program, const char *version) { const char *p; p = strrchr( program, '/' ); if ( p ) program = ++p; fprintf( stderr, "%s v%s -- a small CoAP implementation\n" "(c) 2010-2015 Olaf Bergmann \n\n" "usage: %s [-A type...] [-t type] [-b [num,]size] [-B seconds] [-e text]\n" "\t\t[-m method] [-N] [-o file] [-P addr[:port]] [-p port]\n" "\t\t[-s duration] [-O num,text] [-T string] [-v num] [-a addr] URI\n\n" "\tURI can be an absolute or relative coap URI,\n" "\t-a addr\tthe local interface address to use\n" "\t-A type...\taccepted media types as comma-separated list of\n" "\t\t\tsymbolic or numeric values\n" "\t-t type\t\tcontent format for given resource for PUT/POST\n" "\t-b [num,]size\tblock size to be used in GET/PUT/POST requests\n" "\t \t\t(value must be a multiple of 16 not larger than 1024)\n" "\t \t\tIf num is present, the request chain will start at\n" "\t \t\tblock num\n" "\t-B seconds\tbreak operation after waiting given seconds\n" "\t\t\t(default is %d)\n" "\t-e text\t\tinclude text as payload (use percent-encoding for\n" "\t\t\tnon-ASCII characters)\n" "\t-f file\t\tfile to send with PUT/POST (use '-' for STDIN)\n" "\t-m method\trequest method (get|put|post|delete), default is 'get'\n" "\t-N\t\tsend NON-confirmable message\n" "\t-o file\t\toutput received data to this file (use '-' for STDOUT)\n" "\t-p port\t\tlisten on specified port\n" "\t-s duration\tsubscribe for given duration [s]\n" "\t-v num\t\tverbosity level (default: 3)\n" "\t-O num,text\tadd option num with contents text to request\n" "\t-P addr[:port]\tuse proxy (automatically adds Proxy-Uri option to\n" "\t\t\trequest)\n" "\t-T token\tinclude specified token\n" "\n" "examples:\n" "\tcoap-client -m get coap://[::1]/\n" "\tcoap-client -m get coap://[::1]/.well-known/core\n" "\tcoap-client -m get -T cafe coap://[::1]/time\n" "\techo 1000 | coap-client -m put -T cafe coap://[::1]/time -f -\n" ,program, version, program, wait_seconds); } static coap_list_t * new_option_node(unsigned short key, unsigned int length, unsigned char *data) { coap_list_t *node; node = coap_malloc(sizeof(coap_list_t) + sizeof(coap_option) + length); if (node) { coap_option *option; option = (coap_option *)(node->data); COAP_OPTION_KEY(*option) = key; COAP_OPTION_LENGTH(*option) = length; memcpy(COAP_OPTION_DATA(*option), data, length); } else { coap_log(LOG_DEBUG, "new_option_node: malloc\n"); } return node; } typedef struct { unsigned char code; char *media_type; } content_type_t; static void cmdline_content_type(char *arg, unsigned short key) { static content_type_t content_types[] = { { 0, "plain" }, { 0, "text/plain" }, { 40, "link" }, { 40, "link-format" }, { 40, "application/link-format" }, { 41, "xml" }, { 41, "application/xml" }, { 42, "binary" }, { 42, "octet-stream" }, { 42, "application/octet-stream" }, { 47, "exi" }, { 47, "application/exi" }, { 50, "json" }, { 50, "application/json" }, { 60, "cbor" }, { 60, "application/cbor" }, { 255, NULL } }; coap_list_t *node; unsigned char i, value[10]; int valcnt = 0; unsigned char buf[2]; char *p, *q = arg; while (q && *q) { p = strchr(q, ','); if (isdigit(*q)) { if (p) *p = '\0'; value[valcnt++] = atoi(q); } else { for (i=0; content_types[i].media_type && strncmp(q, content_types[i].media_type, p ? (size_t)(p-q) : strlen(q)) != 0 ; ++i) ; if (content_types[i].media_type) { value[valcnt] = content_types[i].code; valcnt++; } else { warn("W: unknown content-format '%s'\n",arg); } } if (!p || key == COAP_OPTION_CONTENT_TYPE) break; q = p+1; } for (i = 0; i < valcnt; ++i) { node = new_option_node(key, coap_encode_var_bytes(buf, value[i]), buf); if (node) { LL_PREPEND(optlist, node); } } } static void cmdline_uri(char *arg) { unsigned char portbuf[2]; #define BUFSIZE 40 unsigned char _buf[BUFSIZE]; unsigned char *buf = _buf; size_t buflen; int res; if (proxy.length) { /* create Proxy-Uri from argument */ size_t len = strlen(arg); while (len > 270) { coap_insert(&optlist, new_option_node(COAP_OPTION_PROXY_URI, 270, (unsigned char *)arg)); len -= 270; arg += 270; } coap_insert(&optlist, new_option_node(COAP_OPTION_PROXY_URI, len, (unsigned char *)arg)); } else { /* split arg into Uri-* options */ coap_split_uri((unsigned char *)arg, strlen(arg), &uri ); if (uri.port != COAP_DEFAULT_PORT) { coap_insert(&optlist, new_option_node(COAP_OPTION_URI_PORT, coap_encode_var_bytes(portbuf, uri.port), portbuf)); } if (uri.path.length) { buflen = BUFSIZE; res = coap_split_path(uri.path.s, uri.path.length, buf, &buflen); while (res--) { coap_insert(&optlist, new_option_node(COAP_OPTION_URI_PATH, COAP_OPT_LENGTH(buf), COAP_OPT_VALUE(buf))); buf += COAP_OPT_SIZE(buf); } } if (uri.query.length) { buflen = BUFSIZE; buf = _buf; res = coap_split_query(uri.query.s, uri.query.length, buf, &buflen); while (res--) { coap_insert(&optlist, new_option_node(COAP_OPTION_URI_QUERY, COAP_OPT_LENGTH(buf), COAP_OPT_VALUE(buf))); buf += COAP_OPT_SIZE(buf); } } } } static int cmdline_blocksize(char *arg) { unsigned short size; again: size = 0; while(*arg && *arg != ',') size = size * 10 + (*arg++ - '0'); if (*arg == ',') { arg++; block.num = size; goto again; } if (size) block.szx = (coap_fls(size >> 4) - 1) & 0x07; flags |= FLAGS_BLOCK; return 1; } /* Called after processing the options from the commandline to set * Block1 or Block2 depending on method. */ static void set_blocksize(void) { static unsigned char buf[4]; /* hack: temporarily take encoded bytes */ unsigned short opt; unsigned int opt_length; if (method != COAP_REQUEST_DELETE) { opt = method == COAP_REQUEST_GET ? COAP_OPTION_BLOCK2 : COAP_OPTION_BLOCK1; block.m = (opt == COAP_OPTION_BLOCK1) && ((1u << (block.szx + 4)) < payload.length); opt_length = coap_encode_var_bytes(buf, (block.num << 4 | block.m << 3 | block.szx)); coap_insert(&optlist, new_option_node(opt, opt_length, buf)); } } static void cmdline_subscribe(char *arg UNUSED_PARAM) { obs_seconds = atoi(optarg); coap_insert(&optlist, new_option_node(COAP_OPTION_SUBSCRIPTION, 0, NULL)); } static int cmdline_proxy(char *arg) { char *proxy_port_str = strrchr((const char *)arg, ':'); /* explicit port ? */ if (proxy_port_str) { char *ipv6_delimiter = strrchr((const char *)arg, ']'); if (!ipv6_delimiter) { if (proxy_port_str == strchr((const char *)arg, ':')) { /* host:port format - host not in ipv6 hexadecimal string format */ *proxy_port_str++ = '\0'; /* split */ proxy_port = atoi(proxy_port_str); } } else { arg = strchr((const char *)arg, '['); if (!arg) return 0; arg++; *ipv6_delimiter = '\0'; /* split */ if (ipv6_delimiter + 1 == proxy_port_str++) { /* [ipv6 address]:port */ proxy_port = atoi(proxy_port_str); } } } proxy.length = strlen(arg); if ( (proxy.s = coap_malloc(proxy.length + 1)) == NULL) { proxy.length = 0; return 0; } memcpy(proxy.s, arg, proxy.length+1); return 1; } static inline void cmdline_token(char *arg) { strncpy((char *)the_token.s, arg, min(sizeof(_token_data), strlen(arg))); the_token.length = strlen(arg); } static void cmdline_option(char *arg) { unsigned int num = 0; while (*arg && *arg != ',') { num = num * 10 + (*arg - '0'); ++arg; } if (*arg == ',') ++arg; coap_insert(&optlist, new_option_node(num, strlen(arg), (unsigned char *)arg)); } /** * Calculates decimal value from hexadecimal ASCII character given in * @p c. The caller must ensure that @p c actually represents a valid * heaxdecimal character, e.g. with isxdigit(3). * * @hideinitializer */ #define hexchar_to_dec(c) ((c) & 0x40 ? ((c) & 0x0F) + 9 : ((c) & 0x0F)) /** * Decodes percent-encoded characters while copying the string @p seg * of size @p length to @p buf. The caller of this function must * ensure that the percent-encodings are correct (i.e. the character * '%' is always followed by two hex digits. and that @p buf provides * sufficient space to hold the result. This function is supposed to * be called by make_decoded_option() only. * * @param seg The segment to decode and copy. * @param length Length of @p seg. * @param buf The result buffer. */ static void decode_segment(const unsigned char *seg, size_t length, unsigned char *buf) { while (length--) { if (*seg == '%') { *buf = (hexchar_to_dec(seg[1]) << 4) + hexchar_to_dec(seg[2]); seg += 2; length -= 2; } else { *buf = *seg; } ++buf; ++seg; } } /** * Runs through the given path (or query) segment and checks if * percent-encodings are correct. This function returns @c -1 on error * or the length of @p s when decoded. */ static int check_segment(const unsigned char *s, size_t length) { size_t n = 0; while (length) { if (*s == '%') { if (length < 2 || !(isxdigit(s[1]) && isxdigit(s[2]))) return -1; s += 2; length -= 2; } ++s; ++n; --length; } return n; } static int cmdline_input(char *text, str *buf) { int len; len = check_segment((unsigned char *)text, strlen(text)); if (len < 0) return 0; buf->s = (unsigned char *)coap_malloc(len); if (!buf->s) return 0; buf->length = len; decode_segment((unsigned char *)text, strlen(text), buf->s); return 1; } static int cmdline_input_from_file(char *filename, str *buf) { FILE *inputfile = NULL; ssize_t len; int result = 1; struct stat statbuf; if (!filename || !buf) return 0; if (filename[0] == '-' && !filename[1]) { /* read from stdin */ buf->length = 20000; buf->s = (unsigned char *)coap_malloc(buf->length); if (!buf->s) return 0; inputfile = stdin; } else { /* read from specified input file */ if (stat(filename, &statbuf) < 0) { perror("cmdline_input_from_file: stat"); return 0; } buf->length = statbuf.st_size; buf->s = (unsigned char *)coap_malloc(buf->length); if (!buf->s) return 0; inputfile = fopen(filename, "r"); if ( !inputfile ) { perror("cmdline_input_from_file: fopen"); coap_free(buf->s); return 0; } } len = fread(buf->s, 1, buf->length, inputfile); if (len < 0 || ((size_t)len < buf->length)) { if (ferror(inputfile) != 0) { perror("cmdline_input_from_file: fread"); coap_free(buf->s); buf->length = 0; buf->s = NULL; result = 0; } else { buf->length = len; } } if (inputfile != stdin) fclose(inputfile); return result; } static method_t cmdline_method(char *arg) { static char *methods[] = { 0, "get", "post", "put", "delete", 0}; unsigned char i; for (i=1; methods[i] && strcasecmp(arg,methods[i]) != 0 ; ++i) ; return i; /* note that we do not prevent illegal methods */ } static coap_context_t * get_context(const char *node, const char *port) { coap_context_t *ctx = NULL; int s; struct addrinfo hints; struct addrinfo *result, *rp; memset(&hints, 0, sizeof(struct addrinfo)); hints.ai_family = AF_UNSPEC; /* Allow IPv4 or IPv6 */ hints.ai_socktype = SOCK_DGRAM; /* Coap uses UDP */ hints.ai_flags = AI_PASSIVE | AI_NUMERICHOST | AI_NUMERICSERV | AI_ALL; s = getaddrinfo(node, port, &hints, &result); if ( s != 0 ) { fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(s)); return NULL; } /* iterate through results until success */ for (rp = result; rp != NULL; rp = rp->ai_next) { coap_address_t addr; if (rp->ai_addrlen <= sizeof(addr.addr)) { coap_address_init(&addr); addr.size = rp->ai_addrlen; memcpy(&addr.addr, rp->ai_addr, rp->ai_addrlen); ctx = coap_new_context(&addr); if (ctx) { /* TODO: output address:port for successful binding */ goto finish; } } } fprintf(stderr, "no context available for interface '%s'\n", node); finish: freeaddrinfo(result); return ctx; } int main(int argc, char **argv) { coap_context_t *ctx = NULL; coap_address_t dst; static char addr[INET6_ADDRSTRLEN]; void *addrptr = NULL; fd_set readfds; struct timeval tv; int result; coap_tick_t now; coap_queue_t *nextpdu; coap_pdu_t *pdu; static str server; unsigned short port = COAP_DEFAULT_PORT; char port_str[NI_MAXSERV] = "0"; char node_str[NI_MAXHOST] = ""; int opt, res; coap_log_t log_level = LOG_WARNING; coap_tid_t tid = COAP_INVALID_TID; while ((opt = getopt(argc, argv, "Na:b:e:f:g:m:p:s:t:o:v:A:B:O:P:T:")) != -1) { switch (opt) { case 'a' : strncpy(node_str, optarg, NI_MAXHOST-1); node_str[NI_MAXHOST - 1] = '\0'; break; case 'b' : cmdline_blocksize(optarg); break; case 'B' : wait_seconds = atoi(optarg); break; case 'e' : if (!cmdline_input(optarg,&payload)) payload.length = 0; break; case 'f' : if (!cmdline_input_from_file(optarg,&payload)) payload.length = 0; break; case 'p' : strncpy(port_str, optarg, NI_MAXSERV-1); port_str[NI_MAXSERV - 1] = '\0'; break; case 'm' : method = cmdline_method(optarg); break; case 'N' : msgtype = COAP_MESSAGE_NON; break; case 's' : cmdline_subscribe(optarg); break; case 'o' : output_file.length = strlen(optarg); output_file.s = (unsigned char *)coap_malloc(output_file.length + 1); if (!output_file.s) { fprintf(stderr, "cannot set output file: insufficient memory\n"); exit(-1); } else { /* copy filename including trailing zero */ memcpy(output_file.s, optarg, output_file.length + 1); } break; case 'A' : cmdline_content_type(optarg,COAP_OPTION_ACCEPT); break; case 't' : cmdline_content_type(optarg,COAP_OPTION_CONTENT_TYPE); break; case 'O' : cmdline_option(optarg); break; case 'P' : if (!cmdline_proxy(optarg)) { fprintf(stderr, "error specifying proxy address\n"); exit(-1); } break; case 'T' : cmdline_token(optarg); break; case 'v' : log_level = strtol(optarg, NULL, 10); break; default: usage( argv[0], PACKAGE_VERSION ); exit( 1 ); } } coap_set_log_level(log_level); if ( optind < argc ) cmdline_uri( argv[optind] ); else { usage( argv[0], PACKAGE_VERSION ); exit( 1 ); } if (proxy.length) { server = proxy; port = proxy_port; } else { server = uri.host; port = uri.port; } /* resolve destination address where server should be sent */ res = resolve_address(&server, &dst.addr.sa); if (res < 0) { fprintf(stderr, "failed to resolve address\n"); exit(-1); } dst.size = res; dst.addr.sin.sin_port = htons(port); /* add Uri-Host if server address differs from uri.host */ switch (dst.addr.sa.sa_family) { case AF_INET: addrptr = &dst.addr.sin.sin_addr; /* create context for IPv4 */ ctx = get_context(node_str[0] == 0 ? "0.0.0.0" : node_str, port_str); break; case AF_INET6: addrptr = &dst.addr.sin6.sin6_addr; /* create context for IPv6 */ ctx = get_context(node_str[0] == 0 ? "::" : node_str, port_str); break; default: ; } if (!ctx) { coap_log(LOG_EMERG, "cannot create context\n"); return -1; } coap_register_option(ctx, COAP_OPTION_BLOCK2); coap_register_response_handler(ctx, message_handler); /* construct CoAP message */ if (!proxy.length && addrptr && (inet_ntop(dst.addr.sa.sa_family, addrptr, addr, sizeof(addr)) != 0) && (strlen(addr) != uri.host.length || memcmp(addr, uri.host.s, uri.host.length) != 0)) { /* add Uri-Host */ coap_insert(&optlist, new_option_node(COAP_OPTION_URI_HOST, uri.host.length, uri.host.s)); } /* set block option if requested at commandline */ if (flags & FLAGS_BLOCK) set_blocksize(); if (! (pdu = coap_new_request(ctx, method, &optlist, payload.s, payload.length))) return -1; #ifndef NDEBUG if (LOG_DEBUG <= coap_get_log_level()) { debug("sending CoAP request:\n"); coap_show_pdu(pdu); } #endif if (pdu->hdr->type == COAP_MESSAGE_CON) tid = coap_send_confirmed(ctx, ctx->endpoint, &dst, pdu); else tid = coap_send(ctx, ctx->endpoint, &dst, pdu); if (pdu->hdr->type != COAP_MESSAGE_CON || tid == COAP_INVALID_TID) coap_delete_pdu(pdu); set_timeout(&max_wait, wait_seconds); debug("timeout is set to %d seconds\n", wait_seconds); while ( !(ready && coap_can_exit(ctx)) ) { FD_ZERO(&readfds); FD_SET( ctx->sockfd, &readfds ); nextpdu = coap_peek_next( ctx ); coap_ticks(&now); while (nextpdu && nextpdu->t <= now - ctx->sendqueue_basetime) { coap_retransmit( ctx, coap_pop_next( ctx )); nextpdu = coap_peek_next( ctx ); } if (nextpdu && nextpdu->t < min(obs_wait ? obs_wait : max_wait, max_wait) - now) { /* set timeout if there is a pdu to send */ tv.tv_usec = ((nextpdu->t) % COAP_TICKS_PER_SECOND) * 1000000 / COAP_TICKS_PER_SECOND; tv.tv_sec = (nextpdu->t) / COAP_TICKS_PER_SECOND; } else { /* check if obs_wait fires before max_wait */ if (obs_wait && obs_wait < max_wait) { tv.tv_usec = ((obs_wait - now) % COAP_TICKS_PER_SECOND) * 1000000 / COAP_TICKS_PER_SECOND; tv.tv_sec = (obs_wait - now) / COAP_TICKS_PER_SECOND; } else { tv.tv_usec = ((max_wait - now) % COAP_TICKS_PER_SECOND) * 1000000 / COAP_TICKS_PER_SECOND; tv.tv_sec = (max_wait - now) / COAP_TICKS_PER_SECOND; } } result = select(ctx->sockfd + 1, &readfds, 0, 0, &tv); if ( result < 0 ) { /* error */ perror("select"); } else if ( result > 0 ) { /* read from socket */ if ( FD_ISSET( ctx->sockfd, &readfds ) ) { coap_read( ctx ); /* read received data */ /* coap_dispatch( ctx ); /\* and dispatch PDUs from receivequeue *\/ */ } } else { /* timeout */ coap_ticks(&now); if (max_wait <= now) { info("timeout\n"); break; } if (obs_wait && obs_wait <= now) { debug("clear observation relationship\n"); clear_obs(ctx, ctx->endpoint, &dst); /* FIXME: handle error case COAP_TID_INVALID */ /* make sure that the obs timer does not fire again */ obs_wait = 0; obs_seconds = 0; } } } close_output(); coap_delete_list(optlist); coap_free_context( ctx ); return 0; }