/* | |
Copyright (c) 2013, Ashley Mills. | |
All rights reserved. | |
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. | |
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. | |
*/ | |
// version, 2 bits | |
// type, 2 bits | |
// 00 Confirmable | |
// 01 Non-confirmable | |
// 10 Acknowledgement | |
// 11 Reset | |
// token length, 4 bits | |
// length of token in bytes (only 0 to 8 bytes allowed) | |
#include <stdio.h> | |
#include <stdlib.h> | |
#include <stdint.h> | |
#include <string.h> | |
#include "mbtk_coap_pdu.h" | |
//#include "mbtk_log.h" | |
//#ifdef FEATURE_MBTK_ECOAP | |
/// Memory-managed constructor. Buffer for PDU is dynamically sized and allocated by the object. | |
/** | |
* When using this constructor, the CoapPDU class will allocate space for the PDU. | |
* Contrast this with the parameterized constructors, which allow the use of an external buffer. | |
* | |
* Note, the PDU container and space can be reused by issuing a CoapPDU::reset(). If the new PDU exceeds the | |
* space of the previously allocated memory, then further memory will be dynamically allocated. | |
* | |
* Deleting the object will free the Object container and all dynamically allocated memory. | |
* | |
* \note It would have been nice to use something like UDP_CORK or MSG_MORE, to allow separate buffers | |
* for token, options, and payload but these FLAGS aren't implemented for UDP in LwIP so stuck with one buffer for now. | |
* | |
* CoAP version defaults to 1. | |
* | |
* \sa CoapPDU::CoapPDU(uint8_t *pdu, int pduLength), CoapPDU::CoapPDU::(uint8_t *buffer, int bufferLength, int pduLength), | |
* CoapPDU:CoapPDU()~ | |
* | |
*/ | |
//#define printf(fmtstr) LOGD(fmtstr) | |
//#define printf(fmtstr ,arg1) LOGD(fmtstr, arg1) | |
//#define printf(fmtstr, arg1, arg2) LOGD(fmtstr , arg1, arg2) | |
//#define printf(fmtstr, arg1, arg2, arg3) LOGD(fmtstr , arg1, arg2, arg3) | |
/* | |
void* operator new(unsigned int size, void* alloc, ds_appsrv_mem_e_type mem_type) throw() | |
{ | |
// Do nothing | |
return alloc; | |
} | |
void operator delete(void* obj, void* alloc, ds_appsrv_mem_e_type mem_type) throw() | |
{ | |
// Do nothing | |
} | |
*/ | |
CoapPDU::CoapPDU() | |
{ | |
// pdu | |
_pdu = (uint8_t*)calloc(4,sizeof(uint8_t)); | |
_pduLength = 4; | |
_bufferLength = _pduLength; | |
//options | |
_numOptions = 0; | |
_maxAddedOptionNumber = 0; | |
// payload | |
_payloadPointer = NULL; | |
_payloadLength = 0; | |
_constructedFromBuffer = 0; | |
setVersion(1); | |
} | |
/// Construct object from external buffer that may be larger than actual PDU. | |
/** | |
* This differs from CoapPDU::CoapPDU(uint8_t *pdu, int pduLength) in that the buffer may be larger | |
* than the actual CoAP PDU contained int the buffer. This is typically used when a large buffer is reused | |
* multiple times. Note that \b pduLength can be 0. | |
* | |
* If an actual CoAP PDU is passed in the buffer, \b pduLength should match its length. CoapPDU::validate() must | |
* be called to initiate the object before member functions can be used. | |
* | |
* A PDU constructed in this manner must be validated with CoapPDU::validate() before the member variables will be accessible. | |
* | |
* \warning The validation call parses the PDU structure to set some internal parameters. If you do | |
* not validate the PDU, then the behaviour of member access functions will be undefined. | |
* | |
* The buffer can be reused by issuing a CoapPDU::reset() but the class will not change the size of the buffer. If the | |
* newly constructed PDU exceeds the size of the buffer, the function called (for example CoapPDU::addOption) will fail. | |
* | |
* Deleting this object will only delete the Object container and will not delete the PDU buffer. | |
* | |
* \param buffer A buffer which either contains a CoAP PDU or is intended to be used to construct one. | |
* \param bufferLength The length of the buffer | |
* \param pduLength If the buffer contains a CoAP PDU, this specifies the length of the PDU within the buffer. | |
* | |
* \sa CoapPDU::CoapPDU(), CoapPDU::CoapPDU(uint8_t *pdu, int pduLength) | |
*/ | |
CoapPDU::CoapPDU(uint8_t *buffer, int bufferLength, int pduLength) | |
{ | |
printf("CoapPDU(),bufferLength:%d, pduLength;%d\n",bufferLength,pduLength); | |
// sanity | |
if(pduLength<4&&pduLength!=0) | |
{ | |
printf("PDU cannot have a length less than 4"); | |
} | |
// pdu | |
_pdu = buffer; | |
_bufferLength = bufferLength; | |
if(pduLength==0) | |
{ | |
// this is actually a fresh pdu, header always exists | |
_pduLength = 4; | |
// make sure header is zeroed | |
_pdu[0] = 0x00; | |
_pdu[1] = 0x00; | |
_pdu[2] = 0x00; | |
_pdu[3] = 0x00; | |
setVersion(1); | |
} | |
else | |
{ | |
_pduLength = pduLength; | |
} | |
_constructedFromBuffer = 1; | |
// options | |
_numOptions = 0; | |
_maxAddedOptionNumber = 0; | |
// payload | |
_payloadPointer = NULL; | |
_payloadLength = 0; | |
} | |
/// Reset CoapPDU container so it can be reused to build a new PDU. | |
/** | |
* This resets the CoapPDU container, setting the pdu length, option count, etc back to zero. The | |
* PDU can then be populated as if it were newly constructed. | |
* | |
* Note that the space available will depend on how the CoapPDU was originally constructed: | |
* -# CoapPDU::CoapPDU() | |
* | |
* Available space initially be \b _pduLength. But further space will be allocated as needed on demand, | |
* limited only by the OS/environment. | |
* | |
* -# CoapPDU::CoapPDU(uint8_t *pdu, int pduLength) | |
* | |
* Space is limited by the variable \b pduLength. The PDU cannot exceed \b pduLength bytes. | |
* | |
* -# CoapPDU::CoapPDU(uint8_t *buffer, int bufferLength, int pduLength) | |
* | |
* Space is limited by the variable \b bufferLength. The PDU cannot exceed \b bufferLength bytes. | |
* | |
* \return 0 on success, 1 on failure. | |
*/ | |
int CoapPDU::reset() | |
{ | |
// pdu | |
memset(_pdu,0x00,_bufferLength); | |
// packet always has at least a header | |
_pduLength = 4; | |
// options | |
_numOptions = 0; | |
_maxAddedOptionNumber = 0; | |
// payload | |
_payloadPointer = NULL; | |
_payloadLength = 0; | |
content.clear(); | |
return 0; | |
} | |
/// Validates a PDU constructed using an external buffer. | |
/** | |
* When a CoapPDU is constructed using an external buffer, the programmer must call this function to | |
* check that the received PDU is a valid CoAP PDU. | |
* | |
* \warning The validation call parses the PDU structure to set some internal parameters. If you do | |
* not validate the PDU, then the behaviour of member access functions will be undefined. | |
* | |
* \return 1 if the PDU validates correctly, 0 if not. XXX maybe add some error codes | |
*/ | |
int CoapPDU::validate() | |
{ | |
if(_pduLength<4) | |
{ | |
printf("PDU has to be a minimum of 4 bytes. This: %d bytes",_pduLength); | |
return 0; | |
} | |
// check header | |
// 0 1 2 3 | |
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 | |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | |
// |Ver| T | TKL | Code | Message ID | | |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | |
// | Token (if any, TKL bytes) ... | |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | |
// | Options (if any) ... | |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | |
// |1 1 1 1 1 1 1 1| Payload (if any) ... | |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | |
// version must be 1 | |
int version = getVersion(); | |
if (version != 1) | |
{ | |
printf("Invalid version: %d", version); | |
return 0; | |
} | |
printf("Version: %d", version); | |
printf("Type: %d", getType()); | |
// token length must be between 0 and 8 | |
int tokenLength = getTokenLength(); | |
if(tokenLength<0||tokenLength>8) | |
{ | |
printf("Invalid token length: %d",tokenLength); | |
return 0; | |
} | |
printf("Token length: %d",tokenLength); | |
// check total length | |
if((COAP_HDR_SIZE+tokenLength)>_pduLength) | |
{ | |
printf("Token length would make pdu longer than actual length."); | |
return 0; | |
} | |
// check that code is valid | |
int code = getCode(); | |
if(code<COAP_EMPTY || | |
(code>COAP_LASTMETHOD&&code<COAP_CREATED) || | |
(code>COAP_CONTENT&&code<COAP_CONTINUE) || | |
(code>COAP_CONTINUE&&code<COAP_BAD_REQUEST) || | |
(code>COAP_NOT_ACCEPTABLE&&code<COAP_PRECONDITION_FAILED) || | |
(code==0x8E) || | |
(code>COAP_UNSUPPORTED_CONTENT_FORMAT&&code<COAP_INTERNAL_SERVER_ERROR) || | |
(code>COAP_PROXYING_NOT_SUPPORTED) ) | |
{ | |
printf("Invalid CoAP code: %d",code); | |
return 0; | |
} | |
printf("CoAP code: %d",code); | |
// token can be anything so nothing to check | |
// check that options all make sense | |
uint16_t optionDelta =0, optionNumber = 0, optionValueLength = 0; | |
int totalLength = 0; | |
// first option occurs after token | |
int optionPos = COAP_HDR_SIZE + getTokenLength(); | |
// may be 0 options | |
if(optionPos==_pduLength) | |
{ | |
printf("No options. No payload."); | |
_numOptions = 0; | |
_payloadLength = 0; | |
return 1; | |
} | |
int bytesRemaining = _pduLength-optionPos; | |
int numOptions = 0; | |
uint8_t upperNibble = 0x00, lowerNibble = 0x00; | |
// walk over options and record information | |
while(1) | |
{ | |
// check for payload marker | |
if(bytesRemaining>0) | |
{ | |
uint8_t optionHeader = _pdu[optionPos]; | |
if(optionHeader==0xFF) | |
{ | |
// payload | |
if(bytesRemaining>1) | |
{ | |
_payloadPointer = &_pdu[optionPos+1]; | |
_payloadLength = (bytesRemaining-1); | |
_numOptions = numOptions; | |
printf("Payload found, length: %d",_payloadLength); | |
return 1; | |
} | |
// payload marker but no payload | |
_payloadPointer = NULL; | |
_payloadLength = 0; | |
printf("Payload marker but no payload."); | |
return 0; | |
} | |
// check that option delta and option length are valid values | |
upperNibble = (optionHeader & 0xF0) >> 4; | |
lowerNibble = (optionHeader & 0x0F); | |
if(upperNibble==0x0F||lowerNibble==0x0F) | |
{ | |
printf("Expected option header or payload marker, got: 0x%x%x",upperNibble,lowerNibble); | |
return 0; | |
} | |
printf("Option header byte appears sane: 0x%x%x",upperNibble,lowerNibble); | |
} | |
else | |
{ | |
printf("No more data. No payload."); | |
_payloadPointer = NULL; | |
_payloadLength = 0; | |
_numOptions = numOptions; | |
return 1; | |
} | |
// skip over option header byte | |
bytesRemaining--; | |
// check that there is enough space for the extended delta and length bytes (if any) | |
int headerBytesNeeded = computeExtraBytes(upperNibble); | |
printf("%d extra bytes needed for extended delta",headerBytesNeeded); | |
if(headerBytesNeeded>bytesRemaining) | |
{ | |
printf("Not enough space for extended option delta, needed %d, have %d.",headerBytesNeeded,bytesRemaining); | |
return 0; | |
} | |
headerBytesNeeded += computeExtraBytes(lowerNibble); | |
if(headerBytesNeeded>bytesRemaining) | |
{ | |
printf("Not enough space for extended option length, needed %d, have %d.", | |
(headerBytesNeeded-computeExtraBytes(upperNibble)),bytesRemaining); | |
return 0; | |
} | |
printf("Enough space for extended delta and length: %d, continuing.",headerBytesNeeded); | |
// extract option details | |
optionDelta = getOptionDelta(&_pdu[optionPos]); | |
optionNumber += optionDelta; | |
optionValueLength = getOptionValueLength(&_pdu[optionPos]); | |
printf("Got option: %d with length %d",optionNumber,optionValueLength); | |
// compute total length | |
totalLength = 1; // mandatory header | |
totalLength += computeExtraBytes(optionDelta); | |
totalLength += computeExtraBytes(optionValueLength); | |
totalLength += optionValueLength; | |
// check there is enough space | |
if(optionPos+totalLength>_pduLength) | |
{ | |
printf("Not enough space for option payload, needed %d, have %d.",(totalLength-headerBytesNeeded-1),_pduLength-optionPos); | |
return 0; | |
} | |
printf("Enough space for option payload: %d %d",optionValueLength,(totalLength-headerBytesNeeded-1)); | |
// recompute bytesRemaining | |
bytesRemaining -= totalLength; | |
bytesRemaining++; // correct for previous -- | |
// move to next option | |
optionPos += totalLength; | |
// inc number of options XXX | |
numOptions++; | |
} | |
return 1; | |
} | |
/// Destructor. Does not free buffer if constructor passed an external buffer. | |
/** | |
* The destructor acts differently, depending on how the object was initially constructed (from buffer or not): | |
* | |
* -# CoapPDU::CoapPDU() | |
* | |
* Complete object is destroyed. | |
* | |
* -# CoapPDU::CoapPDU(uint8_t *pdu, int pduLength) | |
* | |
* Only object container is destroyed. \b pdu is left intact. | |
* | |
* -# CoapPDU::CoapPDU(uint8_t *buffer, int bufferLength, int pduLength) | |
* | |
* Only object container is destroyed. \b pdu is left intact. | |
* | |
*/ | |
CoapPDU::~CoapPDU() | |
{ | |
if(!_constructedFromBuffer) | |
{ | |
free(_pdu); | |
} | |
} | |
/// Returns a pointer to the internal buffer. | |
uint8_t* CoapPDU::getPDUPointer() | |
{ | |
return _pdu; | |
} | |
/// Set the PDU length to the length specified. | |
/** | |
* This is used when re-using a PDU container before calling CoapPDU::validate() as it | |
* is not possible to deduce the length of a PDU since the payload has no length marker. | |
* \param len The length of the PDU | |
*/ | |
void CoapPDU::setPDULength(int len) | |
{ | |
_pduLength = len; | |
} | |
/// Shorthand function for setting a resource URI. | |
/** | |
* Calls CoapPDU::setURI(uri,strlen(uri). | |
*/ | |
int CoapPDU::setURI(char *uri) | |
{ | |
return setURI(uri,strlen(uri)); | |
} | |
/// Shorthand function for setting a resource URI. | |
/** | |
* This will parse the supplied \b uri and construct enough URI_PATH and URI_QUERY options to encode it. | |
* The options are added to the PDU. | |
* | |
* At present only simple URI formatting is handled, only '/','?', and '&' separators, and no port or protocol specificaiton. | |
* | |
* The function will split on '/' and create URI_PATH elements until it either reaches the end of the string | |
* in which case it will stop or if it reaches '?' it will start splitting on '&' and create URI_QUERY elements | |
* until it reaches the end of the string. | |
* | |
* Here is an example: | |
* | |
* /a/b/c/d?x=1&y=2&z=3 | |
* | |
* Will be broken into four URI_PATH elements "a", "b", "c", "d", and three URI_QUERY elements "x=1", "y=2", "z=3" | |
* | |
* TODO: Add protocol extraction, port extraction, and some malformity checking. | |
* | |
* \param uri The uri to parse. | |
* \param urilen The length of the uri to parse. | |
* | |
* \return 0 on success, 1 on failure. | |
*/ | |
int CoapPDU::setURI(char *uri, int urilen) | |
{ | |
// only '/', '?', '&' and ascii chars allowed | |
// sanitation | |
if(urilen<=0||uri==NULL) | |
{ | |
printf("Null or zero-length uri passed."); | |
return 1; | |
} | |
// single character URI path (including '/' case) | |
if(urilen==1) | |
{ | |
addOption(COAP_OPTION_URI_PATH,1,(uint8_t*)uri); | |
return 0; | |
} | |
// TODO, queries | |
// extract ? to mark where to stop processing path components | |
// and then process the query params | |
// local vars | |
char *startP=uri,*endP=NULL; | |
int oLen = 0; | |
char splitChar = '/'; | |
int queryStageTriggered = 0; | |
uint16_t optionType = COAP_OPTION_URI_PATH; | |
while(1) | |
{ | |
// stop at end of string or query | |
if(*startP==0x00||*(startP+1)==0x00) | |
{ | |
break; | |
} | |
// ignore leading slash | |
if(*startP==splitChar) | |
{ | |
printf("Skipping leading slash"); | |
startP++; | |
} | |
// find next split point | |
endP = strchr(startP,splitChar); | |
// might not be another slash | |
if(endP==NULL) | |
{ | |
printf("Ending out of slash"); | |
// check if there is a ? | |
endP = strchr(startP,'?'); | |
// done if no queries | |
if(endP==NULL) | |
{ | |
endP = uri+urilen; | |
} | |
else | |
{ | |
queryStageTriggered = 1; | |
} | |
} | |
// get length of segment | |
oLen = endP-startP; | |
#ifdef DEBUG | |
char *b = (char*)malloc(oLen+1); | |
memcpy(b,startP,oLen); | |
b[oLen] = 0x00; | |
printf("Adding URI_PATH %s",b); | |
free(b); | |
#endif | |
// add option | |
if(addOption(optionType,oLen,(uint8_t*)startP)!=0) | |
{ | |
printf("Error adding option"); | |
return 1; | |
} | |
startP = endP; | |
if(queryStageTriggered) | |
{ | |
splitChar = '&'; | |
optionType = COAP_OPTION_URI_QUERY; | |
startP++; | |
queryStageTriggered = false; | |
} | |
} | |
return 0; | |
} | |
/// Shorthand for adding a URI QUERY to the option list. | |
/** | |
* Adds a new option to the CoAP PDU that encodes a URI_QUERY. | |
* | |
* \param query The uri query to encode. | |
* \return 0 on success, 1 on failure. | |
*/ | |
int CoapPDU::addURIQuery(char *query) | |
{ | |
return addOption(COAP_OPTION_URI_QUERY,strlen(query),(uint8_t*)query); | |
} | |
/// Concatenates any URI_PATH elements and URI_QUERY elements into a single string. | |
/** | |
* Parses the PDU options and extracts all URI_PATH and URI_QUERY elements, | |
* concatenating them into a single string with slash and amphersand separators accordingly. | |
* | |
* The produced string will be NULL terminated. | |
* | |
* \param dst Buffer into which to copy the concatenated path elements. | |
* \param dstlen Length of buffer. | |
* \param outLen Pointer to integer, into which URI length will be placed. | |
* | |
* \return 0 on success, 1 on failure. \b outLen will contain the length of the concatenated elements. | |
*/ | |
int CoapPDU::getURI(char *dst, int dstlen, int *outLen) | |
{ | |
if(outLen==NULL) | |
{ | |
printf("Output length pointer is NULL"); | |
return 1; | |
} | |
if(dst==NULL) | |
{ | |
printf("NULL destination buffer"); | |
*outLen = 0; | |
return 1; | |
} | |
// check destination space | |
if(dstlen<=0) | |
{ | |
*dst = 0x00; | |
*outLen = 0; | |
printf("Destination buffer too small (0)!"); | |
return 1; | |
} | |
// check option count | |
if(_numOptions==0) | |
{ | |
*dst = 0x00; | |
*outLen = 0; | |
return 0; | |
} | |
// get options | |
CoapPDU::CoapOption *options = getOptions(); | |
if(options==NULL) | |
{ | |
*dst = 0x00; | |
*outLen = 0; | |
return 0; | |
} | |
// iterate over options to construct URI | |
CoapOption *o = NULL; | |
int bytesLeft = dstlen-1; // space for 0x00 | |
int oLen = 0; | |
// add slash at beggining | |
if(bytesLeft>=1) | |
{ | |
*dst = '/'; | |
dst++; | |
bytesLeft--; | |
} | |
else | |
{ | |
printf("No space for initial slash needed 1, got %d",bytesLeft); | |
free(options); | |
return 1; | |
} | |
char separator = '/'; | |
int firstQuery = 1; | |
for(int i=0; i<_numOptions; i++) | |
{ | |
o = &options[i]; | |
oLen = o->optionValueLength; | |
if(o->optionNumber==COAP_OPTION_URI_PATH||o->optionNumber==COAP_OPTION_URI_QUERY) | |
{ | |
// if the option is a query, change the separator to & | |
if(o->optionNumber==COAP_OPTION_URI_QUERY) | |
{ | |
if(firstQuery) | |
{ | |
// change previous '/' to a '?' | |
*(dst-1) = '?'; | |
firstQuery = 0; | |
} | |
separator = '&'; | |
} | |
// check space | |
if(oLen>bytesLeft) | |
{ | |
printf("Destination buffer too small, needed %d, got %d",oLen,bytesLeft); | |
free(options); | |
return 1; | |
} | |
// case where single '/' exists | |
if(oLen==1&&o->optionValuePointer[0]=='/') | |
{ | |
*dst = 0x00; | |
*outLen = 1; | |
free(options); | |
return 0; | |
} | |
// copy URI path or query component | |
memcpy(dst,o->optionValuePointer,oLen); | |
// adjust counters | |
dst += oLen; | |
bytesLeft -= oLen; | |
// add separator following (don't know at this point if another option is coming) | |
if(bytesLeft>=1) | |
{ | |
*dst = separator; | |
dst++; | |
bytesLeft--; | |
} | |
else | |
{ | |
printf("Ran out of space after processing option"); | |
free(options); | |
return 1; | |
} | |
} | |
} | |
// remove terminating separator | |
dst--; | |
bytesLeft++; | |
// add null terminating byte (always space since reserved) | |
*dst = 0x00; | |
*outLen = (dstlen-1)-bytesLeft; | |
free(options); | |
return 0; | |
} | |
/// Sets the CoAP version. | |
/** | |
* \param version CoAP version between 0 and 3. | |
* \return 0 on success, 1 on failure. | |
*/ | |
int CoapPDU::setVersion(uint8_t version) | |
{ | |
if(version>3) | |
{ | |
return 0; | |
} | |
_pdu[0] &= 0x3F; | |
_pdu[0] |= (version << 6); | |
return 1; | |
} | |
/** | |
* Gets the CoAP Version. | |
* @return The CoAP version between 0 and 3. | |
*/ | |
uint8_t CoapPDU::getVersion() | |
{ | |
return (_pdu[0]&0xC0)>>6; | |
} | |
/** | |
* Sets the type of this CoAP PDU. | |
* \param mt The type, one of: | |
* - COAP_CONFIRMABLE | |
* - COAP_NON_CONFIRMABLE | |
* - COAP_ACKNOWLEDGEMENT | |
* - COAP_RESET. | |
*/ | |
void CoapPDU::setType(int mt) | |
{ | |
_pdu[0] &= 0xCF; | |
_pdu[0] |= mt; | |
} | |
/// Returns the type of the PDU. | |
int CoapPDU::getType() | |
{ | |
return (int)(_pdu[0]&0x30); | |
} | |
/// Set the token length. | |
/** | |
* \param tokenLength The length of the token in bytes, between 0 and 8. | |
* \return 0 on success, 1 on failure. | |
*/ | |
int CoapPDU::setTokenLength(uint8_t tokenLength) | |
{ | |
if(tokenLength>8) | |
return 1; | |
_pdu[0] &= 0xF0; | |
_pdu[0] |= tokenLength; | |
return 0; | |
} | |
/// Returns the token length. | |
int CoapPDU::getTokenLength() | |
{ | |
return _pdu[0] & 0x0F; | |
} | |
/// Returns a pointer to the PDU token. | |
uint8_t* CoapPDU::getTokenPointer() | |
{ | |
if(getTokenLength()==0) | |
{ | |
return NULL; | |
} | |
return &_pdu[4]; | |
} | |
/// Set the PDU token to the supplied byte sequence. | |
/** | |
* This sets the PDU token to \b token and sets the token length to \b tokenLength. | |
* \param token A sequence of bytes representing the token. | |
* \param tokenLength The length of the byte sequence. | |
* \return 0 on success, 1 on failure. | |
*/ | |
int CoapPDU::setToken(uint8_t *token, uint8_t tokenLength) | |
{ | |
printf("Setting token"); | |
if(token==NULL) | |
{ | |
printf("NULL pointer passed as token reference"); | |
return 1; | |
} | |
if(tokenLength==0) | |
{ | |
printf("Token has zero length"); | |
return 1; | |
} | |
// if tokenLength has not changed, just copy the new value | |
uint8_t oldTokenLength = getTokenLength(); | |
if(tokenLength==oldTokenLength) | |
{ | |
memcpy((void*)&_pdu[4],token,tokenLength); | |
return 0; | |
} | |
// otherwise compute new length of PDU | |
uint8_t oldPDULength = _pduLength; | |
_pduLength -= oldTokenLength; | |
_pduLength += tokenLength; | |
// now, have to shift old memory around, but shift direction depends | |
// whether pdu is now bigger or smaller | |
if(_pduLength>oldPDULength) | |
{ | |
// new PDU is bigger, need to allocate space for new PDU | |
if(!_constructedFromBuffer) | |
{ | |
uint8_t *newMemory = (uint8_t*)realloc(_pdu,_pduLength); | |
if(newMemory==NULL) | |
{ | |
// malloc failed | |
printf("Failed to allocate memory for token"); | |
_pduLength = oldPDULength; | |
return 1; | |
} | |
_pdu = newMemory; | |
_bufferLength = _pduLength; | |
} | |
else | |
{ | |
// constructed from buffer, check space | |
if(_pduLength>_bufferLength) | |
{ | |
printf("Buffer too small to contain token, needed %d, got %d.",_pduLength-oldPDULength,_bufferLength-oldPDULength); | |
_pduLength = oldPDULength; | |
return 1; | |
} | |
} | |
// and then shift everything after token up to end of new PDU | |
// memory overlaps so do this manually so to avoid additional mallocs | |
int shiftOffset = _pduLength-oldPDULength; | |
int shiftAmount = _pduLength-tokenLength-COAP_HDR_SIZE; // everything after token | |
shiftPDUUp(shiftOffset,shiftAmount); | |
// now copy the token into the new space and set official token length | |
memcpy((void*)&_pdu[4],token,tokenLength); | |
setTokenLength(tokenLength); | |
// and return success | |
return 0; | |
} | |
// new PDU is smaller, copy the new token value over the old one | |
memcpy((void*)&_pdu[4],token,tokenLength); | |
// and shift everything after the new token down | |
int startLocation = COAP_HDR_SIZE+tokenLength; | |
int shiftOffset = oldPDULength-_pduLength; | |
int shiftAmount = oldPDULength-oldTokenLength-COAP_HDR_SIZE; | |
shiftPDUDown(startLocation,shiftOffset,shiftAmount); | |
// then reduce size of buffer | |
if(!_constructedFromBuffer) | |
{ | |
uint8_t *newMemory = (uint8_t*)realloc(_pdu,_pduLength); | |
if(newMemory==NULL) | |
{ | |
// malloc failed, PDU in inconsistent state | |
printf("Failed to shrink PDU for new token. PDU probably broken"); | |
return 1; | |
} | |
_pdu = newMemory; | |
_bufferLength = _pduLength; | |
} | |
// and officially set the new tokenLength | |
setTokenLength(tokenLength); | |
return 0; | |
} | |
/// Sets the CoAP response code | |
void CoapPDU::setCode(int code) | |
{ | |
_pdu[1] = code; | |
// there is a limited set of response codes | |
} | |
/// Gets the CoAP response code | |
int CoapPDU::getCode() | |
{ | |
return (int)_pdu[1]; | |
} | |
/// Converts a http status code as an integer, to a CoAP code. | |
/** | |
* \param httpStatus the HTTP status code as an integer (e.g 200) | |
* \return The correct corresponding int on success, | |
* CoapPDU::COAP_UNDEFINED_CODE on failure. | |
*/ | |
int CoapPDU::httpStatusToCode(int httpStatus) | |
{ | |
switch(httpStatus) | |
{ | |
case 1: | |
return CoapPDU::COAP_GET; | |
case 2: | |
return CoapPDU::COAP_POST; | |
case 3: | |
return CoapPDU::COAP_PUT; | |
case 4: | |
return CoapPDU::COAP_DELETE; | |
case 201: | |
return CoapPDU::COAP_CREATED; | |
case 202: | |
return CoapPDU::COAP_DELETED; | |
case 203: | |
return CoapPDU::COAP_VALID; | |
case 204: | |
return CoapPDU::COAP_CHANGED; | |
case 205: | |
return CoapPDU::COAP_CONTENT; | |
case 400: | |
return CoapPDU::COAP_BAD_REQUEST; | |
case 401: | |
return CoapPDU::COAP_UNAUTHORIZED; | |
case 402: | |
return CoapPDU::COAP_BAD_OPTION; | |
case 403: | |
return CoapPDU::COAP_FORBIDDEN; | |
case 404: | |
return CoapPDU::COAP_NOT_FOUND; | |
case 405: | |
return CoapPDU::COAP_METHOD_NOT_ALLOWED; | |
case 406: | |
return CoapPDU::COAP_NOT_ACCEPTABLE; | |
case 412: | |
return CoapPDU::COAP_PRECONDITION_FAILED; | |
case 413: | |
return CoapPDU::COAP_REQUEST_ENTITY_TOO_LARGE; | |
case 415: | |
return CoapPDU::COAP_UNSUPPORTED_CONTENT_FORMAT; | |
case 500: | |
return CoapPDU::COAP_INTERNAL_SERVER_ERROR; | |
case 501: | |
return CoapPDU::COAP_NOT_IMPLEMENTED; | |
case 502: | |
return CoapPDU::COAP_BAD_GATEWAY; | |
case 503: | |
return CoapPDU::COAP_SERVICE_UNAVAILABLE; | |
case 504: | |
return CoapPDU::COAP_GATEWAY_TIMEOUT; | |
case 505: | |
return CoapPDU::COAP_PROXYING_NOT_SUPPORTED; | |
default: | |
return CoapPDU::COAP_UNDEFINED_CODE; | |
} | |
} | |
/// Set messageID to the supplied value. | |
/** | |
* \param messageID A 16bit message id. | |
* \return 0 on success, 1 on failure. | |
*/ | |
int CoapPDU::setMessageID(uint16_t messageID) | |
{ | |
// message ID is stored in network byte order | |
uint8_t *to = &_pdu[2]; | |
endian_store16(to, messageID); | |
return 0; | |
} | |
/// Returns the 16 bit message ID of the PDU. | |
uint16_t CoapPDU::getMessageID() | |
{ | |
// mesasge ID is stored in network byteorder | |
uint8_t *from = &_pdu[2]; | |
uint16_t messageID = endian_load16(uint16_t, from); | |
return messageID; | |
} | |
/// Returns the length of the PDU. | |
int CoapPDU::getPDULength() | |
{ | |
return _pduLength; | |
} | |
/// Return the number of options that the PDU has. | |
int CoapPDU::getNumOptions() | |
{ | |
return _numOptions; | |
} | |
/** | |
* This returns the options as a sequence of structs. | |
*/ | |
CoapPDU::CoapOption* CoapPDU::getOptions() | |
{ | |
printf("getOptions() called, %d options.",_numOptions); | |
uint16_t optionDelta =0, optionNumber = 0, optionValueLength = 0; | |
int totalLength = 0; | |
if(_numOptions==0) | |
{ | |
return NULL; | |
} | |
// malloc space for options | |
CoapOption *options = (CoapOption*)malloc(_numOptions*sizeof(CoapOption)); | |
if(options==NULL) | |
{ | |
printf("Failed to allocate memory for options."); | |
return NULL; | |
} | |
// first option occurs after token | |
int optionPos = COAP_HDR_SIZE + getTokenLength(); | |
// walk over options and record information | |
for(int i=0; i<_numOptions; i++) | |
{ | |
// extract option details | |
optionDelta = getOptionDelta(&_pdu[optionPos]); | |
optionNumber += optionDelta; | |
optionValueLength = getOptionValueLength(&_pdu[optionPos]); | |
// compute total length | |
totalLength = 1; // mandatory header | |
totalLength += computeExtraBytes(optionDelta); | |
totalLength += computeExtraBytes(optionValueLength); | |
totalLength += optionValueLength; | |
// record option details | |
options[i].optionNumber = optionNumber; | |
options[i].optionDelta = optionDelta; | |
options[i].optionValueLength = optionValueLength; | |
options[i].totalLength = totalLength; | |
options[i].optionPointer = &_pdu[optionPos]; | |
options[i].optionValuePointer = &_pdu[optionPos+totalLength-optionValueLength]; | |
// move to next option | |
optionPos += totalLength; | |
} | |
return options; | |
} | |
/// Add an option to the PDU. | |
/** | |
* Unlike other implementations, options can be added in any order, and in-memory manipulation will be | |
* performed to ensure the correct ordering of options (they use a delta encoding of option numbers). | |
* Re-ordering memory like this incurs a small performance cost, so if you care about this, then you | |
* might want to add options in ascending order of option number. | |
* \param optionNumber The number of the option, see the enum CoapPDU::Option for shorthand notations. | |
* \param optionLength The length of the option payload in bytes. | |
* \param optionValue A pointer to the byte sequence that is the option payload (bytes will be copied). | |
* \return 0 on success, 1 on failure. | |
*/ | |
int CoapPDU::addOption(uint16_t insertedOptionNumber, uint16_t optionValueLength, uint8_t *optionValue) | |
{ | |
// this inserts the option in memory, and re-computes the deltas accordingly | |
// prevOption <-- insertionPosition | |
// nextOption | |
// find insertion location and previous option number | |
uint16_t prevOptionNumber = 0; // option number of option before insertion point | |
int insertionPosition = findInsertionPosition(insertedOptionNumber,&prevOptionNumber); | |
printf("inserting option at position %d, after option with number: %hu",insertionPosition,prevOptionNumber); | |
// compute option delta length | |
uint16_t optionDelta = insertedOptionNumber-prevOptionNumber; | |
uint8_t extraDeltaBytes = computeExtraBytes(optionDelta); | |
// compute option length length | |
uint16_t extraLengthBytes = computeExtraBytes(optionValueLength); | |
// compute total length of option | |
uint16_t optionLength = COAP_OPTION_HDR_BYTE + extraDeltaBytes + extraLengthBytes + optionValueLength; | |
// if this is at the end of the PDU, job is done, just malloc and insert | |
if(insertionPosition==_pduLength) | |
{ | |
printf("Inserting at end of PDU"); | |
// optionNumber must be biggest added | |
_maxAddedOptionNumber = insertedOptionNumber; | |
// set new PDU length and allocate space for extra option | |
int oldPDULength = _pduLength; | |
_pduLength += optionLength; | |
if(!_constructedFromBuffer) | |
{ | |
uint8_t *newMemory = (uint8_t*)realloc(_pdu,_pduLength); | |
if(newMemory==NULL) | |
{ | |
printf("Failed to allocate memory for option."); | |
_pduLength = oldPDULength; | |
// malloc failed | |
return 1; | |
} | |
_pdu = newMemory; | |
_bufferLength = _pduLength; | |
} | |
else | |
{ | |
// constructed from buffer, check space | |
if(_pduLength>_bufferLength) | |
{ | |
printf("Buffer too small for new option: needed %d, got %d.",_pduLength-oldPDULength,_bufferLength-oldPDULength); | |
_pduLength = oldPDULength; | |
return 1; | |
} | |
} | |
// insert option at position | |
insertOption(insertionPosition,optionDelta,optionValueLength,optionValue); | |
_numOptions++; | |
return 0; | |
} | |
// XXX could do 0xFF pdu payload case for changing of dynamically allocated application space SDUs < yeah, if you're insane | |
// the next option might (probably) needs it's delta changing | |
// I want to take this into account when allocating space for the new | |
// option, to avoid having to do two mallocs, first get info about this option | |
int nextOptionDelta = getOptionDelta(&_pdu[insertionPosition]); | |
int nextOptionNumber = prevOptionNumber + nextOptionDelta; | |
int nextOptionDeltaBytes = computeExtraBytes(nextOptionDelta); | |
printf("nextOptionDeltaBytes: %d",nextOptionDeltaBytes); | |
// recompute option delta, relative to inserted option | |
int newNextOptionDelta = nextOptionNumber-insertedOptionNumber; | |
int newNextOptionDeltaBytes = computeExtraBytes(newNextOptionDelta); | |
printf("newNextOptionDeltaBytes: %d",newNextOptionDeltaBytes); | |
// determine adjustment | |
int optionDeltaAdjustment = newNextOptionDeltaBytes-nextOptionDeltaBytes; | |
// create space for new option, including adjustment space for option delta | |
printf("Creating space"); | |
int mallocLength = optionLength+optionDeltaAdjustment; | |
int oldPDULength = _pduLength; | |
_pduLength += mallocLength; | |
if(!_constructedFromBuffer) | |
{ | |
uint8_t *newMemory = (uint8_t*)realloc(_pdu,_pduLength); | |
if(newMemory==NULL) | |
{ | |
printf("Failed to allocate memory for option"); | |
_pduLength = oldPDULength; | |
return 1; | |
} | |
_pdu = newMemory; | |
_bufferLength = _pduLength; | |
} | |
else | |
{ | |
// constructed from buffer, check space | |
if(_pduLength>_bufferLength) | |
{ | |
printf("Buffer too small to contain option, needed %d, got %d.",_pduLength-oldPDULength,_bufferLength-oldPDULength); | |
_pduLength = oldPDULength; | |
return 1; | |
} | |
} | |
// move remainder of PDU data up to create hole for new option | |
printf("Shifting PDU."); | |
shiftPDUUp(mallocLength,_pduLength-(insertionPosition+mallocLength)); | |
// adjust option delta bytes of following option | |
// move the option header to the correct position | |
int nextHeaderPos = insertionPosition+mallocLength; | |
_pdu[nextHeaderPos-optionDeltaAdjustment] = _pdu[nextHeaderPos]; | |
nextHeaderPos -= optionDeltaAdjustment; | |
// and set the new value | |
setOptionDelta(nextHeaderPos, newNextOptionDelta); | |
// new option shorter | |
// p p n n x x x x x | |
// p p n n x x x x x - | |
// p p - n n x x x x x | |
// p p - - n x x x x x | |
// p p o o n x x x x x | |
// new option longer | |
// p p n n x x x x x | |
// p p n n x x x x x - - - | |
// p p - - - n n x x x x x | |
// p p - - n n n x x x x x | |
// p p o o n n n x x x x x | |
// note, it can only ever be shorter or the same since if an option was inserted the delta got smaller | |
// but I'll leave that little comment in, just to show that it would work even if the delta got bigger | |
// now insert the new option into the gap | |
printf("Inserting new option..."); | |
insertOption(insertionPosition,optionDelta,optionValueLength,optionValue); | |
printf("done\r\n"); | |
// done, mark it with B! | |
_numOptions++; | |
return 0; | |
} | |
/// Allocate space for a payload. | |
/** | |
* For dynamically constructed PDUs, this will allocate space for a payload in the object | |
* and return a pointer to it. If the PDU was constructed from a buffer, this doesn't | |
* malloc anything, it just changes the _pduLength and returns the payload pointer. | |
* | |
* \note The pointer returned points into the PDU buffer. | |
* \param len The length of the payload buffer to allocate. | |
* \return Either a pointer to the payload buffer, or NULL if there wasn't enough space / allocation failed. | |
*/ | |
uint8_t* CoapPDU::mallocPayload(int len) | |
{ | |
printf("Entering mallocPayload"); | |
// sanity checks | |
if(len==0) | |
{ | |
printf("Cannot allocate a zero length payload"); | |
return NULL; | |
} | |
// further sanity | |
if(len==_payloadLength) | |
{ | |
printf("Space for payload of specified length already exists"); | |
if(_payloadPointer==NULL) | |
{ | |
printf("Garbage PDU. Payload length is %d, but existing _payloadPointer NULL",_payloadLength); | |
return NULL; | |
} | |
return _payloadPointer; | |
} | |
printf("_bufferLength: %d, _pduLength: %d, _payloadLength: %d",_bufferLength,_pduLength,_payloadLength); | |
// might be making payload bigger (including bigger than 0) or smaller | |
int markerSpace = 1; | |
int payloadSpace = len; | |
// is this a resizing? | |
if(_payloadLength!=0) | |
{ | |
// marker already exists | |
markerSpace = 0; | |
// compute new payload length (can be negative if shrinking payload) | |
payloadSpace = len-_payloadLength; | |
} | |
// make space for payload (and payload marker if necessary) | |
int newLen = _pduLength+payloadSpace+markerSpace; | |
if(!_constructedFromBuffer) | |
{ | |
uint8_t* newPDU = (uint8_t*)realloc(_pdu,newLen); | |
if(newPDU==NULL) | |
{ | |
printf("Cannot allocate (or shrink) space for payload"); | |
return NULL; | |
} | |
_pdu = newPDU; | |
_bufferLength = newLen; | |
} | |
else | |
{ | |
// constructed from buffer, check space | |
printf("newLen: %d, _bufferLength: %d",newLen,_bufferLength); | |
if(newLen>_bufferLength) | |
{ | |
printf("Buffer too small to contain desired payload, needed %d, got %d.",newLen-_pduLength,_bufferLength-_pduLength); | |
return NULL; | |
} | |
} | |
// deal with fresh allocation case separately | |
if(_payloadPointer==NULL) | |
{ | |
// set payload marker | |
_pdu[_pduLength] = 0xFF; | |
// payload at end of old PDU | |
_payloadPointer = &_pdu[_pduLength+1]; | |
_pduLength = newLen; | |
_payloadLength = len; | |
return _payloadPointer; | |
} | |
// otherwise, just adjust length of PDU | |
_pduLength = newLen; | |
_payloadLength = len; | |
printf("Leaving mallocPayload"); | |
return _payloadPointer; | |
} | |
/// Set the payload to the byte sequence specified. Allocates memory in dynamic PDU if necessary. | |
/** | |
* This will set the payload to \b payload. It will allocate memory in the case where the PDU was | |
* constructed without an external buffer. | |
* | |
* This will fail either if the fixed buffer isn't big enough, or if memory could not be allocated | |
* in the non-external-buffer case. | |
* | |
* \param payload Pointer to payload byte sequence. | |
* \param len Length of payload byte sequence. | |
* \return 0 on success, 1 on failure. | |
*/ | |
int CoapPDU::setPayload(uint8_t *payload, int len) | |
{ | |
if(payload==NULL) | |
{ | |
printf("NULL payload pointer."); | |
return 1; | |
} | |
uint8_t *payloadPointer = mallocPayload(len); | |
if(payloadPointer==NULL) | |
{ | |
printf("Allocation of payload failed"); | |
return 1; | |
} | |
// copy payload contents | |
memcpy(payloadPointer,payload,len); | |
return 0; | |
} | |
/// Returns a pointer to the payload buffer. | |
uint8_t* CoapPDU::getPayloadPointer() | |
{ | |
return _payloadPointer; | |
} | |
/// Gets the length of the payload buffer. | |
int CoapPDU::getPayloadLength() | |
{ | |
return _payloadLength; | |
} | |
/// Returns a pointer to a buffer which is a copy of the payload buffer (dynamically allocated). | |
uint8_t* CoapPDU::getPayloadCopy() | |
{ | |
if(_payloadLength==0) | |
{ | |
return NULL; | |
} | |
// malloc space for copy | |
uint8_t *payload = (uint8_t*)malloc(_payloadLength); | |
if(payload==NULL) | |
{ | |
printf("Unable to allocate memory for payload"); | |
return NULL; | |
} | |
// copy and return | |
memcpy(payload,_payloadPointer,_payloadLength); | |
return payload; | |
} | |
/// Shorthand for setting the content-format option. | |
/** | |
* Sets the content-format to the specified value (adds an option). | |
* \param format The content format, one of: | |
* | |
* - COAP_CONTENT_FORMAT_TEXT_PLAIN | |
* - COAP_CONTENT_FORMAT_APP_LINK | |
* - COAP_CONTENT_FORMAT_APP_XML | |
* - COAP_CONTENT_FORMAT_APP_OCTET | |
* - COAP_CONTENT_FORMAT_APP_EXI | |
* - COAP_CONTENT_FORMAT_APP_JSON | |
* | |
* \return 0 on success, 1 on failure. | |
*/ | |
int CoapPDU::setContentFormat(int format) | |
{ | |
if(format==0) | |
{ | |
// minimal representation means null option value | |
if(addOption(CoapPDU::COAP_OPTION_CONTENT_FORMAT,0,NULL)!=0) | |
{ | |
printf("Error setting content format"); | |
return 1; | |
} | |
return 0; | |
} | |
uint8_t c[2]; | |
// just use 1 byte if can do it | |
//if((uint16_t)format <= 0xffu) { | |
if(1) | |
{ | |
c[0] = format; | |
if(addOption(CoapPDU::COAP_OPTION_CONTENT_FORMAT,1,c)!=0) | |
{ | |
printf("Error setting content format"); | |
return 1; | |
} | |
return 0; | |
} | |
uint8_t *to = c; | |
endian_store16(to, format); | |
if(addOption(CoapPDU::COAP_OPTION_CONTENT_FORMAT,2,c)!=0) | |
{ | |
printf("Error setting content format"); | |
return 1; | |
} | |
return 0; | |
} | |
// PRIVATE PRIVATE PRIVATE PRIVATE PRIVATE PRIVATE PRIVATE | |
// PRIVATE PRIVATE PRIVATE PRIVATE PRIVATE PRIVATE PRIVATE | |
/// Moves a block of bytes to end of PDU from given offset. | |
/** | |
* This moves the block of bytes _pdu[_pduLength-1-shiftOffset-shiftAmount] ... _pdu[_pduLength-1-shiftOffset] | |
* to the end of the PDU. | |
* \param shiftOffset End of block to move, relative to end of PDU (-1). | |
* \param shiftAmount Length of block to move. | |
*/ | |
void CoapPDU::shiftPDUUp(int shiftOffset, int shiftAmount) | |
{ | |
printf("shiftOffset: %d, shiftAmount: %d",shiftOffset,shiftAmount); | |
int destPointer = _pduLength-1; | |
int srcPointer = destPointer-shiftOffset; | |
while(shiftAmount--) | |
{ | |
_pdu[destPointer] = _pdu[srcPointer]; | |
destPointer--; | |
srcPointer--; | |
} | |
} | |
/// Moves a block of bytes down a specified number of steps. | |
/** | |
* Moves the block of bytes _pdu[startLocation+shiftOffset] ... _pdu[startLocation+shiftOffset+shiftAmount] | |
* down to \b startLocation. | |
* \param startLocation Index where to shift the block to. | |
* \param shiftOffset Where the block starts, relative to start index. | |
* \param shiftAmount Length of block to shift. | |
*/ | |
void CoapPDU::shiftPDUDown(int startLocation, int shiftOffset, int shiftAmount) | |
{ | |
printf("startLocation: %d, shiftOffset: %d, shiftAmount: %d",startLocation,shiftOffset,shiftAmount); | |
int srcPointer = startLocation+shiftOffset; | |
while(shiftAmount--) | |
{ | |
_pdu[startLocation] = _pdu[srcPointer]; | |
startLocation++; | |
srcPointer++; | |
} | |
} | |
/// Gets the payload length of an option. | |
/** | |
* \param option Pointer to location of option in PDU. | |
* \return The 16 bit option-payload length. | |
*/ | |
uint16_t CoapPDU::getOptionValueLength(uint8_t *option) | |
{ | |
uint16_t delta = (option[0] & 0xF0) >> 4; | |
uint16_t length = (option[0] & 0x0F); | |
// no extra bytes | |
if(length<13) | |
{ | |
return length; | |
} | |
// extra bytes skip header | |
int offset = 1; | |
// skip extra option delta bytes | |
if(delta==13) | |
{ | |
offset++; | |
} | |
else if(delta==14) | |
{ | |
offset+=2; | |
} | |
// process length | |
if(length==13) | |
{ | |
return (option[offset]+13); | |
} | |
else | |
{ | |
uint8_t *from = &option[offset]; | |
uint16_t value = endian_load16(uint16_t, from); | |
return value+269; | |
} | |
} | |
/// Gets the delta of an option. | |
/** | |
* \param option Pointer to location of option in PDU. | |
* \return The 16 bit delta. | |
*/ | |
uint16_t CoapPDU::getOptionDelta(uint8_t *option) | |
{ | |
uint16_t delta = (option[0] & 0xF0) >> 4; | |
if(delta<13) | |
{ | |
return delta; | |
} | |
else if(delta==13) | |
{ | |
// single byte option delta | |
return (option[1]+13); | |
} | |
else if(delta==14) | |
{ | |
uint8_t *from = &option[1]; | |
uint16_t value = endian_load16(uint16_t, from); | |
return value+269; | |
} | |
else | |
{ | |
// should only ever occur in payload marker | |
return delta; | |
} | |
} | |
/// Finds the insertion position in the current list of options for the specified option. | |
/** | |
* \param optionNumber The option's number. | |
* \param prevOptionNumber A pointer to a uint16_t which will store the option number of the option previous | |
* to the insertion point. | |
* \return 0 on success, 1 on failure. \b prevOptionNumber will contain the option number of the option | |
* before the insertion position (for example 0 if no options have been inserted). | |
*/ | |
int CoapPDU::findInsertionPosition(uint16_t optionNumber, uint16_t *prevOptionNumber) | |
{ | |
// zero this for safety | |
*prevOptionNumber = 0x00; | |
printf("_pduLength: %d",_pduLength); | |
// if option is bigger than any currently stored, it goes at the end | |
// this includes the case that no option has yet been added | |
if( (optionNumber >= _maxAddedOptionNumber) || (_pduLength == (COAP_HDR_SIZE+getTokenLength())) ) | |
{ | |
*prevOptionNumber = _maxAddedOptionNumber; | |
return _pduLength; | |
} | |
// otherwise walk over the options | |
int optionPos = COAP_HDR_SIZE + getTokenLength(); | |
uint16_t optionDelta = 0, optionValueLength = 0; | |
uint16_t currentOptionNumber = 0; | |
while(optionPos<_pduLength && _pdu[optionPos]!=0xFF) | |
{ | |
optionDelta = getOptionDelta(&_pdu[optionPos]); | |
currentOptionNumber += optionDelta; | |
optionValueLength = getOptionValueLength(&_pdu[optionPos]); | |
// test if this is insertion position | |
if(currentOptionNumber>optionNumber) | |
{ | |
return optionPos; | |
} | |
// keep track of the last valid option number | |
*prevOptionNumber = currentOptionNumber; | |
// move onto next option | |
optionPos += computeExtraBytes(optionDelta); | |
optionPos += computeExtraBytes(optionValueLength); | |
optionPos += optionValueLength; | |
optionPos++; // (for mandatory option header byte) | |
} | |
return optionPos; | |
} | |
/// CoAP uses a minimal-byte representation for length fields. This returns the number of bytes needed to represent a given length. | |
int CoapPDU::computeExtraBytes(uint16_t n) | |
{ | |
if(n<13) | |
{ | |
return 0; | |
} | |
if(n<269) | |
{ | |
return 1; | |
} | |
return 2; | |
} | |
/// Set the option delta to the specified value. | |
/** | |
* This assumes space has been made for the option delta. | |
* \param optionPosition The index of the option in the PDU. | |
* \param optionDelta The option delta value to set. | |
*/ | |
void CoapPDU::setOptionDelta(int optionPosition, uint16_t optionDelta) | |
{ | |
int headerStart = optionPosition; | |
// clear the old option delta bytes | |
_pdu[headerStart] &= 0x0F; | |
// set the option delta bytes | |
if(optionDelta<13) | |
{ | |
_pdu[headerStart] |= (optionDelta << 4); | |
} | |
else if(optionDelta<269) | |
{ | |
// 1 extra byte | |
_pdu[headerStart] |= 0xD0; // 13 in first nibble | |
_pdu[++optionPosition] &= 0x00; | |
_pdu[optionPosition] |= (optionDelta-13); | |
} | |
else | |
{ | |
// 2 extra bytes, network byte order uint16_t | |
_pdu[headerStart] |= 0xE0; // 14 in first nibble | |
optionDelta -= 269; | |
uint8_t *to = &_pdu[++optionPosition]; | |
endian_store16(to, optionDelta); | |
} | |
} | |
/// Insert an option in-memory at the specified location. | |
/** | |
* This assumes that there is enough space at the location specified. | |
* \param insertionPosition Position in the PDU where the option should be placed. | |
* \param optionDelta The delta value for the option. | |
* \param optionValueLength The length of the option value. | |
* \param optionValue A pointer to the sequence of bytes representing the option value. | |
* \return 0 on success, 1 on failure. | |
*/ | |
int CoapPDU::insertOption( | |
int insertionPosition, | |
uint16_t optionDelta, | |
uint16_t optionValueLength, | |
uint8_t *optionValue) | |
{ | |
int headerStart = insertionPosition; | |
// clear old option header start | |
_pdu[headerStart] &= 0x00; | |
// set the option delta bytes | |
if(optionDelta<13) | |
{ | |
_pdu[headerStart] |= (optionDelta << 4); | |
} | |
else if(optionDelta<269) | |
{ | |
// 1 extra byte | |
_pdu[headerStart] |= 0xD0; // 13 in first nibble | |
_pdu[++insertionPosition] &= 0x00; | |
_pdu[insertionPosition] |= (optionDelta-13); | |
} | |
else | |
{ | |
// 2 extra bytes, network byte order uint16_t | |
_pdu[headerStart] |= 0xE0; // 14 in first nibble | |
optionDelta -= 269; | |
uint8_t *to = &_pdu[++insertionPosition]; | |
endian_store16(to, optionDelta); | |
insertionPosition += 1; | |
} | |
// set the option value length bytes | |
if(optionValueLength<13) | |
{ | |
_pdu[headerStart] |= (optionValueLength & 0x000F); | |
} | |
else if(optionValueLength<269) | |
{ | |
_pdu[headerStart] |= 0x0D; // 13 in second nibble | |
_pdu[++insertionPosition] &= 0x00; | |
_pdu[insertionPosition] |= (optionValueLength-13); | |
} | |
else | |
{ | |
_pdu[headerStart] |= 0x0E; // 14 in second nibble | |
// this is in network byte order | |
printf("optionValueLength: %u",optionValueLength); | |
uint8_t *to = &_pdu[++insertionPosition]; | |
optionValueLength -= 269; | |
endian_store16(to, optionValueLength); | |
insertionPosition += 1; | |
} | |
// and finally copy the option value itself | |
memcpy(&_pdu[++insertionPosition],optionValue,optionValueLength); | |
return 0; | |
} | |
void CoapPDU::getOptionValueById | |
( | |
uint16_t optionNumber, | |
uint16_t *optionValueLength, | |
uint8_t *optionValuePointer | |
) | |
{ | |
if(_numOptions == 0) | |
return; | |
CoapOption* options = getOptions(); | |
int i = 0; | |
for(; i < _numOptions; i++) | |
{ | |
if(options[i].optionNumber == optionNumber) | |
{ | |
*optionValueLength = options[i].optionValueLength; | |
memcpy(optionValuePointer, options[i].optionValuePointer, options[i].optionValueLength); | |
} | |
} | |
free(options); | |
} | |
int CoapPDU::hasOption | |
( | |
uint16_t optionNumber | |
) | |
{ | |
int res = 0; | |
if(_numOptions == 0) | |
return res; | |
CoapOption* options = getOptions(); | |
int i = 0; | |
for(; i < _numOptions; i++) | |
{ | |
if(options[i].optionNumber == optionNumber) | |
{ | |
res = 1; | |
break; | |
} | |
} | |
free(options); | |
return res; | |
} | |
const char *CoapPDU::printHuman() | |
{ | |
content.clear(); | |
char temp1[128]; | |
sprintf(temp1,"PDU is %d bytes long\r\n",_pduLength); | |
content.append(temp1); | |
sprintf(temp1,"CoAP Version: %d\r\n",getVersion()); | |
content.append("Message Type: "); | |
switch(getType()) | |
{ | |
case COAP_CONFIRMABLE: | |
content.append("Confirmable"); | |
break; | |
case COAP_NON_CONFIRMABLE: | |
content.append("Non-Confirmable"); | |
break; | |
case COAP_ACKNOWLEDGEMENT: | |
content.append("Acknowledgement"); | |
break; | |
case COAP_RESET: | |
content.append("Reset"); | |
break; | |
} | |
sprintf(temp1,"\r\nToken length: %d\r\n",getTokenLength()); | |
content.append(temp1); | |
content.append("Code: "); | |
switch(getCode()) | |
{ | |
case COAP_EMPTY: | |
content.append("0.00 Empty"); | |
break; | |
case COAP_GET: | |
content.append("0.01 GET"); | |
break; | |
case COAP_POST: | |
content.append("0.02 POST"); | |
break; | |
case COAP_PUT: | |
content.append("0.03 PUT"); | |
break; | |
case COAP_DELETE: | |
content.append("0.04 DELETE"); | |
break; | |
case COAP_CREATED: | |
content.append("2.01 Created"); | |
break; | |
case COAP_DELETED: | |
content.append("2.02 Deleted"); | |
break; | |
case COAP_VALID: | |
content.append("2.03 Valid"); | |
break; | |
case COAP_CHANGED: | |
content.append("2.04 Changed"); | |
break; | |
case COAP_CONTENT: | |
content.append("2.05 Content"); | |
break; | |
case COAP_CONTINUE: | |
content.append("2.31 Continue"); | |
break; | |
case COAP_BAD_REQUEST: | |
content.append("4.00 Bad Request"); | |
break; | |
case COAP_UNAUTHORIZED: | |
content.append("4.01 Unauthorized"); | |
break; | |
case COAP_BAD_OPTION: | |
content.append("4.02 Bad Option"); | |
break; | |
case COAP_FORBIDDEN: | |
content.append("4.03 Forbidden"); | |
break; | |
case COAP_NOT_FOUND: | |
content.append("4.04 Not Found"); | |
break; | |
case COAP_METHOD_NOT_ALLOWED: | |
content.append("4.05 Method Not Allowed"); | |
break; | |
case COAP_NOT_ACCEPTABLE: | |
content.append("4.06 Not Acceptable"); | |
break; | |
case COAP_PRECONDITION_FAILED: | |
content.append("4.12 Precondition Failed"); | |
break; | |
case COAP_REQUEST_ENTITY_TOO_LARGE: | |
content.append("4.13 Request Entity Too Large"); | |
break; | |
case COAP_UNSUPPORTED_CONTENT_FORMAT: | |
content.append("4.15 Unsupported Content-Format"); | |
break; | |
case COAP_INTERNAL_SERVER_ERROR: | |
content.append("5.00 Internal Server Error"); | |
break; | |
case COAP_NOT_IMPLEMENTED: | |
content.append("5.01 Not Implemented"); | |
break; | |
case COAP_BAD_GATEWAY: | |
content.append("5.02 Bad Gateway"); | |
break; | |
case COAP_SERVICE_UNAVAILABLE: | |
content.append("5.03 Service Unavailable"); | |
break; | |
case COAP_GATEWAY_TIMEOUT: | |
content.append("5.04 Gateway Timeout"); | |
break; | |
case COAP_PROXYING_NOT_SUPPORTED: | |
content.append("5.05 Proxying Not Supported"); | |
break; | |
default: | |
sprintf(temp1, "Undefined Code %u",(unsigned)(getCode())); | |
content.append(temp1); | |
} | |
// print message ID | |
sprintf(temp1,"\r\nMessage ID: %u\r\n",getMessageID()); | |
content.append(temp1); | |
// print token value | |
int tokenLength = getTokenLength(); | |
uint8_t *tokenPointer = getPDUPointer()+COAP_HDR_SIZE; | |
if(tokenLength==0) | |
{ | |
content.append("No token.\r\n"); | |
} | |
else | |
{ | |
//memset(temp1,0,50); | |
sprintf(temp1,"Token of %d bytes.\r\n",tokenLength); | |
content.append(temp1); | |
content.append("Value: 0x"); | |
char temp2[5]; | |
for(int j=0; j<tokenLength; j++) | |
{ | |
sprintf(temp2,"%.2X",tokenPointer[j]); | |
content.append(temp2); | |
} | |
} | |
// print options | |
CoapPDU::CoapOption* options = getOptions(); | |
if(options==NULL) | |
{ | |
content.append("\r\nNO options"); | |
} | |
else | |
{ | |
//memset(temp1,0,50); | |
sprintf(temp1,"\r\n%d options:",_numOptions); | |
content.append(temp1); | |
} | |
for(int i=0; i<_numOptions; i++) | |
{ | |
char optionTemp[128]; | |
sprintf(optionTemp,"\r\nOPTION (%d/%d)\r\n" | |
"Option number (delta): %hu (%hu)\r\n" | |
"Name: " | |
,i + 1,_numOptions, | |
options[i].optionNumber,options[i].optionDelta); | |
content.append(optionTemp); | |
boolean asString = FALSE; | |
switch(options[i].optionNumber) | |
{ | |
case COAP_OPTION_IF_MATCH: | |
content.append("IF_MATCH"); | |
break; | |
case COAP_OPTION_URI_HOST: | |
content.append("URI_HOST"); | |
asString = TRUE; | |
break; | |
case COAP_OPTION_ETAG: | |
content.append("ETAG"); | |
break; | |
case COAP_OPTION_IF_NONE_MATCH: | |
content.append("IF_NONE_MATCH"); | |
break; | |
case COAP_OPTION_OBSERVE: | |
content.append("OBSERVE"); | |
break; | |
case COAP_OPTION_URI_PORT: | |
content.append("URI_PORT"); | |
break; | |
case COAP_OPTION_LOCATION_PATH: | |
content.append("LOCATION_PATH"); | |
break; | |
case COAP_OPTION_URI_PATH: | |
content.append("URI_PATH"); | |
asString = TRUE; | |
break; | |
case COAP_OPTION_CONTENT_FORMAT: | |
content.append("CONTENT_FORMAT"); | |
break; | |
case COAP_OPTION_MAX_AGE: | |
content.append("MAX_AGE"); | |
break; | |
case COAP_OPTION_URI_QUERY: | |
content.append("URI_QUERY"); | |
asString = TRUE; | |
break; | |
case COAP_OPTION_ACCEPT: | |
content.append("ACCEPT"); | |
break; | |
case COAP_OPTION_LOCATION_QUERY: | |
content.append("LOCATION_QUERY"); | |
asString = TRUE; | |
break; | |
case COAP_OPTION_PROXY_URI: | |
content.append("PROXY_URI"); | |
asString = TRUE; | |
break; | |
case COAP_OPTION_PROXY_SCHEME: | |
content.append("PROXY_SCHEME"); | |
asString = TRUE; | |
break; | |
case COAP_OPTION_BLOCK1: | |
content.append("BLOCK1"); | |
break; | |
case COAP_OPTION_BLOCK2: | |
content.append("BLOCK2"); | |
break; | |
case COAP_OPTION_SIZE1: | |
content.append("SIZE1"); | |
break; | |
case COAP_OPTION_SIZE2: | |
content.append("SIZE2"); | |
break; | |
default: | |
//memset(temp1,0,50); | |
sprintf(temp1,"Unknown option %u",(unsigned)options[i].optionNumber); | |
content.append(temp1); | |
break; | |
} | |
//memset(temp1,0,50); | |
sprintf(temp1,"\r\nValue length: %u\r\n",options[i].optionValueLength); | |
content.append(temp1); | |
if(asString) | |
{ | |
content.append("Value: "); | |
} | |
else | |
{ | |
content.append("Value: 0x"); | |
} | |
char temp3[5]; | |
for(int j=0; j<options[i].optionValueLength; j++) | |
{ | |
if(asString) | |
{ | |
char c = options[i].optionValuePointer[j]; | |
if((c>='!'&&c<='~')||c==' ') | |
{ | |
sprintf(temp3,"%c", c); | |
} | |
else | |
{ | |
sprintf(temp3,"\\%.2d",c); | |
} | |
content.append(temp3); | |
} | |
else | |
{ | |
sprintf(temp3,"%.2X",options[i].optionValuePointer[j]); | |
content.append(temp3); | |
} | |
} | |
} | |
// print payload | |
if(_payloadLength==0) | |
{ | |
content.append("\r\nNo payload.\r\n"); | |
} | |
else | |
{ | |
//memset(temp1,0,50); | |
sprintf(temp1,"\r\nPayload of %d bytes\r\nValue: 0x",_payloadLength); | |
content.append(temp1); | |
char temp4[5]; | |
for(int j=0; j<_payloadLength; j++) | |
{ | |
sprintf(temp4,"%.2X",_payloadPointer[j]); | |
content.append(temp4); | |
} | |
content.append("\r\n"); | |
} | |
if(options) | |
free(options); | |
return content.c_str(); | |
} | |
const char * CoapPDU::printHex() | |
{ | |
content.clear(); | |
char temp[5]; | |
for(int i=0; i<_pduLength; i++) | |
{ | |
sprintf(temp,"%.2X",_pdu[i]); | |
content.append(temp); | |
} | |
content.append("\r\n"); | |
return content.c_str(); | |
} | |
//#endif |