example/libcoap_test : introduce libcoap 4.1.1 test examples
authorJin-Seong Kim <jseong82.kim@samsung.com>
Mon, 15 May 2017 08:14:48 +0000 (17:14 +0900)
committerEunBong Song <eunb.song@samsung.com>
Tue, 11 Jul 2017 01:26:55 +0000 (10:26 +0900)
This commit is to introduce libcoap 4.1.1 test examples
 - server / client examples

Change-Id: Iac1df3cc9d9e4ba1c7b8d283738abfcb0c364487
Signed-off-by: Jin-Seong Kim <jseong82.kim@samsung.com>
apps/examples/libcoap_test/Kconfig [new file with mode: 0644]
apps/examples/libcoap_test/Make.defs [new file with mode: 0644]
apps/examples/libcoap_test/Makefile [new file with mode: 0644]
apps/examples/libcoap_test/coap-client.c [new file with mode: 0644]
apps/examples/libcoap_test/coap-server.c [new file with mode: 0644]

diff --git a/apps/examples/libcoap_test/Kconfig b/apps/examples/libcoap_test/Kconfig
new file mode 100644 (file)
index 0000000..68199cf
--- /dev/null
@@ -0,0 +1,11 @@
+#
+# For a description of the syntax of this configuration file,
+# see misc/tools/kconfig-language.txt.
+#
+
+config EXAMPLES_LIBCOAP_TEST
+       bool "\"libcoap Test\" example"
+       default n
+       depends on NETUTILS_LIBCOAP
+       ---help---
+               Enable the \"libcoap Test\" example
diff --git a/apps/examples/libcoap_test/Make.defs b/apps/examples/libcoap_test/Make.defs
new file mode 100644 (file)
index 0000000..23f2665
--- /dev/null
@@ -0,0 +1,21 @@
+###########################################################################
+#
+# Copyright 2017 Samsung Electronics All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+# either express or implied. See the License for the specific
+# language governing permissions and limitations under the License.
+#
+###########################################################################
+
+ifeq ($(CONFIG_EXAMPLES_LIBCOAP_TEST),y)
+CONFIGURED_APPS += examples/libcoap_test
+endif
diff --git a/apps/examples/libcoap_test/Makefile b/apps/examples/libcoap_test/Makefile
new file mode 100644 (file)
index 0000000..e379784
--- /dev/null
@@ -0,0 +1,155 @@
+############################################################################
+# apps/examples/libcoap_test/Makefile
+#
+#   Copyright (C) 2008, 2010-2013 Gregory Nutt. All rights reserved.
+#   Author: Gregory Nutt <gnutt@nuttx.org>
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+#
+# 1. Redistributions of source code must retain the above copyright
+#    notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+#    notice, this list of conditions and the following disclaimer in
+#    the documentation and/or other materials provided with the
+#    distribution.
+# 3. Neither the name NuttX nor the names of its contributors may be
+#    used to endorse or promote products derived from this software
+#    without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+# OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+#
+############################################################################
+
+-include $(TOPDIR)/.config
+-include $(TOPDIR)/Make.defs
+include $(APPDIR)/Make.defs
+
+# built-in application info
+
+CLIENT_APPNAME = coap-client
+SERVER_APPNAME = coap-server
+CLIENT_FUNCNAME = coap_client_test_main
+SERVER_FUNCNAME = coap_server_test_main
+THREADEXEC = TASH_EXECMD_ASYNC
+
+# mqtt test example
+
+ASRCS =
+CSRCS =
+MAINSRC = coap-client.c coap-server.c
+
+
+AOBJS = $(ASRCS:.S=$(OBJEXT))
+COBJS = $(CSRCS:.c=$(OBJEXT))
+MAINOBJ = $(MAINSRC:.c=$(OBJEXT))
+
+SRCS = $(ASRCS) $(CSRCS) $(MAINSRC)
+OBJS = $(AOBJS) $(COBJS)
+
+# libcoap flags
+CFLAGS += -DWITH_POSIX
+CFLAGS += -D__TINYARA__
+ifeq ($(CONFIG_NETUTILS_LIBCOAP_DEBUG),n)
+CFLAGS += -DNDEBUG
+endif
+CFLAGS += ${shell $(INCDIR) $(INCDIROPT) "$(CC)" $(APPDIR)$(DELIM)include$(DELIM)netutils$(DELIM)libcoap}
+
+ifneq ($(CONFIG_BUILD_KERNEL),y)
+  OBJS += $(MAINOBJ)
+endif
+
+ifeq ($(CONFIG_WINDOWS_NATIVE),y)
+  BIN = ..\..\libapps$(LIBEXT)
+else
+ifeq ($(WINTOOL),y)
+  BIN = ..\\..\\libapps$(LIBEXT)
+else
+  BIN = ../../libapps$(LIBEXT)
+endif
+endif
+
+ifeq ($(WINTOOL),y)
+  INSTALL_DIR = "${shell cygpath -w $(BIN_DIR)}"
+else
+  INSTALL_DIR = $(BIN_DIR)
+endif
+
+CONFIG_EXAMPLES_LIBCOAP_PROGNAME ?= libcoap$(EXEEXT)
+PROGNAME = $(CONFIG_EXAMPLES_LIBCOAP_PROGNAME)
+
+ROOTDEPPATH = --dep-path .
+
+# Common build
+
+VPATH =
+
+all: .built
+.PHONY: clean depend distclean
+
+$(AOBJS): %$(OBJEXT): %.S
+       $(call ASSEMBLE, $<, $@)
+
+$(COBJS) $(MAINOBJ): %$(OBJEXT): %.c
+       $(call COMPILE, $<, $@)
+
+.built: $(OBJS)
+       $(call ARCHIVE, $(BIN), $(OBJS))
+       @touch .built
+
+ifeq ($(CONFIG_BUILD_KERNEL),y)
+$(BIN_DIR)$(DELIM)$(PROGNAME): $(OBJS) $(MAINOBJ)
+       @echo "LD: $(PROGNAME)"
+       $(Q) $(LD) $(LDELFFLAGS) $(LDLIBPATH) -o $(INSTALL_DIR)$(DELIM)$(PROGNAME) $(ARCHCRT0OBJ) $(MAINOBJ) $(LDLIBS)
+       $(Q) $(NM) -u  $(INSTALL_DIR)$(DELIM)$(PROGNAME)
+
+install: $(BIN_DIR)$(DELIM)$(PROGNAME)
+
+else
+install:
+
+endif
+
+ifeq ($(CONFIG_BUILTIN_APPS)$(CONFIG_EXAMPLES_LIBCOAP_TEST),yy)
+$(BUILTIN_REGISTRY)$(DELIM)$(CLIENT_APPNAME)_main.bdat: $(DEPCONFIG) Makefile
+       $(call REGISTER,$(CLIENT_APPNAME),$(CLIENT_FUNCNAME),$(THREADEXEC))
+
+$(BUILTIN_REGISTRY)$(DELIM)$(SERVER_APPNAME)_main.bdat: $(DEPCONFIG) Makefile
+       $(call REGISTER,$(SERVER_APPNAME),$(SERVER_FUNCNAME),$(THREADEXEC))
+
+context: $(BUILTIN_REGISTRY)$(DELIM)$(CLIENT_APPNAME)_main.bdat $(BUILTIN_REGISTRY)$(DELIM)$(SERVER_APPNAME)_main.bdat
+
+else
+context:
+
+endif
+
+.depend: Makefile $(SRCS)
+       @$(MKDEP) $(ROOTDEPPATH) "$(CC)" -- $(CFLAGS) -- $(SRCS) >Make.dep
+       @touch $@
+
+depend: .depend
+
+clean:
+       $(call DELFILE, .built)
+       $(call CLEAN)
+
+distclean: clean
+       $(call DELFILE, Make.dep)
+       $(call DELFILE, .depend)
+
+-include Make.dep
+.PHONY: preconfig
+preconfig:
diff --git a/apps/examples/libcoap_test/coap-client.c b/apps/examples/libcoap_test/coap-client.c
new file mode 100644 (file)
index 0000000..cbd067f
--- /dev/null
@@ -0,0 +1,1143 @@
+/* coap-client -- simple CoAP client
+ *
+ * Copyright (C) 2010--2013 Olaf Bergmann <bergmann@tzi.org>
+ *
+ * This file is part of the CoAP library libcoap. Please see
+ * README for terms of use.
+ */
+
+#include <tinyara/config.h>
+
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <ctype.h>
+#include <sys/select.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+
+#include "coap.h"
+
+#ifndef NI_MAXHOST
+#define NI_MAXHOST 1025
+#endif
+#ifndef NI_MAXSERV
+#define NI_MAXSERV 32
+#endif
+
+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))
+
+static inline void set_timeout(coap_tick_t *timer, const unsigned int seconds)
+{
+       coap_ticks(timer);
+       *timer += seconds * COAP_TICKS_PER_SECOND;
+}
+
+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;
+}
+
+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);
+       }
+}
+
+coap_pdu_t *new_ack(coap_context_t *ctx, coap_queue_t *node)
+{
+       coap_pdu_t *pdu = coap_new_pdu();
+
+       if (pdu) {
+               pdu->hdr->type = COAP_MESSAGE_ACK;
+               pdu->hdr->code = 0;
+               pdu->hdr->id = node->pdu->hdr->id;
+       }
+
+       return pdu;
+}
+
+coap_pdu_t *new_response(coap_context_t *ctx, coap_queue_t *node, unsigned int code)
+{
+       coap_pdu_t *pdu = new_ack(ctx, node);
+
+       if (pdu) {
+               pdu->hdr->code = code;
+       }
+
+       return pdu;
+}
+
+coap_pdu_t *coap_new_request(coap_context_t *ctx, method_t m, coap_list_t *options)
+{
+       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);
+
+       for (opt = options; opt; opt = opt->next) {
+               coap_add_option(pdu, COAP_OPTION_KEY(*(coap_option *) opt->data), COAP_OPTION_LENGTH(*(coap_option *) opt->data), COAP_OPTION_DATA(*(coap_option *) opt->data));
+       }
+
+       if (payload.length) {
+               if ((flags & FLAGS_BLOCK) == 0) {
+                       coap_add_data(pdu, payload.length, payload.s);
+               } else {
+                       coap_add_block(pdu, payload.length, payload.s, block.num, block.szx);
+               }
+       }
+
+       return pdu;
+}
+
+coap_tid_t clear_obs(coap_context_t *ctx, const coap_address_t *remote)
+{
+       coap_list_t *option;
+       coap_pdu_t *pdu;
+       coap_tid_t tid = COAP_INVALID_TID;
+
+       /* create bare PDU w/o any option  */
+       pdu = coap_new_request(ctx, COAP_REQUEST_GET, NULL);
+
+       if (pdu) {
+               /* FIXME: add token */
+               /* add URI components from optlist */
+               for (option = optlist; option; option = option->next) {
+                       switch (COAP_OPTION_KEY(*(coap_option *) option->data)) {
+                       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(*(coap_option *) option->data), COAP_OPTION_LENGTH(*(coap_option *) option->data), COAP_OPTION_DATA(*(coap_option *) option->data));
+                               break;
+                       default:
+                               ;                               /* skip other options */
+                       }
+               }
+
+               if (pdu->hdr->type == COAP_MESSAGE_CON) {
+                       tid = coap_send_confirmed(ctx, remote, pdu);
+               } else {
+                       tid = coap_send(ctx, 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;
+}
+
+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: %d\n", 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;
+}
+
+static inline coap_opt_t *get_block(coap_pdu_t *pdu, coap_opt_iterator_t *opt_iter)
+{
+       coap_opt_filter_t f;
+
+       assert(pdu);
+
+       memset(f, 0, sizeof(coap_opt_filter_t));
+       coap_option_setb(f, COAP_OPTION_BLOCK1);
+       coap_option_setb(f, COAP_OPTION_BLOCK2);
+
+       coap_option_iterator_init(pdu, opt_iter, f);
+       return coap_option_next(opt_iter);
+}
+
+#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)))
+
+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;
+}
+
+void message_handler(struct coap_context_t *ctx, const coap_address_t *remote, coap_pdu_t *sent, coap_pdu_t *received, const coap_tid_t id)
+{
+
+       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, remote, received);
+               }
+               return;
+       }
+
+       switch (received->hdr->type) {
+       case COAP_MESSAGE_CON:
+               /* acknowledge received response if confirmable (TODO: check Token) */
+               coap_send_ack(ctx, remote, received);
+               break;
+       case COAP_MESSAGE_RST:
+               info("got RST\n");
+               return;
+       default:
+               ;
+       }
+
+       /* output the received data, if any */
+       if (received->hdr->code == COAP_RESPONSE_CODE(205)) {
+
+               /* 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 = get_block(received, &opt_iter);
+               if (!block_opt) {
+                       /* 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 {
+                       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);      /* first, create bare PDU w/o any option  */
+                               if (pdu) {
+                                       /* add URI components from optlist */
+                                       for (option = optlist; option; option = option->next) {
+                                               switch (COAP_OPTION_KEY(*(coap_option *) option->data)) {
+                                               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(*(coap_option *) option->data), COAP_OPTION_LENGTH(*(coap_option *) option->data), COAP_OPTION_DATA(*(coap_option *) option->data));
+                                                       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 (received->hdr->type == COAP_MESSAGE_CON) {
+                                               tid = coap_send_confirmed(ctx, remote, pdu);
+                                       } else {
+                                               tid = coap_send(ctx, 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 (received->hdr->type != COAP_MESSAGE_CON) {
+                                                       coap_delete_pdu(pdu);
+                                               }
+                                       }
+
+                                       return;
+                               }
+                       }
+               }
+       } 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, 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;
+}
+
+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-2013 Olaf Bergmann <bergmann@tzi.org>\n\n"
+                       "usage: %s [-A type...] [-t type] [-b [num,]size] [-B seconds] [-e text]\n"
+                       "\t\t[-g group] [-m method] [-N] [-o file] [-P addr[:port]] [-p port]\n"
+                       "\t\t[-s duration] [-O num,text] [-T string] [-v num] URI\n\n"
+                       "\tURI can be an absolute or relative coap URI,\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 type 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-g group\tjoin the given multicast group\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);
+}
+
+#if 0
+int join(coap_context_t *ctx, char *group_name)
+{
+       struct ipv6_mreq mreq;
+       struct addrinfo *reslocal = NULL, *resmulti = NULL, hints, *ainfo;
+       int result = -1;
+
+       /* we have to resolve the link-local interface to get the interface id */
+       memset(&hints, 0, sizeof(hints));
+       hints.ai_family = AF_INET6;
+       hints.ai_socktype = SOCK_DGRAM;
+
+       result = getaddrinfo("::", NULL, &hints, &reslocal);
+       if (result < 0) {
+               fprintf(stderr, "join: cannot resolve link-local interface: %s\n", gai_strerror(result));
+               goto finish;
+       }
+
+       /* get the first suitable interface identifier */
+       for (ainfo = reslocal; ainfo != NULL; ainfo = ainfo->ai_next) {
+               if (ainfo->ai_family == AF_INET6) {
+                       mreq.ipv6mr_interface = ((struct sockaddr_in6 *)ainfo->ai_addr)->sin6_scope_id;
+                       break;
+               }
+       }
+
+       memset(&hints, 0, sizeof(hints));
+       hints.ai_family = AF_INET6;
+       hints.ai_socktype = SOCK_DGRAM;
+
+       /* resolve the multicast group address */
+       result = getaddrinfo(group_name, NULL, &hints, &resmulti);
+
+       if (result < 0) {
+               fprintf(stderr, "join: cannot resolve multicast address: %s\n", gai_strerror(result));
+               goto finish;
+       }
+
+       for (ainfo = resmulti; ainfo != NULL; ainfo = ainfo->ai_next) {
+               if (ainfo->ai_family == AF_INET6) {
+                       mreq.ipv6mr_multiaddr = ((struct sockaddr_in6 *)ainfo->ai_addr)->sin6_addr;
+                       break;
+               }
+       }
+
+       result = setsockopt(ctx->sockfd, IPPROTO_IPV6, IPV6_JOIN_GROUP, (char *)&mreq, sizeof(mreq));
+       if (result < 0) {
+               perror("join: setsockopt");
+       }
+
+finish:
+       freeaddrinfo(resmulti);
+       freeaddrinfo(reslocal);
+
+       return result;
+}
+#endif
+
+int order_opts(void *a, void *b)
+{
+       if (!a || !b) {
+               return a < b ? -1 : 1;
+       }
+
+       if (COAP_OPTION_KEY(*(coap_option *) a) < COAP_OPTION_KEY(*(coap_option *) b)) {
+               return -1;
+       }
+
+       return COAP_OPTION_KEY(*(coap_option *) a) == COAP_OPTION_KEY(*(coap_option *) b);
+}
+
+coap_list_t *new_option_node(unsigned short key, unsigned int length, unsigned char *data)
+{
+       coap_option *option;
+       coap_list_t *node;
+
+       option = coap_malloc(sizeof(coap_option) + length);
+       if (!option) {
+               goto error;
+       }
+
+       COAP_OPTION_KEY(*option) = key;
+       COAP_OPTION_LENGTH(*option) = length;
+       memcpy(COAP_OPTION_DATA(*option), data, length);
+
+       /* we can pass NULL here as delete function since option is released automatically  */
+       node = coap_new_listnode(option, NULL);
+
+       if (node) {
+               return node;
+       }
+
+error:
+       perror("new_option_node: malloc");
+       coap_free(option);
+       return NULL;
+}
+
+typedef struct {
+       unsigned char code;
+       char *media_type;
+} content_type_t;
+
+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"},
+               {42, "binary"},
+               {42, "octet-stream"},
+               {42, "application/octet-stream"},
+               {47, "exi"},
+               {47, "application/exi"},
+               {50, "json"},
+               {50, "application/json"},
+               {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 ? p - q : strlen(q)) != 0; ++i) ;
+
+                       if (content_types[i].media_type) {
+                               value[valcnt] = content_types[i].code;
+                               valcnt++;
+                       } else {
+                               warn("W: unknown content-type '%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) {
+                       coap_insert(&optlist, node, order_opts);
+               }
+       }
+}
+
+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), order_opts);
+                       len -= 270;
+                       arg += 270;
+               }
+
+               coap_insert(&optlist, new_option_node(COAP_OPTION_PROXY_URI, len, (unsigned char *)arg), order_opts);
+       } 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), order_opts);
+               }
+
+               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)), order_opts);
+                               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)), order_opts);
+                               buf += COAP_OPT_SIZE(buf);
+                       }
+               }
+       }
+}
+
+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. */
+void set_blocksize(void)
+{
+       static unsigned char buf[4];    /* hack: temporarily take encoded bytes */
+       unsigned short opt;
+
+       if (method != COAP_REQUEST_DELETE) {
+               opt = method == COAP_REQUEST_GET ? COAP_OPTION_BLOCK2 : COAP_OPTION_BLOCK1;
+
+               coap_insert(&optlist, new_option_node(opt, coap_encode_var_bytes(buf, (block.num << 4 | block.szx)), buf), order_opts);
+       }
+}
+
+void cmdline_subscribe(char *arg)
+{
+       obs_seconds = atoi(optarg);
+       coap_insert(&optlist, new_option_node(COAP_OPTION_SUBSCRIPTION, 0, NULL), order_opts);
+}
+
+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;
+}
+
+inline void cmdline_token(char *arg)
+{
+       strncpy((char *)the_token.s, arg, min(sizeof(_token_data), strlen(arg)));
+       the_token.length = strlen(arg);
+}
+
+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), order_opts);
+}
+
+extern int check_segment(const unsigned char *s, size_t length);
+extern void decode_segment(const unsigned char *seg, size_t length, unsigned char *buf);
+
+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;
+}
+
+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 < 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;
+}
+
+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: %d\n", 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 coap_client_test_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";
+       int opt, res;
+       char *group = NULL;
+       coap_log_t log_level = LOG_WARNING;
+       coap_tid_t tid = COAP_INVALID_TID;
+
+       while ((opt = getopt(argc, argv, "Nb:e:f:g:m:p:s:t:o:v:A:B:O:P:T:")) != -1) {
+               switch (opt) {
+               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 'g':
+                       group = optarg;
+                       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("0.0.0.0", port_str);
+               break;
+       case AF_INET6:
+               addrptr = &dst.addr.sin6.sin6_addr;
+
+               /* create context for IPv6 */
+               ctx = get_context("::", 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);
+
+       /* join multicast group if requested at command line */
+#if 0
+       if (group) {
+               join(ctx, group);
+       }
+#endif
+       /* 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), order_opts);
+       }
+
+       /* set block option if requested at commandline */
+       if (flags & FLAGS_BLOCK) {
+               set_blocksize();
+       }
+
+       if (!(pdu = coap_new_request(ctx, method, optlist))) {
+               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, &dst, pdu);
+       } else {
+               tid = coap_send(ctx, &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, &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_free_context(ctx);
+
+       return 0;
+}
diff --git a/apps/examples/libcoap_test/coap-server.c b/apps/examples/libcoap_test/coap-server.c
new file mode 100644 (file)
index 0000000..53f613e
--- /dev/null
@@ -0,0 +1,445 @@
+/* coap -- simple implementation of the Constrained Application Protocol (CoAP)
+ *         as defined in draft-ietf-core-coap
+ *
+ * Copyright (C) 2010--2013 Olaf Bergmann <bergmann@tzi.org>
+ *
+ * This file is part of the CoAP library libcoap. Please see
+ * README for terms of use.
+ */
+
+#include <tinyara/config.h>
+
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <ctype.h>
+#include <sys/select.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+#include <sys/stat.h>
+#include <dirent.h>
+#include <errno.h>
+#include <signal.h>
+
+#include "coap.h"
+
+#define COAP_RESOURCE_CHECK_TIME 2
+
+#ifndef min
+#define min(a,b) ((a) < (b) ? (a) : (b))
+#endif
+
+#ifndef NI_MAXHOST
+#define NI_MAXHOST 1025
+#endif
+#ifndef NI_MAXSERV
+#define NI_MAXSERV 32
+#endif
+
+#if defined(__TINYARA__)
+#ifndef FD_SETSIZE
+#define FD_SETSIZE     (CONFIG_NFILE_DESCRIPTORS + CONFIG_NSOCKET_DESCRIPTORS)
+#endif
+#endif
+
+/* temporary storage for dynamic resource representations */
+static int quit = 0;
+
+/* changeable clock base (see handle_put_time()) */
+static time_t my_clock_base = 0;
+
+struct coap_resource_t *time_resource = NULL;
+
+#ifndef WITHOUT_ASYNC
+/* This variable is used to mimic long-running tasks that require
+ * asynchronous responses. */
+static coap_async_state_t *async = NULL;
+#endif                                                 /* WITHOUT_ASYNC */
+
+/* SIGINT handler: set quit to 1 for graceful termination */
+void handle_sigint(int signum)
+{
+       quit = 1;
+}
+
+#define INDEX "This is a test server made with libcoap (see http://libcoap.sf.net)\n" \
+             "Copyright (C) 2010--2013 Olaf Bergmann <bergmann@tzi.org>\n\n"
+
+void hnd_get_index(coap_context_t *ctx, struct coap_resource_t *resource, coap_address_t *peer, coap_pdu_t *request, str *token, coap_pdu_t *response)
+{
+       unsigned char buf[3];
+
+       response->hdr->code = COAP_RESPONSE_CODE(205);
+
+       coap_add_option(response, COAP_OPTION_CONTENT_TYPE, coap_encode_var_bytes(buf, COAP_MEDIATYPE_TEXT_PLAIN), buf);
+
+       coap_add_option(response, COAP_OPTION_MAXAGE, coap_encode_var_bytes(buf, 0x2ffff), buf);
+
+       coap_add_data(response, strlen(INDEX), (unsigned char *)INDEX);
+}
+
+void hnd_get_time(coap_context_t *ctx, struct coap_resource_t *resource, coap_address_t *peer, coap_pdu_t *request, str *token, coap_pdu_t *response)
+{
+       coap_opt_iterator_t opt_iter;
+       coap_opt_t *option;
+       unsigned char buf[40];
+       size_t len;
+       time_t now;
+       coap_tick_t t;
+       coap_subscription_t *subscription;
+
+       /* FIXME: return time, e.g. in human-readable by default and ticks
+        * when query ?ticks is given. */
+
+       /* if my_clock_base was deleted, we pretend to have no such resource */
+       response->hdr->code = my_clock_base ? COAP_RESPONSE_CODE(205) : COAP_RESPONSE_CODE(404);
+
+       if (request != NULL && coap_check_option(request, COAP_OPTION_OBSERVE, &opt_iter)) {
+               subscription = coap_add_observer(resource, peer, token);
+               if (subscription) {
+                       subscription->non = request->hdr->type == COAP_MESSAGE_NON;
+                       coap_add_option(response, COAP_OPTION_OBSERVE, 0, NULL);
+               }
+       }
+       if (resource->dirty == 1) {
+               coap_add_option(response, COAP_OPTION_OBSERVE, coap_encode_var_bytes(buf, ctx->observe), buf);
+       }
+
+       if (my_clock_base) {
+               coap_add_option(response, COAP_OPTION_CONTENT_FORMAT, coap_encode_var_bytes(buf, COAP_MEDIATYPE_TEXT_PLAIN), buf);
+       }
+
+       coap_add_option(response, COAP_OPTION_MAXAGE, coap_encode_var_bytes(buf, 0x01), buf);
+
+       if (my_clock_base) {
+
+               /* calculate current time */
+               coap_ticks(&t);
+               now = my_clock_base + (t / COAP_TICKS_PER_SECOND);
+
+               if (request != NULL && (option = coap_check_option(request, COAP_OPTION_URI_QUERY, &opt_iter))
+                       && memcmp(COAP_OPT_VALUE(option), "ticks", min(5, COAP_OPT_LENGTH(option))) == 0) {
+                       /* output ticks */
+                       len = snprintf((char *)buf, min(sizeof(buf), response->max_size - response->length), "%u", (unsigned int)now);
+                       coap_add_data(response, len, buf);
+
+               } else {                                /* output human-readable time */
+                       struct tm *tmp;
+                       tmp = gmtime(&now);
+                       len = strftime((char *)buf, min(sizeof(buf), response->max_size - response->length), "%b %d %H:%M:%S", tmp);
+                       coap_add_data(response, len, buf);
+               }
+       }
+}
+
+void hnd_put_time(coap_context_t *ctx, struct coap_resource_t *resource, coap_address_t *peer, coap_pdu_t *request, str *token, coap_pdu_t *response)
+{
+       coap_tick_t t;
+       size_t size;
+       unsigned char *data;
+
+       /* FIXME: re-set my_clock_base to clock_offset if my_clock_base == 0
+        * and request is empty. When not empty, set to value in request payload
+        * (insist on query ?ticks). Return Created or Ok.
+        */
+
+       /* if my_clock_base was deleted, we pretend to have no such resource */
+       response->hdr->code = my_clock_base ? COAP_RESPONSE_CODE(204) : COAP_RESPONSE_CODE(201);
+
+       resource->dirty = 1;
+
+       coap_get_data(request, &size, &data);
+
+       if (size == 0) {                        /* re-init */
+               my_clock_base = clock_offset;
+       } else {
+               my_clock_base = 0;
+               coap_ticks(&t);
+               while (size--) {
+                       my_clock_base = my_clock_base * 10 + *data++;
+               }
+               my_clock_base -= t / COAP_TICKS_PER_SECOND;
+       }
+}
+
+void hnd_delete_time(coap_context_t *ctx, struct coap_resource_t *resource, coap_address_t *peer, coap_pdu_t *request, str *token, coap_pdu_t *response)
+{
+       my_clock_base = 0;                      /* mark clock as "deleted" */
+
+       /* type = request->hdr->type == COAP_MESSAGE_CON  */
+       /*   ? COAP_MESSAGE_ACK : COAP_MESSAGE_NON; */
+}
+
+#ifndef WITHOUT_ASYNC
+void hnd_get_async(coap_context_t *ctx, struct coap_resource_t *resource, coap_address_t *peer, coap_pdu_t *request, str *token, coap_pdu_t *response)
+{
+       coap_opt_iterator_t opt_iter;
+       coap_opt_t *option;
+       unsigned long delay = 5;
+       size_t size;
+
+       if (async) {
+               if (async->id != request->hdr->id) {
+                       coap_opt_filter_t f;
+                       coap_option_filter_clear(f);
+                       response->hdr->code = COAP_RESPONSE_CODE(503);
+               }
+               return;
+       }
+
+       option = coap_check_option(request, COAP_OPTION_URI_QUERY, &opt_iter);
+       if (option) {
+               unsigned char *p = COAP_OPT_VALUE(option);
+
+               delay = 0;
+               for (size = COAP_OPT_LENGTH(option); size; --size, ++p) {
+                       delay = delay * 10 + (*p - '0');
+               }
+       }
+
+       async = coap_register_async(ctx, peer, request, COAP_ASYNC_SEPARATE | COAP_ASYNC_CONFIRM, (void *)(COAP_TICKS_PER_SECOND * delay));
+}
+
+void check_async(coap_context_t *ctx, coap_tick_t now)
+{
+       coap_pdu_t *response;
+       coap_async_state_t *tmp;
+
+       size_t size = sizeof(coap_hdr_t) + 8;
+
+       if (!async || now < async->created + (unsigned long)async->appdata) {
+               return;
+       }
+
+       response = coap_pdu_init(async->flags & COAP_ASYNC_CONFIRM ? COAP_MESSAGE_CON : COAP_MESSAGE_NON, COAP_RESPONSE_CODE(205), 0, size);
+       if (!response) {
+               debug("check_async: insufficient memory, we'll try later\n");
+               async->appdata = (void *)((unsigned long)async->appdata + 15 * COAP_TICKS_PER_SECOND);
+               return;
+       }
+
+       response->hdr->id = coap_new_message_id(ctx);
+
+       if (async->tokenlen) {
+               coap_add_token(response, async->tokenlen, async->token);
+       }
+
+       coap_add_data(response, 4, (unsigned char *)"done");
+
+       if (coap_send(ctx, &async->peer, response) == COAP_INVALID_TID) {
+               debug("check_async: cannot send response for message %d\n", response->hdr->id);
+       }
+       coap_delete_pdu(response);
+       coap_remove_async(ctx, async->id, &tmp);
+       coap_free_async(async);
+       async = NULL;
+}
+#endif                                                 /* WITHOUT_ASYNC */
+
+void init_resources(coap_context_t *ctx)
+{
+       coap_resource_t *r;
+
+       r = coap_resource_init(NULL, 0, 0);
+       coap_register_handler(r, COAP_REQUEST_GET, hnd_get_index);
+
+       coap_add_attr(r, (unsigned char *)"ct", 2, (unsigned char *)"0", 1, 0);
+       coap_add_attr(r, (unsigned char *)"title", 5, (unsigned char *)"\"General Info\"", 14, 0);
+       coap_add_resource(ctx, r);
+
+       /* store clock base to use in /time */
+       my_clock_base = clock_offset;
+
+       r = coap_resource_init((unsigned char *)"time", 4, 0);
+       coap_register_handler(r, COAP_REQUEST_GET, hnd_get_time);
+       coap_register_handler(r, COAP_REQUEST_PUT, hnd_put_time);
+       coap_register_handler(r, COAP_REQUEST_DELETE, hnd_delete_time);
+
+       coap_add_attr(r, (unsigned char *)"ct", 2, (unsigned char *)"0", 1, 0);
+       coap_add_attr(r, (unsigned char *)"title", 5, (unsigned char *)"\"Internal Clock\"", 16, 0);
+       coap_add_attr(r, (unsigned char *)"rt", 2, (unsigned char *)"\"Ticks\"", 7, 0);
+       r->observable = 1;
+       coap_add_attr(r, (unsigned char *)"if", 2, (unsigned char *)"\"clock\"", 7, 0);
+
+       coap_add_resource(ctx, r);
+       time_resource = r;
+
+#ifndef WITHOUT_ASYNC
+       r = coap_resource_init((unsigned char *)"async", 5, 0);
+       coap_register_handler(r, COAP_REQUEST_GET, hnd_get_async);
+
+       coap_add_attr(r, (unsigned char *)"ct", 2, (unsigned char *)"0", 1, 0);
+       coap_add_resource(ctx, r);
+#endif                                                 /* WITHOUT_ASYNC */
+}
+
+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,2011 Olaf Bergmann <bergmann@tzi.org>\n\n" "usage: %s [-A address] [-p port]\n\n" "\t-A address\tinterface address to bind to\n" "\t-p port\t\tlisten on specified port\n" "\t-v num\t\tverbosity level (default: 3)\n" "\t-Q exit server program\n", program, version, program);
+}
+
+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;
+
+       s = getaddrinfo(node, port, &hints, &result);
+       if (s != 0) {
+               fprintf(stderr, "getaddrinfo: %d\n", 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 coap_server_test_main(int argc, char **argv)
+{
+       coap_context_t *ctx;
+       fd_set readfds;
+       struct timeval tv, *timeout;
+       int result;
+       coap_tick_t now;
+       coap_queue_t *nextpdu;
+       char *addr_str = NULL;
+       char port_str[NI_MAXSERV] = "5683\0";
+       int opt;
+       coap_log_t log_level = LOG_WARNING;
+
+       while ((opt = getopt(argc, argv, "A:p:v:Q")) != -1) {
+               switch (opt) {
+               case 'A':
+                       addr_str = (char *)coap_malloc(sizeof(char) * NI_MAXHOST);
+                       if (addr_str == NULL) {
+                               coap_log(LOG_CRIT, "memory allocation failure\n");
+                               return -1;
+                       }
+                       strncpy(addr_str, optarg, NI_MAXHOST - 1);
+                       addr_str[NI_MAXHOST - 1] = '\0';
+                       break;
+               case 'p':
+                       strncpy(port_str, optarg, NI_MAXSERV - 1);
+                       port_str[NI_MAXSERV - 1] = '\0';
+                       break;
+               case 'v':
+                       log_level = strtol(optarg, NULL, 10);
+                       break;
+               case 'Q':
+                       quit = 1;
+                       return 0;
+               default:
+                       usage(argv[0], PACKAGE_VERSION);
+                       exit(1);
+               }
+       }
+
+       coap_set_log_level(log_level);
+
+       ctx = get_context(addr_str, port_str);
+       if (!ctx) {
+               return -1;
+       }
+
+       init_resources(ctx);
+       /* initialize global variables */
+       quit = 0;
+
+#if 0
+       signal(SIGINT, handle_sigint);
+#endif
+
+       while (!quit) {
+               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 <= COAP_RESOURCE_CHECK_TIME) {
+                       /* set timeout if there is a pdu to send before our automatic timeout occurs */
+                       tv.tv_usec = ((nextpdu->t) % COAP_TICKS_PER_SECOND) * 1000000 / COAP_TICKS_PER_SECOND;
+                       tv.tv_sec = (nextpdu->t) / COAP_TICKS_PER_SECOND;
+                       timeout = &tv;
+               } else {
+                       tv.tv_usec = 0;
+                       tv.tv_sec = COAP_RESOURCE_CHECK_TIME;
+                       timeout = &tv;
+               }
+               result = select(FD_SETSIZE, &readfds, 0, 0, timeout);
+
+               if (result < 0) {               /* error */
+                       if (errno != EINTR) {
+                               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 */
+                       if (time_resource) {
+                               time_resource->dirty = 1;
+                       }
+               }
+
+#ifndef WITHOUT_ASYNC
+               /* check if we have to send asynchronous responses */
+               check_async(ctx, now);
+#endif                                                 /* WITHOUT_ASYNC */
+
+#ifndef WITHOUT_OBSERVE
+               /* check if we have to send observe notifications */
+               coap_check_notify(ctx);
+#endif                                                 /* WITHOUT_OBSERVE */
+       }
+
+       coap_free_context(ctx);
+       if (addr_str) {
+               coap_free(addr_str);
+       }
+
+       return 0;
+}