Operation of node router...

37 views
Skip to first unread message

Steven Ackerman

unread,
Oct 1, 2014, 2:45:05 PM10/1/14
to smcp...@googlegroups.com
Robert-

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 ?

Robert Quattlebaum

unread,
Oct 2, 2014, 3:51:04 PM10/2/14
to Steven Ackerman, SMCP Developers Group Group
Hi Steven!

On Oct 1, 2014, at 11:45 AM, Steven Ackerman <sjack...@verizon.net> wrote:

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.

Thanks for giving it a try!

I wonder if you can direct me to a more elaborate example using the node router.

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!

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 ? 

From reading over the full email, it sounds like you have a fair understanding of what is going on, but just for the sake of clarity I'll spell out what the node router does very specifically:

The node router is a way to dispatch inbound packets to a specific packet handler based on a hierarchical interpretation of the path options. When an inbound packet is being processed, it starts out being evaluated at the root node. If the next path option of the inbound packet matches the name of one of the children of the current node, we descent into that child and move forward to the next path element. We continue to descend and follow this process until we get to a node where we don't have a match. Once this happens—even if there are additional path elements which haven't matched—the packet is processed by the handler of the current node.

Keep in mind that this parsing of the path and descending into child nodes happens if there is a handler specified for that node or not.

If a handler isn't specified, the list handler is used. The list handler is simply a convenience function which returns a link list of that node's children. It's handy and easy to use, but in exchange you give up a little flexibility. For example, with the exception of adding child nodes, you currently can't programmatically add entries to the list, nor can you easily add additional attributes to items in the list.

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...

As a convenience (that, by the way, is actually built into the list node), if the root doesn't have a handler (and neither does "/.well-known/core"), then a request for "/.well-known/core" is interpreted as a request for the root (but will yield absolute URIs in the link list instead of relative URIs). But this is really only useful for quick prototyping: to do proper service discovery, you'll need to list more than just the contents of your root node: so unless you are doing something very simple, you will likely need a custom handler for "/.well-known" or "/.well-known/core".

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 ?

You could imagine having handlers for "/.well-known", "/dev/mdl", and "/dev/ser" using the list handler for "/". As for what makes sense, consider the following:

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.

Originally, the node router was an integral part of SMCP, which really bogged it down. After learning how node.js worked, I decided to divorce the node router from the rest of SMCP, allowing the developer the flexibility to handle inbound packets in whatever way they like. The node router is convenient for certain applications when you are using hardware that isn't too constrained, but for heavily constrained devices I'm finding that rolling your own request router is the better way to go. Observability still works.

You can also still use the confusingly-named "variable node"---which isn't really a node but a convenience handler for implementing interfaces with lots of variables. 

The convention of the node router (which is also followed by the "variable node") is that when your handler is called that the next option you fetch will be the option immediately after the path that was evaluated to get you to that handler. So if you want to use the variable node handler to wrap around getting and setting variables ("a", "b", and "c") that lives at "/mydevice/blah/", when the variable node handles a GET for "/mydevice/blah/a", the first option it fetches will be the urlpath option "a".

I'm open to suggestions. My big task at the moment is implementing support for "sessions", which is a requirement for DTLS and TCP support.

Thoughts?

-- Robert

Steven Ackerman

unread,
Oct 2, 2014, 5:14:27 PM10/2/14
to smcp...@googlegroups.com
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.

I'm running uIP with a port demultiplexer on the front end distributing uip_appcalls and supporting several protocols using protothreads under a single FreeRTOS task - currently supporting Telnet, FTP server, VNC server, simple HTTP server, DHCP client, NTP client, SMTP client and ArtNet. I was looking for a way to provide some M2M communications between devices and that's when I decided to investigate CoAP which led me to your code.

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.


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.

Then, like your complex contiki example I added a create_dev_node( ) which adds the /dev and children along with their link attributes:

