doap/source/doap/protocol/packet.d

1291 lines
41 KiB
D

module doap.protocol.packet;
import doap.protocol.types : MessageType;
import doap.protocol.codes : Code;
import doap.exceptions : CoapException;
import std.conv : to;
import doap.utils : order, Order, toBytes;
/**
* Payload marker
*/
private ubyte PAYLOAD_MARKER = cast(ubyte)-1;
/**
* A header option
*/
public struct CoapOption
{
/**
* Option ID
*/
public ushort id;
/**
* Option value
*/
public ubyte[] value;
/**
* Compares this option with
* the provided one
*
* Params:
* right = the right-hand side
* `CoapOption` to compare to
* Returns: difference
*/
public int opCmp(CoapOption right)
{
return this.id-right.id;
}
}
// TODO: remove this
import std.stdio : writeln;
private enum OptionLenType
{
ZERO_TO_TWELVE,
_8BIT_EXTENDED,
_12_BIT_EXTENDED,
UPPER_PAYLOAD_MARKER
}
private enum OptionDeltaType
{
ZERO_TO_TWELVE,
_8BIT_EXTENDED,
_12_BIT_EXTENDED,
UPPER_PAYLOAD_MARKER
}
/**
* Represents a CoAP packet
*/
public class CoapPacket
{
/**
* The protocol version
*/
private ubyte ver;
/**
* The message type
*/
private MessageType type;
/**
* Token length
*/
private ubyte tokenLen;
/**
* The code
*/
private Code code;
/**
* The message id (mid)
*/
private ushort mid;
/**
* The token (if any)
*/
private ubyte[] token;
/**
* List of options (if any)
*/
private CoapOption[] options;
/**
* The payload (if any)
*/
private ubyte[] payload;
/**
* Constructs a new `CoapPacket`
* with protocol version 1
*/
this()
{
// Set the version (Default is 1)
ver = 1;
}
/**
* Encodes the current `CoapPacket`
* into the byte sequence
*
* Returns: the bytes
*/
public ubyte[] getBytes()
{
ubyte[] encoded;
// Calculate the first byte (ver | type | tkl)
ubyte firstByte = cast(ubyte)(ver << 6);
firstByte = firstByte | cast(ubyte)(type << 4);
firstByte = firstByte | tokenLen;
encoded ~= firstByte;
// Set the request/response code
encoded ~= code;
// Set the message ID (encoded as big endian)
version(LittleEndian)
{
ubyte* basePtr = cast(ubyte*)&mid;
ubyte lowByte = *basePtr;
ubyte hiByte = *(basePtr+1);
encoded ~= [hiByte, lowByte];
}
else version(BigEndian)
{
ubyte* basePtr = cast(ubyte*)&mid;
ubyte lowByte = *(basePtr+1);
ubyte hiByte = *(basePtr);
encoded ~= [hiByte, lowByte];
}
// Set the token (if any)
if(tokenLen)
{
encoded ~= token;
}
// FIXME: Add options encoding
ushort lstOptid = 0;
foreach(CoapOption option; orderOptions())
{
encoded ~= encodeOption(option, lstOptid);
}
// Set the payload marker
encoded ~= PAYLOAD_MARKER;
// Set the payload
encoded ~= payload;
return encoded;
}
// TODO: Make public in the future
private static ubyte[] encodeOption(CoapOption option, ref ushort lstOptid)
{
// Finally constructed option encoded
ubyte[] encoded;
// Current option id
ushort curOptionId = option.id;
writeln("encodeOption: LastOptionId enter: ", curOptionId);
// Determine the option id type
OptionDeltaType optType = determineOptionType(option.id);
// Determine the length type
size_t len = option.value.length;
OptionLenType lenType = determineLenType(len);
// Construct the header (option delta)
if(optType == OptionDeltaType.ZERO_TO_TWELVE)
{
// Calculate the delta as (curOptionId-lastOptionId)
ubyte delta = cast(ubyte)(curOptionId-lstOptid);
// Update the last option id
lstOptid = curOptionId;
// Encode the option delta directly
ubyte optHdr = cast(ubyte)(delta<<4);
// Add the `(Option delta | Option length)`
encoded ~= optHdr;
}
else if(optType == OptionDeltaType._8BIT_EXTENDED)
{
// Encode the value 13
ubyte optHdr = cast(ubyte)(13<<4);
// Add the `(Option delta | Option length)`
encoded ~= optHdr;
// Calculate the delta as (curOptionId-lstOptionId)-13
ubyte delta = cast(ubyte)((curOptionId-lstOptid)-13);
// Update the last option id
lstOptid = curOptionId;
// Now tack on the delta
encoded ~= cast(ubyte)(delta);
}
else if(optType == OptionDeltaType._12_BIT_EXTENDED)
{
// Encode the value 14
ubyte optHdr = cast(ubyte)(14<<4);
// Add the `(Option delta | Option length)`
encoded ~= optHdr;
// Calculate the delta as (curOptionid-lstOptinId)-269
ushort delta = cast(ushort)(curOptionId-lstOptid-269);
// Update the last option id
lstOptid = curOptionId;
// Now tack on the delta
encoded ~= toBytes(order(cast(ushort)(delta), Order.BE));
}
else
{
throw new CoapException("Cannot encode an option with invalid id of '"~to!(string)(option.id)~"'");
}
// Construct the header (option length)
if(lenType == OptionLenType.ZERO_TO_TWELVE)
{
// Encode the length directly
ubyte lenHdr = cast(ubyte)(len&15); // TODO: Remove useless and
// Add the `(Option delta | Option length)`
encoded[0] |= lenHdr;
}
else if(lenType == OptionLenType._8BIT_EXTENDED)
{
// Encode the value 13
ubyte lenHdr = cast(ubyte)(13&15); // TODO: Remove useless and
// Add the `(Option delta | Option length)`
encoded[0] |= lenHdr;
// Now tack on the length-13
encoded ~= [cast(ubyte)(len-13)];
}
else if(lenType == OptionLenType._12_BIT_EXTENDED)
{
// Encode the value 14
ubyte lenHdr = cast(ubyte)(14&15); // TODO: Remove useless and
// Add the `(Option delta | Option length)`
encoded[0] |= lenHdr;
// Now tack on the length-269
encoded ~= toBytes(order(cast(ushort)(len-269), Order.BE));
}
else
{
throw new CoapException("Cannot encode an option with a length of '"~to!(string)(option.value.length)~"'");
}
// Now tack on the option value
encoded ~= option.value;
return encoded;
}
/**
* Takes the currently set options
* and orders them and returns an ordered
* copy
*
* Returns: the ordered options array
*/
private CoapOption[] orderOptions()
{
import std.algorithm.sorting : sort;
CoapOption[] sorted = sort!("a<b")( this.options.dup).release();
return sorted;
}
/**
* Adds the prvided option.
*
* If an option already exists
* with a matching ID then its
* value is updated with the
* new one.
*
* Params:
* option = the option to add
*/
public void addOption(CoapOption option)
{
foreach(CoapOption curOpt; this.options)
{
if(curOpt.id == option.id)
{
curOpt.value = option.value;
return;
}
}
this.options ~= [option];
}
/**
* Returns the options currently
* configured
*
* Returns: the options
*/
public CoapOption[] getOptions()
{
return this.options.dup;
}
/**
* Given a payload size this determines
* the required type of option length
* encoding to be used.
*
* If the size is unsupported then
* `OptionLenType.UPPER_PAYLOAD_MARKER`
* is returned.
*
* Params:
* dataSize = the payload's size
* Returns: the `OptionLenType`
*/
private static OptionLenType determineLenType(size_t dataSize)
{
if(dataSize >= 0 && dataSize <= 12)
{
return OptionLenType.ZERO_TO_TWELVE;
}
else if(dataSize >= 13 && dataSize <= 268)
{
return OptionLenType._8BIT_EXTENDED;
}
else if(dataSize >= 269 && dataSize <= 65804)
{
return OptionLenType._12_BIT_EXTENDED;
}
else
{
return OptionLenType.UPPER_PAYLOAD_MARKER;
}
}
/**
* Given an option ID this determines
* the required type of option id
* encoding to be used.
*
* If the size is unsupported then
* `OptionLenType.UPPER_PAYLOAD_MARKER`
* is returned.
*
* Params:
* id = the option id
* Returns: the `OptionDeltaType`
*/
private static OptionDeltaType determineOptionType(size_t id)
{
if(id >= 0 && id <= 12)
{
return OptionDeltaType.ZERO_TO_TWELVE;
}
else if(id >= 13 && id <= 268)
{
return OptionDeltaType._8BIT_EXTENDED;
}
else if(id >= 269 && id <= 65804)
{
return OptionDeltaType._12_BIT_EXTENDED;
}
else
{
return OptionDeltaType.UPPER_PAYLOAD_MARKER;
}
}
/**
* Sets the message type
*
* Params:
* type = the `MessageType`
*/
public void setType(MessageType type)
{
this.type = type;
}
/**
* Sets the token
*
* Params:
* token = the token to set
* Throws:
* CoapException if the token
* is too long
*/
public void setToken(ubyte[] token)
{
if(setTokenLength(token.length))
{
this.token = token;
}
else
{
throw new CoapException("Token length above 15 bytes not allowed");
}
}
/**
* Attempts to set the token length
* and returns whether or not it
* was a success
*
* Params:
* tkl = the token length
* Returns: `true` if a valid lenght,
* `false` otherwise
*/
private bool setTokenLength(ulong tkl)
{
if(tkl > 15)
{
return false;
}
else
{
this.tokenLen = cast(ubyte)tkl;
return true;
}
}
/**
* Sets the code
*
* Params:
* code = the `Code`
*/
public void setCode(Code code)
{
this.code = code;
}
/**
* Sets the message ID
*
* Params:
* mid = the message id
*/
public void setMessageId(ushort mid)
{
this.mid = mid;
}
/**
* Adds the provided options
*
* Params:
* options = the options to
* add
*/
public void setOptions(CoapOption[] options)
{
// Add each option (duplication done in callee)
foreach(CoapOption option; options)
{
addOption(option);
}
}
/**
* Sets the payload for this
* message
*
* Params:
* payload = the payload
*/
public void setPayload(ubyte[] payload)
{
this.payload = payload;
}
/**
* Returns the protocol version
*
* Returns: the version
*/
public ubyte getVersion()
{
return this.ver;
}
/**
* Returns the type of message
*
* Returns: the `MessageType`
*/
public MessageType getType()
{
return this.type;
}
/**
* Returns the length of the currently
* set token
*
* Returns: the length
*/
public ubyte getTokenLength()
{
return this.tokenLen;
}
/**
* Returns the token
*
* Returns: the token
*/
public ubyte[] getToken()
{
return this.token;
}
/**
* Returns the code
*
* Returns: the `Code`
*/
public Code getCode()
{
return this.code;
}
/**
* Returns the message id
*
* Returns: the message id
*/
public ushort getMessageId()
{
return this.mid;
}
/**
* Decodes the given bytes into a `CoapPacket`
*
* Params:
* data = the bytes to decode
* Returns: a decoded `CoapPacket`
* Throws:
* CoapException on error decoding
*/
public static CoapPacket fromBytes(ubyte[] data)
{
CoapPacket packet = new CoapPacket();
if(data.length < 4)
{
throw new CoapException("CoAP message must be at least 4 bytes in size");
}
packet.ver = data[0]>>6;
packet.type = cast(MessageType)( (data[0]>>4) & 3);
packet.tokenLen = data[0]&15;
packet.code = cast(Code)(data[1]);
writeln("Decoded code: ", packet.code);
ubyte* midBase = data[2..4].ptr;
version(LittleEndian)
{
ubyte* pMidBase = cast(ubyte*)&packet.mid;
*(pMidBase) = *(midBase+1);
*(pMidBase+1) = *(midBase);
}
else version(BigEndian)
{
ubyte* pMidBase = cast(ubyte*)&packet.mid;
*(pMidBase) = *(midBase);
*(pMidBase+1) = *(midBase+1);
}
if(packet.tokenLen)
{
packet.token = data[4..4+packet.tokenLen];
}
// TODO: Do options decode here
ubyte[] remainder = data[4+packet.tokenLen..$];
version(unittest) writeln("Remainder: ", remainder);
ulong idx = 4+packet.tokenLen;
writeln();
writeln();
CoapOption[] createdOptions;
if(remainder.length)
{
// Last option ID
ushort lastOptionId = 0;
while(true)
{
writeln("Last Option ID (ENTER): ", lastOptionId);
writeln("Remainder [from-idx..$] (ENTER): ", data[idx..$]);
scope(exit)
{
writeln("Currently built options: ", createdOptions);
writeln();
writeln();
}
ubyte curValue = data[idx];
// If entire current value is -1/~0
// then we reached the payload marker
if(curValue == PAYLOAD_MARKER)
{
writeln("Found payload marker, stopping option parsing");
idx++;
break;
}
// Option delta
ubyte computed = (curValue&240) >> 4;
writeln("Computed delta-type: ", computed);
// New option id
ushort curOptionId;
// Option value
ubyte[] optionValue;
// 0 to 12 Option ID
if(computed >= 0 && computed <= 12)
{
writeln("Delta is 0 to 12");
// The option-delta-type IS the delta
ubyte delta = computed;
// The new option ID is the lastOption+delta
curOptionId = cast(ushort)(lastOptionId+delta);
writeln("Option id: ", curOptionId);
// Get the type of option length
OptionLenType optLenType = getOptionLenType(curValue);
writeln("Option length type: ", optLenType);
// Simple case (12)
if(optLenType == OptionLenType.ZERO_TO_TWELVE)
{
// Compute the length
ubyte optLen = (curValue&15);
writeln("Option len: ", optLen);
// Update idx to jump over the (option delta | option length)
idx+=1;
// Grab the data from [idx, idx+length)
optionValue = data[idx..idx+optLen];
writeln("Option value: ", optionValue);
// Jump over the option value
idx+=optLen;
}
// Option length extended (8bit) (13)
else if(optLenType == OptionLenType._8BIT_EXTENDED)
{
// Next byte has the length
idx+=1;
// The total length is the extended value (which lacks 13 so we must add it)
writeln(data[idx..$]);
ubyte optLen8BitExt = data[idx];
ushort optLen = optLen8BitExt+13;
writeln("Option len: ", optLen);
// Jump over 8bit opt len ext
idx+=1;
// Grab the data from [idx, idx+optLen)
optionValue = data[idx..idx+optLen];
writeln("Option value: ", optionValue);
writeln("Option value: ", cast(string)optionValue);
// Jump over the option value
idx+=optLen;
}
// Option length extended (16bit) (14)
else if(optLenType == OptionLenType._12_BIT_EXTENDED)
{
// TODO: THIS IS UNTESTED CODE!!!
// Jump to next byte of two bytes (which has length)
idx+=1;
// Option length compute (it lacks 269 so add it back)
ushort optLen = order(*cast(ushort*)&data[idx], Order.BE);
optLen+=269;
writeln("Option len: ", optLen);
// Jump over the two option length bytes
idx+=2;
// Grab the data from [idx, idx+optLen)
optionValue = data[idx..idx+optLen];
writeln("Option value: ", optionValue);
writeln("Option value: ", cast(string)optionValue);
// Jump over the option value
idx+=optLen;
}
}
// 13
else if(computed == 13)
{
writeln("3333 Option delta type: 13 - DEVELOPER ADD SUPPORT! 3333");
// TODO: This is UNTESTED code!!!!
// Skip over the 4bit tuple
idx+=1;
// Delta value is 1 byte (the value found is lacking 13 so add it back)
ushort delta = data[idx]+13;
// New option id is the lastOptionId+delta
curOptionId = cast(ushort)(lastOptionId+delta);
// Jump over the 1 byte option delta
idx+=1;
writeln("8 bit option-id: ", curOptionId);
// Get the type of option length
OptionLenType optLenType = getOptionLenType(curValue);
writeln("Option length type: ", optLenType);
// Simple case (12)
if(optLenType == OptionLenType.ZERO_TO_TWELVE)
{
// Compute the length
ubyte optLen = (curValue&15);
writeln("Option len: ", optLen);
// Grab the data from [idx, idx+length)
optionValue = data[idx..idx+optLen];
writeln("Option value: ", optionValue);
// Jump over the option value
idx+=optLen;
}
// Option length extended (8bit) (13)
else if(optLenType == OptionLenType._8BIT_EXTENDED)
{
// The total length is the extended value (which lacks 13 so we must add it)
writeln(data[idx..$]);
ubyte optLen8BitExt = data[idx];
ushort optLen = optLen8BitExt+13;
writeln("Option len: ", optLen);
// Jump over 8bit opt len ext
idx+=1;
// Grab the data from [idx, idx+optLen)
optionValue = data[idx..idx+optLen];
writeln("Option value: ", optionValue);
writeln("Option value: ", cast(string)optionValue);
// Jump over the option value
idx+=optLen;
}
// Option length extended (16bit) (14)
else if(optLenType == OptionLenType._12_BIT_EXTENDED)
{
// Option length compute (it lacks 269 so add it back)
ushort optLen = order(*cast(ushort*)&data[idx], Order.BE);
optLen+=269;
writeln("Option len: ", optLen);
// Jump over the two option length bytes
idx+=2;
// Grab the data from [idx, idx+optLen)
optionValue = data[idx..idx+optLen];
writeln("Option value: ", optionValue);
writeln("Option value: ", cast(string)optionValue);
// Jump over the option value
idx+=optLen;
}
}
// 14
else if(computed == 14)
{
writeln("Option delta type: 14 - DEVELOPER ADD SUPPORT!");
// Skip over 4bit tuple
idx+=1;
// Delta value is 2 bytes (BE)
ubyte[] optionIdBytes = data[idx..idx+2];
ushort unProcessedValue = *(cast(ushort*)optionIdBytes.ptr);
// The value found is then lacking 269 (so add it back)
ushort delta = cast(ushort)(order(unProcessedValue, Order.BE)+269);
writeln("Delta: ", delta);
// Our option ID is then calculated from lastOptionId+delya
curOptionId = cast(ushort)(lastOptionId+delta);
// Jump over [Option delta extended (16bit)] here
idx+=2;
writeln("16 bit option-id: ", curOptionId);
// Get the option length type
OptionLenType optLenType = getOptionLenType(curValue);
writeln("Option len type: ", optLenType);
// 0 to 12 length type
if(optLenType == OptionLenType.ZERO_TO_TWELVE)
{
// Option length
ubyte optLen = (curValue&15);
writeln("Option len: ", optLen);
// Read the option now
optionValue = data[idx..idx+optLen];
// Jump over the option value
idx+=optLen;
}
// Option length extended (8bit) (13)
else if(optLenType == OptionLenType._8BIT_EXTENDED)
{
// TODO: THIS IS UNTESTED CODE!!!!
// The total length is the extended value (which lacks 13 so we must add it)
writeln(data[idx..$]);
ubyte optLen8BitExt = data[idx];
ushort optLen = optLen8BitExt+13;
writeln("Option len: ", optLen);
// Jump over 8bit opt len ext
idx+=1;
// Grab the data from [idx, idx+optLen)
optionValue = data[idx..idx+optLen];
writeln("Option value: ", optionValue);
writeln("Option value: ", cast(string)optionValue);
// Jump over the option value
idx+=optLen;
}
// Option length extended (16bit) (14)
else if(optLenType == OptionLenType._12_BIT_EXTENDED)
{
// TODO: THIS IS UNTESTED CODE!!!!
// Option length compute (it lacks 269 so add it back)
ushort optLen = order(*cast(ushort*)&data[idx], Order.BE);
optLen+=269;
writeln("Option len: ", optLen);
// Jump over the two option length bytes
idx+=2;
// Grab the data from [idx, idx+optLen)
optionValue = data[idx..idx+optLen];
writeln("Option value: ", optionValue);
writeln("Option value: ", cast(string)optionValue);
// Jump over the option value
idx+=optLen;
}
}
// 15
else if(computed == 15)
{
writeln("FIVEFIVEFIVE Option delta type: 15 - DEVELOPER ADD SUPPORT! FIVEFIVEFIVE");
assert(false);
}
else
{
assert(false);
}
// Create the option and add it to the list of options
CoapOption option;
option.value = optionValue;
option.id = curOptionId;
writeln("Built option: ", option);
createdOptions ~= option;
// Update the last option id
lastOptionId = curOptionId;
}
}
packet.options = createdOptions;
return packet;
}
/**
* Extracts the option length encoding
* type from the header
*
* Params:
* hdr = the header
* Returns: the `OptionLenType`
*/
private static OptionLenType getOptionLenType(ubyte hdr)
{
ubyte type = (hdr&15);
if(type >= 0 && type <= 12)
{
return OptionLenType.ZERO_TO_TWELVE;
}
else if(type == 13)
{
return OptionLenType._8BIT_EXTENDED;
}
else if(type == 14)
{
return OptionLenType._12_BIT_EXTENDED;
}
else
{
return OptionLenType.UPPER_PAYLOAD_MARKER;
}
}
/**
* Returns a string represenation
* of this packet
*
* Returns: the string
*/
public override string toString()
{
return "CoapPacket [ver: "~to!(string)(ver)~
", type: "~to!(string)(type)~
", tkl: "~to!(string)(tokenLen)~
", code: "~to!(string)(code)~
", mid: "~to!(string)(mid)~
", token: "~to!(string)(token)~
", options: "~to!(string)(options)~
"]";
}
}
version(unittest)
{
import std.stdio;
}
/**
* Tests `CoapPacket`'s `determineLenType(size_t)'
*/
unittest
{
assert(CoapPacket.determineLenType(12) == OptionLenType.ZERO_TO_TWELVE);
assert(CoapPacket.determineLenType(268) == OptionLenType._8BIT_EXTENDED);
assert(CoapPacket.determineLenType(65804) == OptionLenType._12_BIT_EXTENDED);
assert(CoapPacket.determineLenType(65804+1) == OptionLenType.UPPER_PAYLOAD_MARKER);
}
/**
* Tests `CoapPacket`'s `determineOptionType(size_t)'
*/
unittest
{
assert(CoapPacket.determineOptionType(12) == OptionDeltaType.ZERO_TO_TWELVE);
assert(CoapPacket.determineOptionType(268) == OptionDeltaType._8BIT_EXTENDED);
assert(CoapPacket.determineOptionType(65804) == OptionDeltaType._12_BIT_EXTENDED);
assert(CoapPacket.determineOptionType(65804+1) == OptionDeltaType.UPPER_PAYLOAD_MARKER);
}
/**
* Tests options encoding
*/
unittest
{
writeln("\n\n");
CoapOption[] expectedOptions = [
CoapOption(3, [49, 48, 48, 46, 54, 52, 46, 48, 46, 49, 50, 58, 53, 54, 56, 51]),
CoapOption(12, [39, 17]),
CoapOption(65001, [1]),
CoapOption(65003, [16]),
CoapOption(65005, [1])
];
// Create a new packet
CoapPacket pack = new CoapPacket();
// Create a copy of the options to add, reverse them in place
// ... (this is to test that the encoder orders them correctly)
import std.algorithm : reverse;
foreach(CoapOption option; reverse(expectedOptions.dup))
{
pack.addOption(option);
}
// Encode
ubyte[] encodedPacket = pack.getBytes();
// Now try decode the packet to we can see if it decodes
// ... the options correctly
CoapPacket actualPacket = CoapPacket.fromBytes(encodedPacket);
// Grab the options
CoapOption[] actualOptions = actualPacket.getOptions();
// We should have the same number of operations
assert(actualOptions.length == expectedOptions.length);
for(size_t i = 0; i < expectedOptions.length; i++)
{
CoapOption expectedOption = expectedOptions[i];
CoapOption actualOption = actualOptions[i];
writeln("Expected option: ", expectedOption);
writeln("Actual option: ", actualOption);
// Option Ids must match
assert(expectedOption.id == actualOption.id);
// Option values must match
assert(expectedOption.value == actualOption.value);
}
writeln("\n\n");
}
/**
* Encoding tests
*
* These set high level parameters and then
* we call `getBytes()` and analyse the components
* of the encoded wire format by hand to ensure
* they are set in place correctly
*/
unittest
{
CoapPacket packet = new CoapPacket();
packet.setType(MessageType.RESET);
ubyte[] token = [0, 69];
packet.setToken(token);
packet.setCode(Code.PONG);
packet.setMessageId(257);
// FIXME: Set options
ubyte[] payload = cast(ubyte[])[-1, -2];
writeln(payload.length);
packet.setPayload(payload);
ubyte[] encoded = packet.getBytes();
ubyte firstByte = encoded[0];
// Ensure the version is set to 1
ubyte versionField = cast(ubyte)(firstByte & 192) >> 6;
assert(versionField == 1);
// Ensure the type is 3/RESET
ubyte typeField = cast(ubyte)(firstByte & 48) >> 4;
assert(typeField == MessageType.RESET);
// Ensure the token length is 2
ubyte tklField = firstByte & 15;
assert(tklField == token.length);
ubyte secondByte = encoded[1];
// Ensure the code is set to PONG
// Class is 7
// Code is 3
ubyte codeClass = cast(ubyte)(secondByte & 224) >> 5;
assert(codeClass == 7);
ubyte code = (secondByte & (~224));
assert(code == 3);
writeln(codeClass);
writeln(code);
assert(secondByte == Code.PONG);
// Ensure the message ID is 257
ubyte thirdByte = encoded[2], fourthByte = encoded[3];
assert(thirdByte == 1);
assert(fourthByte == 1);
// Ensure the token is [0, 69]
ubyte fifthByte = encoded[4], sixthByte = encoded[5];
assert(fifthByte == 0);
assert(sixthByte == 69);
// FIXME: Ensure options
// Ensure the payload marker is here
ubyte seventhByte = encoded[6];
assert(seventhByte == PAYLOAD_MARKER);
// Ensure the payload is [255, 254]
// FIXME: Offset because of options later
ubyte eighthByte = encoded[7], ninthByte = encoded[8];
assert(eighthByte == 255);
assert(ninthByte == 254);
}
/**
* Decoding tests
*
* These tests take a byte array of an encoded
* CoAP packet and then decodes it into a new
* `CoapPacket` object
*/
unittest
{
// Version: 1 | Type: RESET (3) : TLK: 0
// Code: 2 (POST) | MID: 257
ubyte[] packetData = [112, 2, 1, 1];
CoapPacket packet = CoapPacket.fromBytes(packetData);
assert(packet.getVersion() == 1);
assert(packet.getType() == MessageType.RESET);
assert(packet.getTokenLength() == 0);
assert(packet.getCode() == Code.POST);
// TODO: Add message ID check + token check
assert(packet.getMessageId() == 257);
}
unittest
{
writeln("Begin big coap test (lekker real life)\n\n");
ubyte[] testingIn = [
0x41, 0x02, 0xcd, 0x47, 0x45, 0x3d,
0x03, 0x31, 0x30, 0x30, 0x2e, 0x36, 0x34, 0x2e,
0x30, 0x2e, 0x31, 0x32, 0x3a, 0x35,
0x36, 0x38, 0x33, 0x92, 0x27, 0x11, 0xe1, 0xfc,
0xd0, 0x01, 0x21, 0x10, 0x21, 0x01,
0xff, 0xc0, 0x01, 0xc1, 0x00, 0x0f, 0x00, 0x00,
0x28, 0x00, 0x00, 0xff, 0x02, 0x00
];
CoapPacket packet = CoapPacket.fromBytes(testingIn);
writeln(packet);
CoapOption[] expectedOptions = [
CoapOption(3, [0x31, 0x30, 0x30, 0x2e, 0x36, 0x34, 0x2e, 0x30, 0x2e, 0x31, 0x32, 0x3a, 0x35, 0x36, 0x38, 0x33]),
CoapOption(12, [0x27, 0x11]),
CoapOption(65001, [0x01]),
CoapOption(65003, [0x10]),
CoapOption(65005, [0x01])
];
CoapOption[] options = packet.getOptions();
assert(expectedOptions.length == options.length);
writeln(options[0]);
writeln(expectedOptions[0]);
assert(options[0] == expectedOptions[0]);
assert(options[1] == expectedOptions[1]);
assert(options[2] == expectedOptions[2]);
assert(options[3] == expectedOptions[3]);
assert(options[4] == expectedOptions[4]);
}
// unittest
// {
// writeln("Big another coap test (ALSO REAL LIFE FR ONGOD)\n\n");
// ubyte[] testingIn = [
// 0x61, 0x41, 0xa4, 0xdc, 0x45, 0xc2, 0x27, 0x11, 0xb1, 0x0e, 0xe1,
// 0xfc, 0xc5, 0x01, 0x21, 0x10, 0x21, 0x01, 0xff, 0xc4, 0x01, 0xc1,
// 0x00, 0x01, 0x46, 0x02, 0x04, 0x12, 0x00, 0x0f, 0x11, 0x03, 0x09,
// 0x06, 0x00, 0x00, 0x28, 0x00, 0x00, 0xff, 0x02, 0x02, 0x01, 0x0b,
// 0x02, 0x03, 0x0f, 0x01, 0x16, 0x01, 0x00, 0x02, 0x03, 0x0f, 0x02, 0x16, 0x01, 0x00, 0x02, 0x03, 0x0f, 0x03, 0x16, 0x01, 0x00, 0x02, 0x03, 0x0f, 0x04, 0x16, 0x01, 0x00, 0x02, 0x03, 0x0f, 0x05, 0x16, 0x01, 0x00, 0x02, 0x03, 0x0f, 0x06, 0x16, 0x01, 0x00, 0x02, 0x03, 0x0f, 0x07, 0x16, 0x00, 0x00, 0x02, 0x03, 0x0f, 0x08, 0x16, 0x01, 0x00, 0x02, 0x03, 0x0f, 0x09, 0x16, 0x01, 0x00, 0x02, 0x03, 0x0f, 0x0a, 0x16, 0x01, 0x00, 0x02, 0x03, 0x0f, 0x0b, 0x16, 0x01, 0x00, 0x01, 0x06, 0x02, 0x02, 0x0f, 0x01, 0x16, 0x00, 0x02, 0x02, 0x0f, 0x02, 0x16, 0x00, 0x02, 0x02, 0x0f, 0x03, 0x16, 0x00, 0x02, 0x02, 0x0f, 0x04, 0x16, 0x00, 0x02, 0x02, 0x0f, 0x05, 0x16, 0x00, 0x02, 0x02, 0x0f, 0x06, 0x16, 0x00, 0x02, 0x04, 0x12, 0x00, 0x01, 0x11, 0x00, 0x09, 0x06, 0x01, 0x01, 0x00, 0x00, 0x01, 0xff, 0x02, 0x02, 0x01, 0x02, 0x02, 0x03, 0x0f, 0x01, 0x16, 0x01, 0x00, 0x02, 0x03, 0x0f, 0x02, 0x16, 0x01, 0x00, 0x01, 0x00, 0x02, 0x04, 0x12, 0x00, 0x01, 0x11, 0x00, 0x09, 0x06, 0x01, 0x01, 0x00, 0x00, 0x02, 0xff, 0x02, 0x02, 0x01, 0x02, 0x02, 0x03, 0x0f, 0x01, 0x16, 0x01, 0x00, 0x02, 0x03, 0x0f, 0x02, 0x16, 0x01, 0x00, 0x01, 0x00, 0x02, 0x04, 0x12, 0x00, 0x01, 0x11, 0x00, 0x09, 0x06, 0x01, 0x01, 0x00, 0x00, 0x03, 0xff, 0x02, 0x02, 0x01, 0x02, 0x02, 0x03, 0x0f, 0x01, 0x16, 0x01, 0x00, 0x02, 0x03, 0x0f, 0x02, 0x16, 0x01, 0x00, 0x01, 0x00, 0x02, 0x04, 0x12, 0x00, 0x01, 0x11, 0x00, 0x09, 0x06, 0x01, 0x01, 0x60, 0x01, 0x00, 0xff, 0x02, 0x02, 0x01, 0x02, 0x02, 0x03, 0x0f, 0x01, 0x16, 0x01, 0x00, 0x02, 0x03, 0x0f, 0x02, 0x16, 0x01, 0x00, 0x01, 0x00, 0x02, 0x04, 0x12, 0x00, 0x01, 0x11, 0x00, 0x09, 0x06, 0x01, 0x01, 0x00, 0x00, 0x00, 0xff, 0x02, 0x02, 0x01, 0x02, 0x02, 0x03, 0x0f, 0x01, 0x16, 0x01, 0x00, 0x02, 0x03, 0x0f, 0x02, 0x16, 0x01, 0x00, 0x01, 0x00, 0x02, 0x04, 0x12, 0x00, 0x01, 0x11, 0x00, 0x09, 0x06, 0x01, 0x01, 0x60, 0x36, 0x01, 0xff, 0x02, 0x02, 0x01, 0x02, 0x02, 0x03, 0x0f, 0x01, 0x16, 0x01, 0x00, 0x02, 0x03, 0x0f, 0x02, 0x16, 0x01, 0x00, 0x01, 0x00, 0x02, 0x04, 0x12, 0x00, 0x01, 0x11, 0x00, 0x09, 0x06, 0x01, 0x01, 0x60, 0x01, 0x01, 0xff, 0x02, 0x02, 0x01, 0x02, 0x02, 0x03, 0x0f, 0x01, 0x16, 0x01, 0x00, 0x02, 0x03, 0x0f, 0x02, 0x16, 0x01, 0x00, 0x01, 0x00, 0x02, 0x04, 0x12, 0x00, 0x01, 0x11, 0x00, 0x09, 0x06, 0x01, 0x01, 0x60, 0x01, 0x02, 0xff, 0x02, 0x02, 0x01, 0x02, 0x02, 0x03, 0x0f, 0x01, 0x16, 0x01, 0x00, 0x02, 0x03, 0x0f, 0x02, 0x16, 0x01, 0x00, 0x01, 0x00, 0x02, 0x04, 0x12, 0x00, 0x01, 0x11, 0x00, 0x09, 0x06, 0x01, 0x01, 0x00, 0x00, 0x05, 0xff, 0x02, 0x02, 0x01, 0x02, 0x02, 0x03, 0x0f, 0x01, 0x16, 0x01, 0x00, 0x02, 0x03, 0x0f, 0x02, 0x16, 0x01, 0x00, 0x01, 0x00, 0x02, 0x04, 0x12, 0x00, 0x01, 0x11, 0x00, 0x09, 0x06, 0x01, 0x01, 0x00, 0x02, 0x00, 0x80, 0x02, 0x02, 0x01, 0x02, 0x02, 0x03, 0x0f, 0x01, 0x16, 0x01, 0x00, 0x02, 0x03, 0x0f, 0x02, 0x16, 0x01, 0x00, 0x01, 0x00, 0x02, 0x04, 0x12, 0x00, 0x01, 0x11, 0x00, 0x09, 0x06, 0x01, 0x01, 0x00, 0x02, 0x00, 0xff, 0x02, 0x02, 0x01, 0x02, 0x02, 0x03, 0x0f, 0x01, 0x16, 0x01, 0x00, 0x02, 0x03, 0x0f, 0x02, 0x16, 0x01, 0x00, 0x01, 0x00, 0x02, 0x04, 0x12, 0x00, 0x01, 0x11, 0x00, 0x09, 0x06, 0x01, 0x01, 0x00, 0x02, 0x02, 0x80, 0x02, 0x02, 0x01, 0x02, 0x02, 0x03, 0x0f, 0x01, 0x16, 0x01, 0x00, 0x02, 0x03, 0x0f, 0x02, 0x16, 0x01, 0x00, 0x01, 0x00, 0x02, 0x04, 0x12, 0x00, 0x01, 0x11, 0x00, 0x09, 0x06, 0x01, 0x01, 0x60, 0x3a, 0x03, 0xff, 0x02, 0x02, 0x01, 0x02, 0x02, 0x03, 0x0f, 0x01, 0x16, 0x01, 0x00, 0x02, 0x03, 0x0f, 0x02, 0x16, 0x01, 0x00, 0x01, 0x00, 0x02, 0x04, 0x12, 0x00, 0x01, 0x11, 0x00, 0x09, 0x06, 0x01, 0x01, 0x60, 0x3a, 0x0d, 0xff, 0x02, 0x02, 0x01, 0x02, 0x02, 0x03, 0x0f, 0x01, 0x16, 0x01, 0x00, 0x02, 0x03, 0x0f, 0x02, 0x16, 0x01, 0x00, 0x01, 0x00, 0x02, 0x04, 0x12, 0x00, 0x01, 0x11, 0x00, 0x09, 0x06, 0x01, 0x01, 0x60, 0x3a, 0x0e, 0xff, 0x02, 0x02, 0x01, 0x02, 0x02, 0x03, 0x0f, 0x01, 0x16, 0x01, 0x00, 0x02, 0x03, 0x0f, 0x02, 0x16, 0x01, 0x00, 0x01, 0x00, 0x02, 0x04, 0x12, 0x00, 0x01, 0x11, 0x00, 0x09, 0x06, 0x01, 0x01, 0x60, 0x3a, 0x0f, 0xff, 0x02, 0x02, 0x01, 0x02, 0x02, 0x03, 0x0f, 0x01, 0x16, 0x01, 0x00, 0x02, 0x03, 0x0f, 0x02, 0x16, 0x01, 0x00, 0x01, 0x00, 0x02, 0x04, 0x12, 0x00, 0x01, 0x11, 0x00, 0x09, 0x06, 0x01, 0x01, 0x60, 0x3a, 0x13, 0xff, 0x02, 0x02, 0x01, 0x02, 0x02, 0x03, 0x0f, 0x01, 0x16, 0x01, 0x00, 0x02, 0x03, 0x0f, 0x02, 0x16, 0x01, 0x00, 0x01, 0x00, 0x02, 0x04, 0x12, 0x00, 0x01, 0x11, 0x00, 0x09, 0x06, 0x01, 0x01, 0x00, 0x00, 0x04, 0xff, 0x02, 0x02, 0x01, 0x02, 0x02, 0x03, 0x0f, 0x01, 0x16, 0x01, 0x00, 0x02, 0x03, 0x0f, 0x02, 0x16, 0x01, 0x00, 0x01, 0x00, 0x02, 0x04, 0x12, 0x00, 0x01, 0x11, 0x00, 0x09, 0x06, 0x01, 0x01, 0x60, 0x3a, 0x14, 0xff, 0x02, 0x02, 0x01, 0x02, 0x02, 0x03, 0x0f, 0x01, 0x16, 0x01, 0x00, 0x02, 0x03, 0x0f, 0x02, 0x16, 0x01, 0x00, 0x01, 0x00, 0x02, 0x04, 0x12, 0x00, 0x01, 0x11, 0x00, 0x09, 0x06, 0x01, 0x01, 0x60, 0x3a, 0x04, 0xff, 0x02, 0x02, 0x01, 0x02, 0x02, 0x03, 0x0f, 0x01, 0x16, 0x01, 0x00, 0x02, 0x03, 0x0f, 0x02, 0x16, 0x01, 0x00, 0x01, 0x00, 0x02, 0x04, 0x12, 0x00, 0x01, 0x11, 0x00, 0x09, 0x06, 0x01, 0x01, 0x60, 0x3a, 0x10, 0xff, 0x02, 0x02, 0x01, 0x02, 0x02, 0x03, 0x0f, 0x01, 0x16, 0x01, 0x00, 0x02, 0x03, 0x0f, 0x02, 0x16, 0x01, 0x00, 0x01, 0x00, 0x02, 0x04, 0x12, 0x00, 0x01, 0x11, 0x00, 0x09, 0x06, 0x01, 0x01, 0x86, 0x00, 0x1e, 0xff, 0x02, 0x02, 0x01, 0x02, 0x02, 0x03, 0x0f, 0x01, 0x16, 0x01, 0x00, 0x02, 0x03, 0x0f, 0x02, 0x16, 0x03, 0x00, 0x01, 0x00, 0x02, 0x04, 0x12, 0x00, 0x01, 0x11, 0x00, 0x09, 0x06, 0x00, 0x02, 0x2b, 0x01, 0x01, 0xff, 0x02, 0x02, 0x01, 0x02, 0x02, 0x03, 0x0f, 0x01, 0x16, 0x01, 0x00, 0x02, 0x03, 0x0f, 0x02, 0x16, 0x01, 0x00, 0x01, 0x00, 0x02, 0x04, 0x12, 0x00, 0x01, 0x11, 0x00, 0x09, 0x06, 0x00, 0x02, 0x2b, 0x01, 0x08, 0xff, 0x02, 0x02, 0x01, 0x02, 0x02, 0x03, 0x0f, 0x01, 0x16, 0x01, 0x00, 0x02, 0x03, 0x0f, 0x02, 0x16, 0x01, 0x00, 0x01, 0x00, 0x02, 0x04, 0x12, 0x00, 0x01, 0x11, 0x00, 0x09, 0x06, 0x00, 0x02, 0x2b, 0x01, 0x09, 0xff, 0x02, 0x02, 0x01, 0x02, 0x02, 0x03, 0x0f, 0x01, 0x16, 0x01, 0x00, 0x02, 0x03, 0x0f, 0x02, 0x16, 0x01, 0x00, 0x01, 0x00, 0x02, 0x04, 0x12, 0x00, 0x01, 0x11, 0x00, 0x09, 0x06
// ];
// CoapPacket packet = CoapPacket.fromBytes(testingIn);
// writeln(packet);
// }
/**
* Tests the minimum size required for a packet
* (Negative case)
*/
unittest
{
ubyte[] testingIn = [];
try
{
CoapPacket packet = CoapPacket.fromBytes(testingIn);
assert(false);
}
catch(CoapException e)
{
assert(true);
}
testingIn = [ 0x41, 0x02, 0xcd];
try
{
CoapPacket packet = CoapPacket.fromBytes(testingIn);
assert(false);
}
catch(CoapException e)
{
assert(true);
}
}
/**
* Tests the minimum size required for a packet
* (Positive case)
*/
unittest
{
// FIXME: Actually make a better example
// ubyte[] testingIn = [0x41, 0x02, 0xcd, 0x47];
// try
// {
// CoapPacket packet = CoapPacket.fromBytes(testingIn);
// // TODO: Test
// assert(true);
// }
// catch(CoapException e)
// {
// assert(false);
// }
}