Merge pull request #8 from deavmi/feature/exchange_lifetime

Implement EXCHANGE_LIFETIME for message ID generation
This commit is contained in:
Tristan B. Velloza Kildaire 2023-09-26 19:33:32 +02:00 committed by GitHub
commit 481afbb194
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 149 additions and 27 deletions

View File

@ -8,6 +8,8 @@ import core.sync.mutex : Mutex;
import core.sync.condition : Condition;
import std.container.slist : SList;
import core.thread : dur, Duration, Thread;
import std.datetime.stopwatch : StopWatch, AutoStart;
import doap.utils : findNextFree;
/**
* A CoAP client
@ -40,11 +42,15 @@ public class CoapClient
*/
private Mutex requestsLock;
/**
* Rolling Message ID
/**
* Message IDs and lifetime map
*/
private ushort rollingMid;
private Mutex rollingLock;
private StopWatch[ushort] mids;
/**
* Lock for the above
*/
private Mutex midsLock;
/**
* Creates a new CoAP client to the
@ -61,32 +67,71 @@ public class CoapClient
this.messaging = new UDPMessaging(this); //UDP transport
this.requestsLock = new Mutex();
this.rollingMid = 0;
this.rollingLock = new Mutex();
this.midsLock = new Mutex();
init();
}
/**
* Maximum lifetime of a message ID before
* it is considered for re-use
*/
private Duration EXCHANGE_LIFETIME = dur!("msecs")(180);
/**
* Sets the exchange lifetime. In other words
* the duration of time that must pass before
* a message ID is considered free-for-use again
*
* Params:
* lifetime = the lifetime duration
*/
public void setExchangeLifetime(Duration lifetime)
{
this.EXCHANGE_LIFETIME = lifetime;
}
/**
* Generates a new message ID
*
* Returns: the next message id
*/
private final ushort newMid()
private final ushort newMid2()
{
ushort newValue;
// Lock rolling counter
this.rollingLock.lock();
this.midsLock.lock();
newValue = this.rollingMid;
this.rollingMid++;
scope(exit)
{
// Unlock rolling counter
this.midsLock.unlock();
}
// Unlock rolling counter
this.rollingLock.unlock();
// Message IDs which are in use
ushort[] inUse;
return newValue;
foreach(ushort occupied; this.mids.keys())
{
// Peek the value of the stopwatch
if(this.mids[occupied].peek() >= EXCHANGE_LIFETIME)
{
// It's expired, so we can use it (first reset the time)
this.mids[occupied].reset();
return occupied;
}
else
{
inUse ~= occupied;
}
}
// If none was available for re-use then find next available
// ... free and use that (also don't forget to register it)
ushort newMid = findNextFree(inUse);
this.mids[newMid] = StopWatch(AutoStart.yes);
return newMid;
}
/**
@ -172,7 +217,7 @@ public class CoapClient
requestPacket.setCode(requestBuilder.requestCode);
requestPacket.setPayload(requestBuilder.pyld);
requestPacket.setToken(requestBuilder.tkn);
requestPacket.setMessageId(newMid());
requestPacket.setMessageId(newMid2());
// Create the future
CoapRequestFuture future = new CoapRequestFuture();
@ -319,7 +364,15 @@ version(unittest)
/**
* Client testing
*
* Tests the rolling of the message id
* Tests the rolling of the message id,
* here I configure the `EXCHANGE_LIFETIME`
* to be a value high enough to not have
* them quickly expire.
*
* NOTE: In the future it may be calculated
* in relation to other variables and we may
* need a private method accessible here that
* can override it
*/
unittest
{
@ -332,6 +385,9 @@ unittest
.post();
// Set it to something high enough (TODO: Change this later)
client.setExchangeLifetime(dur!("msecs")(300));
writeln("Future start (first)");
CoapPacket response = future.get();
writeln("Future done (first)");

View File

@ -121,19 +121,85 @@ unittest
}
/**
* Checks if the given value is present in
* the given array
*
* Params:
* array = the array to check against
* value = the value to check prescence
* for
* Returns: `true` if present, `false`
* otherwise
*/
public bool isPresent(T)(T[] array, T value)
{
if(array.length == 0)
{
return false;
}
else
{
foreach(T cur; array)
{
if(cur == value)
{
return true;
}
}
return false;
}
}
/**
* Tests the `isPresent!(T)(T[], T)` function
*/
unittest
{
version(LittleEndian)
ubyte[] values = [1,2,3];
foreach(ubyte value; values)
{
ushort i = 1;
writeln("Pre-order: ", i);
ushort ordered = order(i, Order.BE);
writeln("Post-order: ", ordered);
assert(ordered == 256);
assert(isPresent(values, value));
}
else version(BigEndian)
assert(isPresent(values, 0) == false);
assert(isPresent(values, 5) == false);
}
/**
* Given an array of values this tries to find
* the next free value of which is NOT present
* within the given array
*
* Params:
* used = the array of values
* Returns: the free value
*/
public T findNextFree(T)(T[] used) if(__traits(isIntegral, T))
{
T found;
if(used.length == 0)
{
// TODO: Add this AND CI tests for it
return 0;
}
else
{
found = 0;
while(isPresent(used, found)) // FIXME: Constant loop if none available
{
found++;
}
return found;
}
}
/**
* Tests the `findNextFree!(T)(T[])` function
*/
unittest
{
ubyte[] values = [1,2,3];
ubyte free = findNextFree(values);
assert(isPresent(values, free) == false);
}