// dev node
smcp_status_t dev_name_request_handler(void *context)
{
smcp_status_t result = SMCP_STATUS_NOT_ALLOWED;
if (smcp_inbound_get_code() == COAP_METHOD_GET)
{
smcp_outbound_begin_response(COAP_RESULT_205_CONTENT);
smcp_outbound_add_option_uint( COAP_OPTION_CONTENT_TYPE, 
COAP_CONTENT_TYPE_TEXT_PLAIN);
smcp_outbound_append_content("CFSound-IV", SMCP_CSTR_LEN);
result = smcp_outbound_send();
}
return result;
}

smcp_status_t dev_sn_request_handler(void *context)
{
smcp_status_t result = SMCP_STATUS_NOT_ALLOWED;
if (smcp_inbound_get_code() == COAP_METHOD_GET)
{
smcp_outbound_begin_response(COAP_RESULT_205_CONTENT);
smcp_outbound_add_option_uint( COAP_OPTION_CONTENT_TYPE, 
COAP_CONTENT_TYPE_TEXT_PLAIN);
smcp_outbound_append_content(GetSerialNumberFormatted(), SMCP_CSTR_LEN);
result = smcp_outbound_send();
}
return result;
}

smcp_status_t dev_reset_request_handler(void *context)
{
smcp_status_t result = SMCP_STATUS_NOT_ALLOWED;
if (smcp_inbound_get_code() == COAP_METHOD_POST)
{
HW_ResetSystem();
result = SMCP_STATUS_OK;
}
return result;
}

void create_dev_node(smcp_node_t parent)
{
smcp_node_t dev_node = smcp_node_init(NULL, parent, "dev");
dev_node->has_link_content = 1; dev_node->link_content = "rt=\"simple.dev\";if=\"core.ll\"";
smcp_node_t node = smcp_node_init(NULL, dev_node, "name");
node->request_handler = dev_name_request_handler;
node->has_link_content = 1; node->link_content = "rt=\"simple.dev.name\";if=\"core.rp\"";
node = smcp_node_init(NULL, dev_node, "sn");
node->request_handler = dev_sn_request_handler;
node->has_link_content = 1; node->link_content = "rt=\"simple.dev.sn\";if=\"core.rp\"";
node = smcp_node_init(NULL, dev_node, "reset");
node->request_handler = dev_reset_request_handler;
node->has_link_content = 1; node->link_content = "rt=\"simple.dev.reset\";if=\"core.p\"";
}

/*---------------------------------------------------------------------------*/
static PT_THREAD(handle_smcp(void))
{
PT_BEGIN(&gState->pt);
do
{
PT_YIELD_UNTIL(&gState->pt, uip_newdata() || uip_poll());
if (uip_newdata())
{
smcp_sockaddr_t addr;
smcp_inbound_start_packet(uip_appdata, uip_datalen());

memcpy(&addr.smcp_addr,&UIP_IP_BUF->srcipaddr,sizeof(addr.smcp_addr));
addr.smcp_port = UIP_UDP_BUF->srcport;
smcp_inbound_set_srcaddr(&addr);

memcpy(&addr.smcp_addr,&UIP_IP_BUF->destipaddr,sizeof(addr.smcp_addr));
addr.smcp_port = UIP_UDP_BUF->destport;
smcp_inbound_set_destaddr(&addr);

smcp_inbound_finish_packet();
}
else if (uip_poll())
{
smcp_handle_timers();
}
gState->is_responding = false;
} while (1);

PT_END(&gState->pt);
}

/*---------------------------------------------------------------------------*/
void smcp_init(uint16_t port)
{
struct uip_udp_conn *conn;
gState = NULL;
if ((conn = uip_udp_new(NULL, HTONS(port))) != NULL)
{
uip_udp_bind(conn, HTONS(port));
gState = (smcp_t)(&(conn->appstate.States.smcpState));
memset(gState, 0, sizeof (struct smcp_state));
gState->udp_conn = conn;
gState->udp_conn->rport = 0;
PT_INIT(&gState->pt);

smcp_log(gState->log, "SMCP: init port:%u", port);
smcp_node_router_init();
gRootNode = smcp_node_init(NULL, NULL, NULL);
smcp_set_default_request_handler(smcp_node_router_handler, (void *)gRootNode);
create_dev_node(gRootNode);
}
}

