First of all thank you for writing and sharing this code. While there is a lot of code it makes it easier (for me at least) to comprehend how one might implement CoAP using uIP.
I wonder if you can direct me to a more elaborate example using the node router.
While there is a function smcp_handle_list() that is 'hooked in' when no request handler is specified for a node, it appears that to assist in discovery the root node should have a child node ".well-known" with a child "core" that has a hander to provide a response that is decorated with each node's attributes - correct ?Â
Then the other nodes can be connected to the root with their own request handlers. It might be nice to add the attributes to the individual nodes as they are linked in so that the smcp_handle_list can automatically produce the discovery response that remains 'in-sync' with the actual list of nodes ? Or am I missing something...
If I'm implementing the IPSO application framework for example I would have a /dev node off of the root with /mdl, /ser child nodes, each with their own handler or does it make more sense to have a single dev_request_handler that would handle the child node requests ?
Well, about the most elaborate example that I've made pubic is the "complex" contiki example. But I agree that some more documentation and examples are warranted. Perhaps we would collaborate to put one together!
If your device is really constrained, you may want to consider not using the node router, writing your own request router instead. You would have an inbound packet handler that would simply examine the path options and optionally call other handlers directly depending on whatever criteria you want. You don't get the convenience of the "list" handler, but you do get something that is much smaller and flexible.On the other hand, if your device isn't really resource constrained then you can do whatever is more convenient. Having separate nodes for "/dev/mdl" and "/dev/ser" (as opposed to writing a big handler which does both) may make your code easier read and to maintain, at the moderate expense of additional memory consumption and packet processing time.
Robert-Wow - thanks for your detailed reply.Well, about the most elaborate example that I've made pubic is the "complex" contiki example. But I agree that some more documentation and examples are warranted. Perhaps we would collaborate to put one together!Shortly after posting here I did find the "complex" contiki example. It helped a lot and I have made some progress.I think you could add portions of the text in your response to the documentation that may answer a lot of questions for people. However, as the saying goes, the shoe maker's kids are barefoot...
In order to get this to compile and run on my target, I had to make a lot of changes. My dev target is based upon the Renesas Rx62N, and the C compiler, while pretty good, had a few problems compiling the code that I've struggled to work around. Also all of the conditional compilation options - while improving the portability - I found hard to wade through, so I pretty much have gone through a copy of the code and 'configured' it to work in my environment by removing chunks of the source which I didn't require. I also only added a few files at a time and did some testing as I bolted pieces back on. I realize that this makes my changes non-portable, but hopefully I can give back when I've made some progress.
My dev target isn't too resource constrained; 512K flash, 96K SRAM, 32M DRAM, 10/100 Ethernet. I've modified the node router node structure to add a link content member that can be set when configuring the nodes to provide the attributes during discovery. This is added along with the ;ct=40 attribute if has_link_content flag is set.
D:\Data\VS2K13\CoAP.NET-master\CoAP.Example\CoAP.Client\bin\Debug\NET40>coapclient DISCOVER coap://192.168.1.200
BTW – does SMCP stand for Simple Message Control Protocol ?
smcp_status_tsmcp_handle_list(smcp_node_t node) { smcp_status_t ret = 0; char* replyContent; coap_size_t content_break_threshold = 256; size_t filter_len = 0; char * filter = NULL; char * value = NULL; char * attribute = NULL; char * found = NULL; char * end = NULL; char lastchar = '\0'; bool contentOutput = false; char * path = (char *)malloc(256); // The path "/.well-known/core" is a special case. If we get here, // we know that it isn't being handled explicitly, so we just // show the root listing as a reasonable default. if (!node->parent) { if (smcp_inbound_option_strequal_const(COAP_OPTION_URI_PATH, ".well-known")) { smcp_inbound_next_option(NULL, NULL); if (smcp_inbound_option_strequal_const(COAP_OPTION_URI_PATH, "core")) { smcp_inbound_next_option(NULL, NULL); } else { ret = SMCP_STATUS_NOT_ALLOWED; goto bail; } } }
if (smcp_inbound_option_strequal_const(COAP_OPTION_URI_PATH, ""))Â { // Handle trailing '/'. smcp_inbound_next_option(NULL, NULL); }
// Check over the headers to make sure they are sane. { coap_option_key_t key; const uint8_t* val; coap_size_t val_len; while ((key = smcp_inbound_next_option(&val, &val_len)) != COAP_OPTION_INVALID)Â { require_action(key != COAP_OPTION_URI_PATH, bail, ret = SMCP_STATUS_NOT_FOUND); /* Q - query option ? */ if (key == COAP_OPTION_URI_QUERY)Â { /* yes - must be filter (key=value) */ filter = (char *)val; filter_len = val_len; /* Q - filter have length ? */ if (filter_len) { /* yes - terminate filter key, point to value */ value = strchr(filter, '='); value[0] = '\0'; ++value; /* correct filter length to length of value */ filter_len -= strlen(filter) + 1;
/* remember and remove last character of key */ lastchar = value[filter_len - 1]; value[filter_len - 1] = '\0'; /* start response... */ ret = smcp_outbound_begin_response(COAP_RESULT_205_CONTENT); require_noerr(ret, bail);
ret = smcp_outbound_add_option_uint(COAP_OPTION_CONTENT_TYPE, COAP_CONTENT_TYPE_APPLICATION_LINK_FORMAT); require_noerr(ret, bail);
replyContent = smcp_outbound_get_content_ptr(&content_break_threshold); require(NULL != replyContent, bail);
replyContent[0] = 0;
/* process list of nodes... */
while (node)Â { smcp_node_t next;
/* generate inclusive path to node */ smcp_node_get_path(node, path, 256, false); /* Q - room in the generated content for the bracketed path ? */ if ((strlen(path) + 2) > (content_break_threshold - strlen(replyContent) - 1)) { /* no */ break; }
/* Q - was there a query filter ? */ if (filter_len) { /* yes - Q - is the filter key == "href" ? */ if (strcmp(filter, "href") == 0) { /* yes - Q - does the href key value not occur in this node's path or is it anchored and the match is not ? */ attribute = strstr(path, value); if ( attribute == NULL || (value[0] == '/' && attribute != path)) { /* yes */ goto next_node; } /* point after href key value */ end = attribute + strlen(attribute); } else { /* no - Q - does this node have attributes ? */ if (node->has_link_content) { /* yes - does the filter not appear in the attributes or does the match not end with '=' or '"' ? */ attribute = strstr(node->link_content, filter); if ( attribute == NULL || ( attribute[strlen(filter)] != '=' && attribute[strlen(filter)] != '"')) { /* yes */ goto next_node; } /* yes - advance past '=' or '"' */ attribute += strlen(filter) + 2; /* point to end of matching quoted string */ end = strchr(attribute, '"'); } } /* node link content has matching filter string */ found = attribute;
/* look for filter value in attribute... */ while ((found = strstr(found, value)) != NULL) { /* Q - found past the end of the filter ? */ if (found > end) { /* yes */ found = NULL; break; } /* Q - does the last character of the filter value match or is '*' ? */ if (lastchar == found[filter_len - 1] || lastchar == '*') { /* yes */ break; } /* advance past this match */ ++found; } /* Q - was there a matching filter value ? */ if (found == NULL) { /* no */ goto next_node; } /* node link content has matching prefix */ /* Q - is it a non-exploded exact match ? */ if ( lastchar != '*' && ( found[filter_len] != '"'Â && found[filter_len] != ' 'Â && found[filter_len] != '\0')) { /* no */ goto next_node; } /* filter=value has a match */ } /* Q - has any content been generated ? */ if (contentOutput) { /* yes - prefix additional output with comma */#if SMCP_ADD_NEWLINES_TO_LIST_OUTPUT strlcat(replyContent, &",\n"[!node], content_break_threshold);#else strlcat(replyContent, &","[!node], content_break_threshold);#endif contentOutput = false; }
/* Q - is there a generated path ? */ if (path[0])Â { /* yes - add it surrounded with < > if it will fit */ strlcat(replyContent, "<", content_break_threshold); size_t len = strlen(replyContent); if (content_break_threshold - 1 > len) strlcat(replyContent + len, path, content_break_threshold - len); strlcat(replyContent, ">", content_break_threshold); contentOutput = true; }
/* Q - node have child nodes or attributes ? */ if (node->children || node->has_link_content) { /* yes - Q - node have attributes ? */ if (node->link_content != NULL) { /* if attributes doesn't start with ; then prefix it with one */ if (node->link_content[0] != ';') strlcat(replyContent, ";", content_break_threshold); strlcat(replyContent, node->link_content, content_break_threshold); } /* add content type attribute */ strlcat(replyContent, ";ct=40", content_break_threshold); contentOutput = true; }
/* add observable tag if required */ if (node->is_observable) strlcat(replyContent, ";obs", content_break_threshold);
next_node:
/* process next node... */ if (((smcp_node_t)node)->children) next = ((smcp_node_t)node)->children; else next = ll_next((void*)node);
node = next; }
/* send response */ ret = smcp_outbound_set_content_len((coap_size_t)strlen(replyContent)); require_noerr(ret,bail);
ret = smcp_outbound_send();
bail: free(path); return ret;}
Enter code here...Robert-I made the following changes to the node router smcp_handle_list( ) to allow it to automatically generate the /.well-known/core response from the nodes:
. . .
smcp_status_tsmcp_handle_list(smcp_node_t root_node)Â { smcp_node_t node = NULL; smcp_status_t ret = 0; char* replyContent; coap_size_t content_break_threshold = 256; size_t filter_len = 0; char * filter = NULL; char * value = NULL; char * attribute = NULL; char * found = NULL; char * end = NULL; char lastchar = '\0'; bool contentOutput = false; char * path = (char *)malloc(256); // The path "/.well-known/core" is a special case. If we get here, // we know that it isn't being handled explicitly, so we just // show the root listing as a reasonable default. if (!root_node->parent)Â require_action(root_node, bail, ret = SMCP_STATUS_BAD_ARGUMENT);
node = root_node->children;
/* start response... */ ret = smcp_outbound_begin_response(COAP_RESULT_205_CONTENT); require_noerr(ret, bail);
ret = smcp_outbound_add_option_uint(COAP_OPTION_CONTENT_TYPE, COAP_CONTENT_TYPE_APPLICATION_LINK_FORMAT); require_noerr(ret, bail);
replyContent = smcp_outbound_get_content_ptr(&content_break_threshold); require(NULL != replyContent, bail);
replyContent[0] = 0;
/* process list of nodes... */
while (node)Â { if (node->children) { node = node->children; } else { // leaf node - find the parent while (((smcp_node_t)ll_next((void *)node) == NULL) && (node != root_node)) { node = node->parent; } node = (smcp_node_t)ll_next((void *)node);Robert-
Â
Not a problem – I should again say thank you for taking the time to write and develop this and continue to support it. Hopefully I haven’t taken up too much of your time with my questions.
Â
By the way, I added the following function to smcp-observable.c so that I could cancel an observation on the server side from my scripting language:
Â
smcp_status_t
smcp_observable_cancel(smcp_observable_t context, uint8_t key)
{
               smcp_status_t ret = SMCP_STATUS_OK;
               int8_t i;
Â
               for (i = context->first_observer - 1; i >= 0; i = observer_table[i].next - 1)
               {
                               if (observer_table[i].key == key)
                                               break;
               }
Â
               if (i != -1)
               {
                               free_observer(&observer_table[i]);
               }
Â
bail:
               return ret;
}
Â
Â
char *strmbtok(char *input, char *delimit, char *openblock, char *closeblock)Â {Â Â static char *token = NULL;Â Â char *lead = NULL;Â Â char *block = NULL;Â Â int iBlock = 0;Â Â int iBlockIndex = 0;
  if (input != NULL) {    token = input;    lead = input;  }  else {    lead = token;    if (*token == '\0') {      lead = NULL;    }  }
  while (*token != '\0') {    if (iBlock) {      if (closeblock[iBlockIndex] == *token) {        iBlock = 0;      }        token++;      continue;    }      if ((block = strchr(openblock, *token)) != NULL) {      iBlock = 1;      iBlockIndex = block - openblock;      token++;      continue;    }    if (strchr(delimit, *token) != NULL) {      *token = '\0';      token++;      break;    }      token++;  }   return lead;}