Skip to content

Commit

Permalink
Merge 761ddb2 into a23017a
Browse files Browse the repository at this point in the history
  • Loading branch information
deavmi committed Oct 1, 2023
2 parents a23017a + 761ddb2 commit ddbb6b9
Show file tree
Hide file tree
Showing 16 changed files with 1,370 additions and 46 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,4 @@ docs/
*.lst
source/tristanable/queue.d
dub.selections.json
tristanable-test-library
18 changes: 9 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,24 +1,24 @@
![](https://code.dlang.org/packages/tristanable/logo?s=5ef1c9f1250f57dd4c37efbf)
![](branding/logo_small.png)

tristanable
===========

[![D](https://github.com/deavmi/tristanable/actions/workflows/d.yml/badge.svg)](https://github.com/deavmi/tristanable/actions/workflows/d.yml) ![DUB](https://img.shields.io/dub/v/tristanable?color=%23c10000ff%20&style=flat-square) ![DUB](https://img.shields.io/dub/dt/tristanable?style=flat-square) ![DUB](https://img.shields.io/dub/l/tristanable?style=flat-square) [![Coverage Status](https://coveralls.io/repos/github/deavmi/tristanable/badge.svg?branch=master)](https://coveralls.io/github/deavmi/tristanable?branch=master)


**Tristanable** is a library for D-based libraries and applications that need a way to receive variable-length messages of different types (via a `Socket`) and place these messages into their own resepctively tagged queues indicated by their _"type"_ or `id`.
**Tristanable** is a library for D-based libraries and applications that need a way to receive variable-length messages of different types (via a `Socket`) and place these messages into their own respectively tagged queues indicated by their _"type"_ or `id`.

## What problems does it solve?

### Human example

Say now you made a request to a server with a tag `1` and expect a reply with that same tag `1`. Now, for a moment, think about what would happen in a tagless system. You would be expecting a reply, say now the weather report for your area, but what if the server has another thread that writes an instant messenger notification to the server's socket before the weather message is sent? Now you will inetrpret those bytes as if they were a weather message.
Say now you made a request to a server with a tag `1` and expect a reply with that same tag `1`. Now, for a moment, think about what would happen in a tagless system. You would be expecting a reply, say now the weather report for your area, but what if the server has another thread that writes an instant messenger notification to the server's socket before the weather message is sent? Now you will interpret those bytes as if they were a weather message.

Tristanable provides a way for you to receive the "IM notification first" but block and dequeue (when it arrives in the queue) for the "weather report". Irresepctoive of wether (no pun intended) the weather report arrives before the "IM notification" or after.
Tristanable provides a way for you to receive the "IM notification first" but block and dequeue (when it arrives in the queue) for the "weather report". Irrespective of wether (no pun intended) the weather report arrives before the "IM notification" or after.

### Code example

If we wanted to implement the following we would do the following. One note is that instead of waiting on messages of a specific _"type"_ (or rather **tag**), tristanable provides not just a one-message lengthb uffer per tag but infact a full queue per tag, meaning any received message with tag `1` will be enqueued and not dropped after the first message of type `1` is buffered.
If we wanted to implement the following we would do the following. One note is that instead of waiting on messages of a specific _"type"_ (or rather **tag**), tristanable provides not just a one-message length buffer per tag but in fact a full queue per tag, meaning any received message with tag `1` will be enqueued and not dropped after the first message of type `1` is buffered.

```d
import tristanable.manager;
Expand All @@ -42,7 +42,7 @@ manager.addQueue(instantNotification);
QueueItem message = weatherQueue.dequeue();
```

Surely, there must be some sort of encoding mechanism too? The messages afterall need to be encoded. **No problem!**, we have that sorted:
Surely, there must be some sort of encoding mechanism too? The messages after all need to be encoded. **No problem!**, we have that sorted:

```d
import tristanable.encoding;
Expand All @@ -58,7 +58,7 @@ DataMessage tristanEncoded = new DataMessage(tag, data);
socket.send(encodeForSend(tristanEncoded));
```

And let tristanable handle it! We even handle the message lengths and everything using another great project [bformat](http://deavmi.assigned.network/projects/bformat).
And let tristanable handle it! We even handle the message lengths and everything using another great project [bformat](https://deavmi.assigned.network/projects/bformat).

## Format

Expand All @@ -67,8 +67,8 @@ And let tristanable handle it! We even handle the message lengths and everything
```

## Using tristanable in your D project
You can easily add the library (source-based) to your project by running the following command in your
project's root:

You can easily add the library (source-based) to your project by running the following command in your project's root:

```bash
dub add tristanable
Expand Down
Binary file added branding/logo_base.xcf
Binary file not shown.
Binary file added branding/logo_small.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added branding/logo_small.xcf
Binary file not shown.
5 changes: 3 additions & 2 deletions dub.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@
"authors": [
"Tristan B. Velloza Kildaire"
],
"homepage": "https://deavmi.assigned.network/projects/tristanable",
"copyright": "Copyright © 2023, Tristan B. Kildaire",
"dependencies": {
"libsnooze": "0.3.3"
"bformat": ">=4.1.1",
"libsnooze": ">=1.3.0-beta"
},
"description": "Tristanable network message queuing framework",
"homepage": "https://deavmi.assigned.network/projects/tristanable",
"license": "LGPL-3.0",
"name": "tristanable",
"targetType": "library"
Expand Down
221 changes: 221 additions & 0 deletions source/tristanable/encoding.d
Original file line number Diff line number Diff line change
@@ -0,0 +1,221 @@
/**
* Encoding/decoding of the tristanable format
*/
module tristanable.encoding;

import std.conv : to;

/**
* 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;

/* If on little endian then dump direct */
version(LittleEndian)
{
decodedTag = *cast(ulong*)encodedMessage.ptr;
}
/* If on big endian then reverse received 8 bytes */
else version(BigEndian)
{
/* Base of our tag */
byte* tagHighPtr = cast(byte*)decodedTag.ptr;

*(tagHighPtr+0) = encodedMessage[7];
*(tagHighPtr+1) = encodedMessage[6];
*(tagHighPtr+2) = encodedMessage[5];
*(tagHighPtr+3) = encodedMessage[4];
*(tagHighPtr+4) = encodedMessage[3];
*(tagHighPtr+5) = encodedMessage[2];
*(tagHighPtr+6) = encodedMessage[1];
*(tagHighPtr+7) = encodedMessage[0];
}
/* Blessed is the fruit of thy womb Jesus, hail Mary, mother of God, pray for our sinners - now and at the hour of our death - Amen */
else
{
pragma(msg, "Not too sure about tha 'ey 😳️");
}

/* 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());
}
73 changes: 73 additions & 0 deletions source/tristanable/exceptions.d
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
/**
* Error handling type definitions
*/
module tristanable.exceptions;

import std.conv : to;

/**
* The type of sub-error of the `TristanableException`
*/
public enum ErrorType
{
/**
* If the requested queue could not be found
*/
QUEUE_NOT_FOUND,

/**
* If the queue wanting to be registered has already
* been registered under the same tag
*/
QUEUE_ALREADY_EXISTS,

/**
* If no default queue is configured
*/
NO_DEFAULT_QUEUE,

/**
* The blocking call to `dequeue()`, somehow, failed
*/
DEQUEUE_FAILED,

/**
* The call to `enqueue()`, somehow, failed
*/
ENQUEUE_FAILED
}

/**
* Any sort of error that occurs during runtime of the tristanable
* engine
*/
public class TristanableException : Exception
{
/**
* The sub-error type
*/
private ErrorType err;

/**
* Constructs a new `TristanableException` with the provided
* sub-error type
*
* Params:
* err = the `ErrorType`
*/
this(ErrorType err)
{
super(this.classinfo.name~": "~to!(string)(err));
this.err = err;
}

/**
* Retrieve the sub-error type
*
* Returns: the sub-error type as a `ErrorType`
*/
public ErrorType getError()
{
return err;
}
}
15 changes: 0 additions & 15 deletions source/tristanable/manager.d

This file was deleted.

Loading

0 comments on commit ddbb6b9

Please sign in to comment.