/*---------------------------------------------------------------------------*/
void smcp_appcall(void)
{
assert(gState);
handle_smcp();
}

smcp_t smcp_get_state(void)
{
assert(gState);
return gState;
}

void smcp_set_default_request_handler(smcp_request_handler_func request_handler, void* context)
{
assert(gState);
gState->request_handler = request_handler;
gState->request_handler_context = context;
}

This has allowed me to do the following without having to write a /well-known/core handler (using CoAP.NET example client):

D:\Data\VS2K13\CoAP.NET-master\CoAP.Example\CoAP.Client\bin\Debug\NET40>coapclient DISCOVER coap://192.168.1.200
==[ COAP Request ]============================================
ID     : -1
Type   : CON
Token  :
Method : GET
Dest   : 192.168.1.200:5683
Options: URI-Path=.well-known, core, Content-Type=text/plain
Payload: 0 Bytes
===============================================================

==[ COAP Response ]============================================
ID     : 64357
Type   : ACK
Token  : 68C0C24B
Status : 2.05 Content
Options: Content-Type=application/link-format
Payload: 41 Bytes
---------------------------------------------------------------
</dev/>rt="simple.dev";if="core.ll";ct=40
===============================================================

Time (ms): 30.0019

D:\Data\VS2K13\CoAP.NET-master\CoAP.Example\CoAP.Client\bin\Debug\NET40>coapclient DISCOVER coap://192.168.1.200/dev
==[ COAP Request ]============================================
ID     : -1
Type   : CON
Token  :
Method : GET
Dest   : 192.168.1.200:5683
Options: URI-Path=dev, Content-Type=text/plain
Payload: 0 Bytes
===============================================================

==[ COAP Response ]============================================
ID     : 2789
Type   : ACK
Token  : 335F587E
Status : 2.05 Content
Options: Content-Type=application/link-format
Payload: 146 Bytes
---------------------------------------------------------------
<dev/reset>rt="simple.dev.reset";if="core.p";ct=40,<dev/sn>rt="simple.dev.sn";if="core.rp";ct=40,<dev/name>rt="simple.dev.name";if="core.rp";ct=40
===============================================================

Time (ms): 32.0027

D:\Data\VS2K13\CoAP.NET-master\CoAP.Example\CoAP.Client\bin\Debug\NET40>

Haven't looked at the variable_node yet - but it looks like it could be very useful.

Steven J. Ackerman


Steven Ackerman

unread,
Oct 3, 2014, 1:00:18 PM10/3/14
to smcp...@googlegroups.com
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_t
smcp_handle_list(smcp_node_t node) 
{
smcp_status_t ret = 0;
char* replyContent;
coap_size_t content_break_threshold = 256;
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* value;
coap_size_t value_len;
while ((key = smcp_inbound_next_option(&value, &value_len)) != COAP_OPTION_INVALID) 
{
require_action(key != COAP_OPTION_URI_PATH, bail, ret = SMCP_STATUS_NOT_FOUND);
if (key == COAP_OPTION_URI_QUERY) 
{
// Skip URI query components for now.
// } 
// else if (key == COAP_OPTION_SIZE_REQUEST) 
// {
// uint8_t i;
// content_break_threshold = 0;
// for (i = 0; i < value_len; i++)
// content_break_threshold =
// (content_break_threshold << 8) + value[i];
// if (content_break_threshold >= sizeof(replyContent)) 
// {
// DEBUG_PRINTF(
// "Requested size (%d) is too large, trimming to %d.",
// (int)content_break_threshold,
// (int)sizeof(replyContent) - 1);
//
// content_break_threshold = sizeof(replyContent) - 1;
// }
} 
else 
{
if (COAP_OPTION_IS_CRITICAL(key)) 
{
ret = SMCP_STATUS_BAD_OPTION;
assert_printf("Unrecognized option %d, \"%s\"",
key,
coap_option_key_to_cstr(key, false));
goto bail;
}
}
}
}

