556 lines
14 KiB
C
556 lines
14 KiB
C
|
#include "user_config.h"
|
||
|
#include "c_stdio.h"
|
||
|
#include "c_string.h"
|
||
|
#include "coap.h"
|
||
|
#include "uri.h"
|
||
|
|
||
|
extern void endpoint_setup(void);
|
||
|
extern const coap_endpoint_t endpoints[];
|
||
|
|
||
|
#ifdef COAP_DEBUG
|
||
|
void coap_dumpHeader(coap_header_t *hdr)
|
||
|
{
|
||
|
c_printf("Header:\n");
|
||
|
c_printf(" ver 0x%02X\n", hdr->ver);
|
||
|
c_printf(" t 0x%02X\n", hdr->ver);
|
||
|
c_printf(" tkl 0x%02X\n", hdr->tkl);
|
||
|
c_printf(" code 0x%02X\n", hdr->code);
|
||
|
c_printf(" id 0x%02X%02X\n", hdr->id[0], hdr->id[1]);
|
||
|
}
|
||
|
|
||
|
void coap_dump(const uint8_t *buf, size_t buflen, bool bare)
|
||
|
{
|
||
|
if (bare)
|
||
|
{
|
||
|
while(buflen--)
|
||
|
c_printf("%02X%s", *buf++, (buflen > 0) ? " " : "");
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
c_printf("Dump: ");
|
||
|
while(buflen--)
|
||
|
c_printf("%02X%s", *buf++, (buflen > 0) ? " " : "");
|
||
|
c_printf("\n");
|
||
|
}
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
int coap_parseHeader(coap_header_t *hdr, const uint8_t *buf, size_t buflen)
|
||
|
{
|
||
|
if (buflen < 4)
|
||
|
return COAP_ERR_HEADER_TOO_SHORT;
|
||
|
hdr->ver = (buf[0] & 0xC0) >> 6;
|
||
|
if (hdr->ver != 1)
|
||
|
return COAP_ERR_VERSION_NOT_1;
|
||
|
hdr->t = (buf[0] & 0x30) >> 4;
|
||
|
hdr->tkl = buf[0] & 0x0F;
|
||
|
hdr->code = buf[1];
|
||
|
hdr->id[0] = buf[2];
|
||
|
hdr->id[1] = buf[3];
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
int coap_buildHeader(const coap_header_t *hdr, uint8_t *buf, size_t buflen)
|
||
|
{
|
||
|
// build header
|
||
|
if (buflen < 4)
|
||
|
return COAP_ERR_BUFFER_TOO_SMALL;
|
||
|
|
||
|
buf[0] = (hdr->ver & 0x03) << 6;
|
||
|
buf[0] |= (hdr->t & 0x03) << 4;
|
||
|
buf[0] |= (hdr->tkl & 0x0F);
|
||
|
buf[1] = hdr->code;
|
||
|
buf[2] = hdr->id[0];
|
||
|
buf[3] = hdr->id[1];
|
||
|
return 4;
|
||
|
}
|
||
|
|
||
|
int coap_parseToken(coap_buffer_t *tokbuf, const coap_header_t *hdr, const uint8_t *buf, size_t buflen)
|
||
|
{
|
||
|
if (hdr->tkl == 0)
|
||
|
{
|
||
|
tokbuf->p = NULL;
|
||
|
tokbuf->len = 0;
|
||
|
return 0;
|
||
|
}
|
||
|
else
|
||
|
if (hdr->tkl <= 8)
|
||
|
{
|
||
|
if (4 + hdr->tkl > buflen)
|
||
|
return COAP_ERR_TOKEN_TOO_SHORT; // tok bigger than packet
|
||
|
tokbuf->p = buf+4; // past header
|
||
|
tokbuf->len = hdr->tkl;
|
||
|
return 0;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// invalid size
|
||
|
return COAP_ERR_TOKEN_TOO_SHORT;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
int coap_buildToken(const coap_buffer_t *tokbuf, const coap_header_t *hdr, uint8_t *buf, size_t buflen)
|
||
|
{
|
||
|
// inject token
|
||
|
uint8_t *p;
|
||
|
if (buflen < 4 + hdr->tkl)
|
||
|
return COAP_ERR_BUFFER_TOO_SMALL;
|
||
|
p = buf + 4;
|
||
|
if ((hdr->tkl > 0) && (hdr->tkl != tokbuf->len))
|
||
|
return COAP_ERR_UNSUPPORTED;
|
||
|
|
||
|
if (hdr->tkl > 0)
|
||
|
c_memcpy(p, tokbuf->p, hdr->tkl);
|
||
|
|
||
|
// http://tools.ietf.org/html/draft-ietf-core-coap-18#section-3.1
|
||
|
// inject options
|
||
|
return hdr->tkl;
|
||
|
}
|
||
|
|
||
|
// advances p
|
||
|
int coap_parseOption(coap_option_t *option, uint16_t *running_delta, const uint8_t **buf, size_t buflen)
|
||
|
{
|
||
|
const uint8_t *p = *buf;
|
||
|
uint16_t len, delta;
|
||
|
uint8_t headlen = 1;
|
||
|
|
||
|
if (buflen < headlen) // too small
|
||
|
return COAP_ERR_OPTION_TOO_SHORT_FOR_HEADER;
|
||
|
|
||
|
delta = (p[0] & 0xF0) >> 4;
|
||
|
len = p[0] & 0x0F;
|
||
|
|
||
|
// These are untested and may be buggy
|
||
|
if (delta == 13)
|
||
|
{
|
||
|
headlen++;
|
||
|
if (buflen < headlen)
|
||
|
return COAP_ERR_OPTION_TOO_SHORT_FOR_HEADER;
|
||
|
delta = p[1] + 13;
|
||
|
p++;
|
||
|
}
|
||
|
else
|
||
|
if (delta == 14)
|
||
|
{
|
||
|
headlen += 2;
|
||
|
if (buflen < headlen)
|
||
|
return COAP_ERR_OPTION_TOO_SHORT_FOR_HEADER;
|
||
|
delta = ((p[1] << 8) | p[2]) + 269;
|
||
|
p+=2;
|
||
|
}
|
||
|
else
|
||
|
if (delta == 15)
|
||
|
return COAP_ERR_OPTION_DELTA_INVALID;
|
||
|
|
||
|
if (len == 13)
|
||
|
{
|
||
|
headlen++;
|
||
|
if (buflen < headlen)
|
||
|
return COAP_ERR_OPTION_TOO_SHORT_FOR_HEADER;
|
||
|
len = p[1] + 13;
|
||
|
p++;
|
||
|
}
|
||
|
else
|
||
|
if (len == 14)
|
||
|
{
|
||
|
headlen += 2;
|
||
|
if (buflen < headlen)
|
||
|
return COAP_ERR_OPTION_TOO_SHORT_FOR_HEADER;
|
||
|
len = ((p[1] << 8) | p[2]) + 269;
|
||
|
p+=2;
|
||
|
}
|
||
|
else
|
||
|
if (len == 15)
|
||
|
return COAP_ERR_OPTION_LEN_INVALID;
|
||
|
|
||
|
if ((p + 1 + len) > (*buf + buflen))
|
||
|
return COAP_ERR_OPTION_TOO_BIG;
|
||
|
|
||
|
//printf("option num=%d\n", delta + *running_delta);
|
||
|
option->num = delta + *running_delta;
|
||
|
option->buf.p = p+1;
|
||
|
option->buf.len = len;
|
||
|
//coap_dump(p+1, len, false);
|
||
|
|
||
|
// advance buf
|
||
|
*buf = p + 1 + len;
|
||
|
*running_delta += delta;
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
// http://tools.ietf.org/html/draft-ietf-core-coap-18#section-3.1
|
||
|
int coap_parseOptionsAndPayload(coap_option_t *options, uint8_t *numOptions, coap_buffer_t *payload, const coap_header_t *hdr, const uint8_t *buf, size_t buflen)
|
||
|
{
|
||
|
size_t optionIndex = 0;
|
||
|
const uint8_t *p = buf + 4 + hdr->tkl;
|
||
|
const uint8_t *end = buf + buflen;
|
||
|
int rc;
|
||
|
uint16_t delta = 0;
|
||
|
if (p > end)
|
||
|
return COAP_ERR_OPTION_OVERRUNS_PACKET; // out of bounds
|
||
|
|
||
|
//coap_dump(p, end - p);
|
||
|
|
||
|
// 0xFF is payload marker
|
||
|
while((optionIndex < *numOptions) && (p < end) && (*p != 0xFF))
|
||
|
{
|
||
|
if (0 != (rc = coap_parseOption(&options[optionIndex], &delta, &p, end-p)))
|
||
|
return rc;
|
||
|
optionIndex++;
|
||
|
}
|
||
|
*numOptions = optionIndex;
|
||
|
|
||
|
if (p+1 < end && *p == 0xFF) // payload marker
|
||
|
{
|
||
|
payload->p = p+1;
|
||
|
payload->len = end-(p+1);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
payload->p = NULL;
|
||
|
payload->len = 0;
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
int coap_buildOptionHeader(unsigned short optDelta, size_t length, uint8_t *buf, size_t buflen)
|
||
|
{
|
||
|
int n = 0;
|
||
|
uint8_t *p = buf;
|
||
|
uint8_t len, delta = 0;
|
||
|
|
||
|
if (buflen < 5)
|
||
|
return COAP_ERR_BUFFER_TOO_SMALL;
|
||
|
|
||
|
coap_option_nibble(optDelta, &delta);
|
||
|
coap_option_nibble(length, &len);
|
||
|
|
||
|
*p++ = (0xFF & (delta << 4 | len));
|
||
|
n++;
|
||
|
if (delta == 13)
|
||
|
{
|
||
|
*p++ = (optDelta - 13);
|
||
|
n++;
|
||
|
}
|
||
|
else
|
||
|
if (delta == 14)
|
||
|
{
|
||
|
*p++ = ((optDelta-269) >> 8);
|
||
|
*p++ = (0xFF & (optDelta-269));
|
||
|
n+=2;
|
||
|
}
|
||
|
if (len == 13)
|
||
|
{
|
||
|
*p++ = (length - 13);
|
||
|
n++;
|
||
|
}
|
||
|
else
|
||
|
if (len == 14)
|
||
|
{
|
||
|
*p++ = (length >> 8);
|
||
|
*p++ = (0xFF & (length-269));
|
||
|
n+=2;
|
||
|
}
|
||
|
return n;
|
||
|
}
|
||
|
|
||
|
#ifdef COAP_DEBUG
|
||
|
void coap_dumpOptions(coap_option_t *opts, size_t numopt)
|
||
|
{
|
||
|
size_t i;
|
||
|
c_printf(" Options:\n");
|
||
|
for (i=0;i<numopt;i++)
|
||
|
{
|
||
|
c_printf(" 0x%02X [ ", opts[i].num);
|
||
|
coap_dump(opts[i].buf.p, opts[i].buf.len, true);
|
||
|
c_printf(" ]\n");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void coap_dumpPacket(coap_packet_t *pkt)
|
||
|
{
|
||
|
coap_dumpHeader(&pkt->hdr);
|
||
|
coap_dumpOptions(pkt->opts, pkt->numopts);
|
||
|
c_printf("Payload: ");
|
||
|
coap_dump(pkt->payload.p, pkt->payload.len, true);
|
||
|
c_printf("\n");
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
int coap_parse(coap_packet_t *pkt, const uint8_t *buf, size_t buflen)
|
||
|
{
|
||
|
int rc;
|
||
|
|
||
|
// coap_dump(buf, buflen, false);
|
||
|
|
||
|
if (0 != (rc = coap_parseHeader(&pkt->hdr, buf, buflen)))
|
||
|
return rc;
|
||
|
// coap_dumpHeader(&hdr);
|
||
|
if (0 != (rc = coap_parseToken(&pkt->tok, &pkt->hdr, buf, buflen)))
|
||
|
return rc;
|
||
|
pkt->numopts = MAXOPT;
|
||
|
if (0 != (rc = coap_parseOptionsAndPayload(pkt->opts, &(pkt->numopts), &(pkt->payload), &pkt->hdr, buf, buflen)))
|
||
|
return rc;
|
||
|
// coap_dumpOptions(opts, numopt);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
// options are always stored consecutively, so can return a block with same option num
|
||
|
const coap_option_t * coap_findOptions(const coap_packet_t *pkt, uint8_t num, uint8_t *count)
|
||
|
{
|
||
|
// FIXME, options is always sorted, can find faster than this
|
||
|
size_t i;
|
||
|
const coap_option_t *first = NULL;
|
||
|
*count = 0;
|
||
|
for (i=0;i<pkt->numopts;i++)
|
||
|
{
|
||
|
if (pkt->opts[i].num == num)
|
||
|
{
|
||
|
if (NULL == first)
|
||
|
first = &pkt->opts[i];
|
||
|
(*count)++;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if (NULL != first)
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
return first;
|
||
|
}
|
||
|
|
||
|
int coap_buffer_to_string(char *strbuf, size_t strbuflen, const coap_buffer_t *buf)
|
||
|
{
|
||
|
if (buf->len+1 > strbuflen)
|
||
|
return COAP_ERR_BUFFER_TOO_SMALL;
|
||
|
c_memcpy(strbuf, buf->p, buf->len);
|
||
|
strbuf[buf->len] = 0;
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
int coap_build(uint8_t *buf, size_t *buflen, const coap_packet_t *pkt)
|
||
|
{
|
||
|
size_t opts_len = 0, hdr_len = 0, tok_len = 0;
|
||
|
size_t i;
|
||
|
uint8_t *p = buf;
|
||
|
size_t left = *buflen;
|
||
|
uint16_t running_delta = 0;
|
||
|
|
||
|
hdr_len = coap_buildHeader(&(pkt->hdr), buf, *buflen);
|
||
|
p += hdr_len;
|
||
|
left -= hdr_len;
|
||
|
|
||
|
tok_len = coap_buildToken(&(pkt->tok), &(pkt->hdr), buf, *buflen);
|
||
|
p += tok_len;
|
||
|
left -= tok_len;
|
||
|
|
||
|
for (i=0;i<pkt->numopts;i++)
|
||
|
{
|
||
|
uint8_t len, delta = 0;
|
||
|
uint16_t optDelta = 0;
|
||
|
int rc = 0;
|
||
|
|
||
|
if (p-buf > *buflen)
|
||
|
return COAP_ERR_BUFFER_TOO_SMALL;
|
||
|
optDelta = pkt->opts[i].num - running_delta;
|
||
|
|
||
|
rc = coap_buildOptionHeader(optDelta, pkt->opts[i].buf.len, p, left);
|
||
|
p += rc;
|
||
|
left -= rc;
|
||
|
|
||
|
c_memcpy(p, pkt->opts[i].buf.p, pkt->opts[i].buf.len);
|
||
|
p += pkt->opts[i].buf.len;
|
||
|
left -= pkt->opts[i].buf.len;
|
||
|
running_delta = pkt->opts[i].num;
|
||
|
}
|
||
|
|
||
|
opts_len = (p - buf) - 4; // number of bytes used by options
|
||
|
|
||
|
if (pkt->payload.len > 0)
|
||
|
{
|
||
|
if (*buflen < 4 + 1 + pkt->payload.len + opts_len)
|
||
|
return COAP_ERR_BUFFER_TOO_SMALL;
|
||
|
buf[4 + opts_len] = 0xFF; // payload marker
|
||
|
c_memcpy(buf+5 + opts_len, pkt->payload.p, pkt->payload.len);
|
||
|
*buflen = opts_len + 5 + pkt->payload.len;
|
||
|
}
|
||
|
else
|
||
|
*buflen = opts_len + 4;
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
void coap_option_nibble(uint16_t value, uint8_t *nibble)
|
||
|
{
|
||
|
if (value<13)
|
||
|
{
|
||
|
*nibble = (0xFF & value);
|
||
|
}
|
||
|
else
|
||
|
if (((uint32_t)value)<=0xFF+13)
|
||
|
{
|
||
|
*nibble = 13;
|
||
|
} else if (((uint32_t)value) -269 <=0xFFFF)
|
||
|
{
|
||
|
*nibble = 14;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
int coap_make_response(coap_rw_buffer_t *scratch, coap_packet_t *pkt, const uint8_t *content, size_t content_len, uint8_t msgid_hi, uint8_t msgid_lo, const coap_buffer_t* tok, coap_responsecode_t rspcode, coap_content_type_t content_type)
|
||
|
{
|
||
|
pkt->hdr.ver = 0x01;
|
||
|
pkt->hdr.t = COAP_TYPE_ACK;
|
||
|
pkt->hdr.tkl = 0;
|
||
|
pkt->hdr.code = rspcode;
|
||
|
pkt->hdr.id[0] = msgid_hi;
|
||
|
pkt->hdr.id[1] = msgid_lo;
|
||
|
pkt->numopts = 0;
|
||
|
|
||
|
// need token in response
|
||
|
if (tok) {
|
||
|
pkt->hdr.tkl = tok->len;
|
||
|
pkt->tok = *tok;
|
||
|
}
|
||
|
|
||
|
// safe because 1 < MAXOPT
|
||
|
pkt->opts[0].num = COAP_OPTION_CONTENT_FORMAT;
|
||
|
pkt->opts[0].buf.p = scratch->p;
|
||
|
if (scratch->len < 2)
|
||
|
return COAP_ERR_BUFFER_TOO_SMALL;
|
||
|
scratch->p[0] = ((uint16_t)content_type & 0xFF00) >> 8;
|
||
|
scratch->p[1] = ((uint16_t)content_type & 0x00FF);
|
||
|
pkt->opts[0].buf.len = 2;
|
||
|
pkt->payload.p = content;
|
||
|
pkt->payload.len = content_len;
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
|
||
|
unsigned int coap_encode_var_bytes(unsigned char *buf, unsigned int val) {
|
||
|
unsigned int n, i;
|
||
|
|
||
|
for (n = 0, i = val; i && n < sizeof(val); ++n)
|
||
|
i >>= 8;
|
||
|
|
||
|
i = n;
|
||
|
while (i--) {
|
||
|
buf[i] = val & 0xff;
|
||
|
val >>= 8;
|
||
|
}
|
||
|
|
||
|
return n;
|
||
|
}
|
||
|
|
||
|
static uint8_t _token_data[4]={'n','o','d','e'};
|
||
|
coap_buffer_t the_token = { _token_data, 4 };
|
||
|
static unsigned short message_id;
|
||
|
|
||
|
int coap_make_request(coap_rw_buffer_t *scratch, coap_packet_t *pkt, coap_msgtype_t t, coap_method_t m, coap_uri_t *uri, const uint8_t *payload, size_t payload_len)
|
||
|
{
|
||
|
int res;
|
||
|
pkt->hdr.ver = 0x01;
|
||
|
pkt->hdr.t = t;
|
||
|
pkt->hdr.tkl = 0;
|
||
|
pkt->hdr.code = m;
|
||
|
pkt->hdr.id[0] = (message_id >> 8) & 0xFF; //msgid_hi;
|
||
|
pkt->hdr.id[1] = message_id & 0xFF; //msgid_lo;
|
||
|
message_id++;
|
||
|
NODE_DBG("message_id: %d.\n", message_id);
|
||
|
pkt->numopts = 0;
|
||
|
|
||
|
if (the_token.len) {
|
||
|
pkt->hdr.tkl = the_token.len;
|
||
|
pkt->tok = the_token;
|
||
|
}
|
||
|
|
||
|
if (scratch->len < 2) // TBD...
|
||
|
return COAP_ERR_BUFFER_TOO_SMALL;
|
||
|
|
||
|
uint8_t *saved = scratch->p;
|
||
|
|
||
|
/* split arg into Uri-* options */
|
||
|
// const char *addr = uri->host.s;
|
||
|
// if(uri->host.length && (c_strlen(addr) != uri->host.length || c_memcmp(addr, uri->host.s, uri->host.length) != 0)){
|
||
|
if(uri->host.length){
|
||
|
/* add Uri-Host */
|
||
|
// addr is destination address
|
||
|
pkt->opts[pkt->numopts].num = COAP_OPTION_URI_HOST;
|
||
|
pkt->opts[pkt->numopts].buf.p = uri->host.s;
|
||
|
pkt->opts[pkt->numopts].buf.len = uri->host.length;
|
||
|
pkt->numopts++;
|
||
|
}
|
||
|
|
||
|
if (uri->port != COAP_DEFAULT_PORT) {
|
||
|
pkt->opts[pkt->numopts].num = COAP_OPTION_URI_PORT;
|
||
|
res = coap_encode_var_bytes(scratch->p, uri->port);
|
||
|
pkt->opts[pkt->numopts].buf.len = res;
|
||
|
pkt->opts[pkt->numopts].buf.p = scratch->p;
|
||
|
scratch->p += res;
|
||
|
scratch->len -= res;
|
||
|
pkt->numopts++;
|
||
|
}
|
||
|
|
||
|
if (uri->path.length) {
|
||
|
res = coap_split_path(scratch, pkt, uri->path.s, uri->path.length);
|
||
|
}
|
||
|
|
||
|
if (uri->query.length) {
|
||
|
res = coap_split_query(scratch, pkt, uri->query.s, uri->query.length);
|
||
|
}
|
||
|
|
||
|
pkt->payload.p = payload;
|
||
|
pkt->payload.len = payload_len;
|
||
|
scratch->p = saved; // save back the pointer.
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
// FIXME, if this looked in the table at the path before the method then
|
||
|
// it could more easily return 405 errors
|
||
|
int coap_handle_req(coap_rw_buffer_t *scratch, const coap_packet_t *inpkt, coap_packet_t *outpkt)
|
||
|
{
|
||
|
const coap_option_t *opt;
|
||
|
int i;
|
||
|
uint8_t count;
|
||
|
const coap_endpoint_t *ep = endpoints;
|
||
|
|
||
|
while(NULL != ep->handler)
|
||
|
{
|
||
|
if (ep->method != inpkt->hdr.code)
|
||
|
goto next;
|
||
|
if (NULL != (opt = coap_findOptions(inpkt, COAP_OPTION_URI_PATH, &count)))
|
||
|
{
|
||
|
// if (count != ep->path->count)
|
||
|
if ((count != ep->path->count ) && (count != ep->path->count + 1)) // +1 for /f/[function], /v/[variable]
|
||
|
goto next;
|
||
|
for (i=0;i<ep->path->count;i++)
|
||
|
{
|
||
|
if (opt[i].buf.len != c_strlen(ep->path->elems[i]))
|
||
|
goto next;
|
||
|
if (0 != c_memcmp(ep->path->elems[i], opt[i].buf.p, opt[i].buf.len))
|
||
|
goto next;
|
||
|
}
|
||
|
// pre-path match!
|
||
|
if (count==ep->path->count+1 && ep->user_entry == NULL)
|
||
|
goto next;
|
||
|
return ep->handler(ep, scratch, inpkt, outpkt, inpkt->hdr.id[0], inpkt->hdr.id[1]);
|
||
|
}
|
||
|
next:
|
||
|
ep++;
|
||
|
}
|
||
|
|
||
|
coap_make_response(scratch, outpkt, NULL, 0, inpkt->hdr.id[0], inpkt->hdr.id[1], &inpkt->tok, COAP_RSPCODE_NOT_FOUND, COAP_CONTENTTYPE_NONE);
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
void coap_setup(void)
|
||
|
{
|
||
|
message_id = (unsigned short)rand(); // calculate only once
|
||
|
}
|
||
|
|
||
|
inline int
|
||
|
check_token(coap_packet_t *pkt) {
|
||
|
return pkt->tok.len == the_token.len && c_memcmp(pkt->tok.p, the_token.p, the_token.len) == 0;
|
||
|
}
|