tristanable/source/tristanable/encoding.d

200 lines
4.9 KiB
D

/**
* Encoding/decoding of the tristanable format
*/
module tristanable.encoding;
import std.conv : to;
import niknaks.bits : bytesToIntegral, Order, order;
/**
* Represents a tagged message that has been decoded
* from its raw byte encoding, this is a tuple of
* a numeric tag and a byte array of payload data
*
* Also provides a static method to decode from such
* raw encoding and an instance method to do the reverse
*/
public final class TaggedMessage
{
/**
* This message's tag
*/
private ulong tag;
/**
* The payload
*/
private byte[] data;
/**
* Constructs a new TaggedMessage with the given tag and payload
*
* Params:
* tag = the tag to use
* data = the payload
*/
this(ulong tag, byte[] data)
{
this.tag = tag;
this.data = data;
}
/**
* Parameterless constructor used for decoder
*/
private this() {}
/**
* Decodes the wire-formatted tristanable bytes into an instance
* of TaggedMessage whereby the tag and data can be seperately
* accessed and manipulated
*
* Params:
* encodedMessage = the wire-format encoded bytes
* Returns: an instance of TaggedMessage
*/
public static TaggedMessage decode(byte[] encodedMessage)
{
/* The decoded message */
TaggedMessage decodedMessage = new TaggedMessage();
/* The decoded tag */
ulong decodedTag;
/* Take ulong-many bytes and only flip them to LE if not on LE host */
decodedTag = order(bytesToIntegral!(ushort)(cast(ubyte[])encodedMessage), Order.LE);
/* Set the tag */
decodedMessage.setTag(decodedTag);
/* Set the data *(9-th byte onwards) */
decodedMessage.setPayload(encodedMessage[8..$]);
return decodedMessage;
}
/**
* Encodes the tagged message into the tristanable
* wire format ready for transmission
*
* Returns: the encoded bytes
*/
public byte[] encode()
{
/* The encoded bytes */
byte[] encodedMessage;
/* If on little endian, then dump 64 bit as is - little endian */
version(LittleEndian)
{
/* Base (little first) of tag */
byte* basePtr = cast(byte*)&tag;
encodedMessage ~= *(basePtr+0);
encodedMessage ~= *(basePtr+1);
encodedMessage ~= *(basePtr+2);
encodedMessage ~= *(basePtr+3);
encodedMessage ~= *(basePtr+4);
encodedMessage ~= *(basePtr+5);
encodedMessage ~= *(basePtr+6);
encodedMessage ~= *(basePtr+7);
}
/* If on big endian, then traverse 64-bit number in reverse - and tack on */
else version(BigEndian)
{
/* Base (biggest first) of tag */
byte* highPtr = cast(byte*)&tag;
encodedMessage ~= *(highPtr+7);
encodedMessage ~= *(highPtr+6);
encodedMessage ~= *(highPtr+5);
encodedMessage ~= *(highPtr+4);
encodedMessage ~= *(highPtr+3);
encodedMessage ~= *(highPtr+2);
encodedMessage ~= *(highPtr+1);
encodedMessage ~= *(highPtr+0);
}
/* Hail marry, mother of God, pray for our sinners, now and at the our of our death Amen */
else
{
pragma(msg, "Not feeling scrumptious homeslice 😎️");
}
/* Tack on the data */
encodedMessage ~= data;
return encodedMessage;
}
/**
* Get the message's payload
*
* Returns: the payload
*/
public byte[] getPayload()
{
return data;
}
/**
* Get the message's tag
*
* Returns: the tag
*/
public ulong getTag()
{
return tag;
}
/**
* Set the message's payload
*
* Params:
* newPayload = the payload to use
*/
public void setPayload(byte[] newPayload)
{
this.data = newPayload;
}
/**
* Set the message's tag
*
* Params:
* newTag = the tag to use
*/
public void setTag(ulong newTag)
{
this.tag = newTag;
}
/**
* Returns a string representation of the TaggedMessage
*
* Returns: the string represenation
*/
public override string toString()
{
return "TMessage [Tag: "~to!(string)(tag)~", Payload: "~to!(string)(data)~"]";
}
}
/**
* Test encoding and decoding
*/
unittest
{
/* Setup testing data */
TaggedMessage testData = new TaggedMessage(420, [1,2,3]);
/* Encode */
byte[] encoded = testData.encode();
/* Decode */
TaggedMessage decoded = TaggedMessage.decode(encoded);
/* Now ensure that `decoded` == original `testData` */
assert(decoded.getTag() == testData.getTag);
assert(decoded.getPayload() == testData.getPayload());
}