// Node should always be set by the time we get here.
require_action(node, bail, ret = SMCP_STATUS_BAD_ARGUMENT);

if (((smcp_node_t)node)->children)
node = ((smcp_node_t)node)->children;
else
node = NULL;

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;

while (node) 
{
smcp_node_t next;
const char* node_name = node->name;

if (!node_name)
break;

if ((strlen(node_name) + 4) > (content_break_threshold - strlen(replyContent) - 3))
break;

strlcat(replyContent, "<", content_break_threshold);
smcp_node_get_path(node, path, 256, false);

if (path[0]) 
{
size_t len = strlen(replyContent);
if (content_break_threshold-1 > len)
strlcpy(replyContent + len, path, content_break_threshold - len);

strlcat(replyContent, ">", content_break_threshold);

if (node->children || node->has_link_content)
{
if (node->link_content != NULL)
{
strlcat(replyContent, node->link_content, content_break_threshold);
}
strlcat(replyContent, ";ct=40", content_break_threshold);
}

if (node->is_observable)
strlcat(replyContent, ";obs", content_break_threshold);

if (((smcp_node_t)node)->children)
next = ((smcp_node_t)node)->children;
else
next = ll_next((void*)node);

node = next;
#if SMCP_ADD_NEWLINES_TO_LIST_OUTPUT
strlcat(replyContent, &",\n"[!node], content_break_threshold);
#else
strlcat(replyContent, &","[!node], content_break_threshold);
#endif
}

ret = smcp_outbound_set_content_len((coap_size_t)strlen(replyContent));
require_noerr(ret,bail);

ret = smcp_outbound_send();

bail:
free(path);
return ret;
}

. . .

smcp_status_t
smcp_node_get_path(smcp_node_t node, char* path, coap_size_t max_path_len, bool url_encode) 
{
smcp_status_t ret = 0;

require(node, bail);
require(path, bail);

if (node->parent) 
{
// using recursion here just makes this code so much more pretty,
// but it would be ideal to avoid using recursion at all,
// to be nice to the stack. Just a topic of future investigation...
ret = smcp_node_get_path(node->parent, path, max_path_len, url_encode);
} 
else 
{
path[0] = 0;
}


if (node->name) 
{
size_t len;
strlcat(path, "/", max_path_len);
len = strlen(path);

if (max_path_len > len)
{
if (url_encode)
url_encode_cstr(path+len, node->name, max_path_len - len);
else
strlcpy(path + len, node->name, max_path_len - len);
}
}

bail:
return ret;
}

Here's the request / response with these changes:

D:\Data\VS2K13\CoAP.NET-master\CoAP.Example\CoAP.Client\bin\Debug\NET40>coapclient DISCOVER coap://192.168.1.200
==[ COAP Request ]============================================
ID     : -1
Type   : CON
Token  :
Method : GET
Dest   : 192.168.1.200:5683
Options: URI-Path=.well-known, core, Content-Type=text/plain
Payload: 0 Bytes
===============================================================

==[ COAP Response ]============================================
ID     : 9295
Type   : ACK
Token  : 1227510C
Status : 2.05 Content
Options: Content-Type=application/link-format
Payload: 190 Bytes
---------------------------------------------------------------
</dev>rt="simple.dev";if="core.ll";ct=40,</dev/reset>rt="simple.dev.reset";if="core.p";ct=40,</dev/sn>rt="simple.dev.sn";if="core.rp";ct=40,</dev/name>rt="simple.dev.name";if="core.rp";ct=40
===============================================================

Time (ms): 31.0007

Robert Quattlebaum

unread,
Oct 7, 2014, 1:42:54 PM10/7/14
to Steven Ackerman, SMCP Developers Group Group
On Oct 2, 2014, at 2:14 PM, Steven Ackerman <sjack...@verizon.net> wrote:

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...

That's a good suggestion and I've updated the documentation accordingly: http://darconeous.github.io/smcp/doc/html/group__smcp-node-router.html#details

Of course, there is still lots of documentation to be done, but it's a start. :)

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.

I'd love to see what sort of changes were necessary. Mind forking and posting your changes to Github?

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.

You know, given the fact that the node router is kinda targeted at higher-performing systems, I guess it wouldn't be so bad to add functionality to make up for the inflexibility. I had been resisting that urge initially, but, well, if it's intended to be used by more beefy microcontrollers, then why not. 😊

D:\Data\VS2K13\CoAP.NET-master\CoAP.Example\CoAP.Client\bin\Debug\NET40>coapclient DISCOVER coap://192.168.1.200

By the way, have you tried smcpctl? You'd probably need to use cygwin to build it on windows at the moment (patches accepted if you get it building with mingw!), but it sports a very nice and convenient command line interface.

-- Robert

Robert Quattlebaum

unread,
Oct 7, 2014, 1:43:45 PM10/7/14
to Steven Ackerman, SMCP Developers Group Group
Mind forking smcp on github and sharing your changes there? It will make it easier to review.

Robert Quattlebaum

unread,
Oct 7, 2014, 5:17:18 PM10/7/14
to Steven J. Ackerman, SMCP Developers Group Group

On Oct 7, 2014, at 11:22 AM, Steven J. Ackerman <sjack...@verizon.net> wrote:

BTW – does SMCP stand for Simple Message Control Protocol ?

Originally---long before I had heard of CoAP---I was trying to create a protocol for home automation (that happened to be built on very similar ideas to CoAP). That was going to be called "Simple Management and Control Protocol", and I named the code base accordingly. I eventually ported over the code base to use CoAP, but the name stuck. Now it doesn't mean much of anything. 😊

-- Robert

Steven Ackerman

unread,
Oct 20, 2014, 11:12:38 AM10/20/14
to smcp...@googlegroups.com
Some additional changes to the node router smcp_handle_list( ) to add support for search query strings per RFC 6690 section 4.1:

smcp_status_t
smcp_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...



On Friday, October 3, 2014 1:00:18 PM UTC-4, Steven Ackerman wrote:
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:

. . .

Steven Ackerman

unread,
Nov 11, 2014, 10:16:34 AM11/11/14
to smcp...@googlegroups.com
Some additional changes to the smcp-node-router to correctly walk the node tree in smcp_handle_list function:

smcp_status_t
smcp_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 Quattlebaum

unread,
Nov 12, 2014, 1:45:51 PM11/12/14
to Steven Ackerman, SMCP Developers Group Group
By the way, I just wanted to say thanks for your input. It is exciting to see someone not only using SMCP but also extending it!

I haven't had a chance to consider integrating these changes, but I hope to in the coming weeks. 

-- Robert
signature.asc

Steven J. Ackerman

unread,
Nov 12, 2014, 2:00:50 PM11/12/14
to Robert Quattlebaum, SMCP Developers Group Group

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;

}

 

 

Steven Ackerman

unread,
Nov 14, 2014, 1:33:28 PM11/14/14
to smcp...@googlegroups.com, darco...@gmail.com
Added the following function to string-utils.* to allow me to split the comma separated link attributes into multiple string array elements in the scripting language. Strtok() doesn't correctly handle delimiters appearing within quoted strings for example. This function allows you to specify multiple matched block markers to identify string portions to not search for the delimiters in:

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;
}



Steven Ackerman

unread,
Nov 14, 2014, 1:43:06 PM11/14/14
to smcp...@googlegroups.com, darco...@gmail.com
Let's try that again:
Reply all
Reply to author
Forward
0 new messages