1 module doap.client.request;
2 
3 import doap.client.client : CoapClient;
4 import doap.protocol;
5 import doap.client.exceptions;
6 import core.time : Duration;
7 import std.datetime.stopwatch : StopWatch, AutoStart;
8 
9 /**
10  * Represents a request that has been made. This is
11  * normally stored inside the `CoapClient` and used
12  * to find matching responses that come through in
13  * the messaging layer.
14  *
15  * It is composed of the `token` and the future
16  * which was created. Therefore when the messaging
17  * layer receives a new CoAP packet it can then try
18  * match it to one of these requests, in the event
19  * it finds a match it can retrieve the future, place
20  * the received `CoapPacket` into it and then wake up
21  * anyone doing a blocking `get()` on it.
22  */
23 package class CoapRequest
24 {
25     /** 
26      * The original packet (to be able to access the token
27      * such that we can match it up with a respnse)
28      */
29     private CoapPacket requestPacket;
30 
31     /** 
32      * The future which we can fill up with the
33      * response and then wake up the receiver
34      */
35     package CoapRequestFuture future;
36 
37     /**
38      * Stopwatch for counting elapsed time
39      */
40     private StopWatch timer;
41 
42     /** 
43      * Constructs a new request
44      *
45      * Params:
46      *   requestPacket = the actual request
47      *   future = the `CoapRequestFuture` to wake up
48      * on data arrival
49      */
50     this(CoapPacket requestPacket, CoapRequestFuture future)
51     {
52         this.requestPacket = requestPacket;
53         this.future = future;
54         this.timer = StopWatch(AutoStart.no);
55     }
56 
57     /** 
58      * Gets the original request made
59      *
60      * Returns: the request packet
61      */
62     public CoapPacket getRequestPacket()
63     {
64         return this.requestPacket;
65     }
66 
67     /** 
68      * Gets the token from the original request
69      * that was made
70      *
71      * Returns: the token
72      */
73     public ubyte[] getToken()
74     {
75         return this.requestPacket.getToken();
76     }
77 
78     /** 
79      * Gets the message ID from the original
80      * request that was made
81      *
82      * Returns: the message id
83      */
84     public ushort getMid()
85     {
86         return this.requestPacket.getMessageId();
87     }
88 
89     /** 
90      * Starts the timer
91      */
92     package void startTime()
93     {
94         timer.start();
95     }
96 
97     /** 
98      * Checks if this request has expired
99      * according to the given timeout
100      * threshold
101      *
102      * If timed out then the timer
103      * restarts.
104      *
105      * Returns: `true` if timed out,
106      * `false` if not
107      */
108     package bool hasTimedOut(Duration timeoutThreshold)
109     {
110         // Check if the threshold has been reached
111         if(timer.peek() >= timeoutThreshold)
112         {
113             timer.reset();
114             return true;
115         }
116         else
117         {
118             return false;
119         }
120     }
121 
122     /** 
123      * Returns the elapsed time of this request
124      * thus far
125      *
126      * Returns: the elapsed time
127      */
128     public Duration getElapsedTime()
129     {
130         return timer.peek();
131     }
132 }
133 
134 /** 
135  * This allows one to build up a new
136  * CoAP request of some form in a 
137  * method-call-by-method-call manner.
138  *
139  * In order to instantiate one of these
140  * please do so via the `CoapClient`.
141  */
142 package class CoapRequestBuilder
143 {
144     /** 
145      * The associated client for
146      * making the actual request
147      */
148     private CoapClient client;
149 
150     /** 
151      * The request code
152      */
153     package Code requestCode;
154 
155     /** 
156      * The message type
157      */
158     package MessageType type;
159 
160     /** 
161      * The payload
162      */
163     package ubyte[] pyld;
164 
165     /** 
166      * The token
167      */
168     package ubyte[] tkn;
169 
170     /** 
171      * Constructs a new builder
172      *
173      * This requires a working `CoapClient`
174      * such that finalization can be done
175      * on its side
176      *
177      * Params:
178      *   client = the client to associate with
179      */
180     this(CoapClient client)
181     {
182         this.client = client;
183         this.requestCode = Code.GET;
184         this.type = MessageType.CONFIRMABLE;
185     }
186 
187     /** 
188      * Set the payload for this request
189      *
190      * Params:
191      *   payload = the payload
192      * Returns: this builder
193      */
194     public CoapRequestBuilder payload(ubyte[] payload)
195     {
196         this.pyld = payload;
197         return this;
198     }
199 
200     /** 
201      * Set the token to use for this request
202      *
203      * Params:
204      *   tkn = the token
205      * Returns: this builder
206      * Throws:
207      *      CoapClientException = invalid token
208      * length
209      */
210     public CoapRequestBuilder token(ubyte[] tkn)
211     {
212         if(tkn.length > 8)
213         {
214             throw new CoapClientException("The token cannot be more than 8 bytes");
215         }
216 
217         this.tkn = tkn;
218         return this;
219     }
220 
221     /** 
222      * Sets this message as confirmable
223      *
224      * Returns: this builder
225      */
226     public CoapRequestBuilder con()
227     {
228         this.type = MessageType.CONFIRMABLE;
229         return this;
230     }
231 
232     /** 
233      * Sets this message as non-confirmable
234      *
235      * Returns: this builder
236      */
237     public CoapRequestBuilder non()
238     {
239         this.type = MessageType.NON_CONFIRMABLE;
240         return this;
241     }
242 
243     /** 
244      * Build the request, set it in flight
245      * and return the future handle to it.
246      *
247      * This sets the request code to POST.
248      *
249      * Returns: the `CoapRequestFuture` for
250      * this request
251      */
252     public CoapRequestFuture post()
253     {
254         // Set the request code to POST
255         this.requestCode = Code.POST;
256 
257         // Register the request via the client
258         // ... and obtain the future
259         return this.client.doRequest(this);
260     }
261 
262     /** 
263      * Build the request, set it in flight
264      * and return the future handle to it.
265      *
266      * This sets the request code to GET.
267      *
268      * Returns: the `CoapRequestFuture` for
269      * this request
270      */
271     public CoapRequestFuture get()
272     {
273         // Set the request code to GET
274         this.requestCode = Code.GET;
275 
276         // Register the request via the client
277         // ... and obtain the future
278         return this.client.doRequest(this);
279     }
280 }
281 
282 import core.sync.mutex : Mutex;
283 import core.sync.condition : Condition;
284 
285 
286 /** 
287  * The state of a `CoapRequestFuture`
288  */
289 public enum RequestState
290 {
291     /** 
292      * The future has been created
293      */
294     CREATED,
295 
296     /** 
297      * The future has completed
298      * successfully
299      */
300     COMPLETED,
301 
302     /** 
303      * The future was cancelled
304      */
305     CANCELLED,
306 
307     /** 
308      * The future timed out
309      */
310     TIMEDOUT
311 }
312 
313 /** 
314  * This is returned to the user when he
315  * does a finalizing call on a `CoapRequestBuilder`
316  * such as calling `post()`. The client
317  * will then creating the underlying request
318  * such that the messaging layer can match
319  * a future response to it. The client then
320  * sends the CoAP packet and then returns
321  * this so-called "future" as a handle
322  * to it which can be waited on via
323  * a call to `get()`.
324  */
325 public class CoapRequestFuture
326 {
327     /** 
328      * The received response
329      *
330      * This is filled in by the
331      * messaging layer.
332      */
333     private CoapPacket response; // TODO: Volatility?
334 
335     /** 
336      * State of the future
337      */
338     private RequestState state; // TODO: Volatility?
339 
340     /** 
341      * Mutex (for the condition to use)
342      */
343     private Mutex mutex;
344 
345     /** 
346      * Condition variable
347      *
348      * Used for doing `wait()`/`notify()`
349      * such that the messaging layer can
350      * wake up a sleeping/blocking `get()`
351      */
352     private Condition condition;
353 
354     /** 
355      * Constructs a new `CoapRequestFuture`
356      */
357     package this()
358     {
359         this.state = RequestState.CREATED;
360         this.mutex = new Mutex();
361         this.condition = new Condition(mutex);
362     }
363 
364     /** 
365      * Called when a matching (by token) CoAP packet
366      * is received. This will store the received response
367      * and also wake up anyone blocking on the `get()`
368      * call to this future.
369      *
370      * Params:
371      *   response = the `CoapPacket` response
372      */
373     package void receiveWake(CoapPacket response)
374     {
375         // Set the received response
376         this.response = response;
377 
378         // Set completion state
379         this.state = RequestState.COMPLETED;
380 
381         // Wake up the sleepers
382         this.condition.notifyAll();
383     }
384 
385     /** 
386      * Cancels this future such that
387      * all calls to `get()` will
388      * unblock and throw an exception
389      */
390     package void cancel()
391     {
392         // Set cancelled state
393         this.state = RequestState.CANCELLED;
394 
395         // Wake up the sleepers
396         this.condition.notifyAll();
397     }
398 
399     /** 
400      * Blocks until the response is received
401      *
402      * Returns: the response as a `CoapPacket`
403      * Throws:
404      *     CoapClientException on cancelled request
405      */
406     public CoapPacket get()
407     {
408         // We can only wait on a condition if we
409         // ... first have a-hold of the lock
410         this.mutex.lock();
411 
412         // Await a response
413         this.condition.wait();
414 
415         // Upon waking up release lock
416         this.mutex.unlock();
417 
418         // If successfully completed
419         if(this.state == RequestState.COMPLETED)
420         {
421             return this.response;
422         }
423         // On error
424         else
425         {
426             throw new CoapClientException("Request future cancelled");
427         }   
428     }
429 
430     /** 
431      * Blocks until the response is received
432      * but will unbllock if the timeout given
433      * is exceeded
434      *
435      * Returns: the response as a `CoapPacket`
436      * Throws:
437      *     RequestTimeoutException on the
438      * future request timing out
439      *     CoapClientException on cancellation
440      * of the request
441      */
442     public CoapPacket get(Duration timeout)
443     {
444         // We can only wait on a condition if we
445         // ... first have a-hold of the lock
446         this.mutex.lock();
447 
448         scope(exit)
449         {
450             // Unlock the lock (either from successfully
451             // ... waiting or timing out)
452             this.mutex.unlock();
453         }
454 
455         // Await a response
456         if(this.condition.wait(timeout))
457         {
458             // If successfully completed
459             if(this.state == RequestState.COMPLETED)
460             {
461                 return this.response;
462             }
463             // On error
464             else
465             {
466                 throw new CoapClientException("Request future cancelled");
467             }
468         }
469         else
470         {
471             this.state = RequestState.TIMEDOUT;
472             throw new RequestTimeoutException(this, timeout);
473         }
474     }
475     
476     /** 
477      * Returns the state of this future
478      *
479      * Returns: the state
480      */
481     public RequestState getState()
482     {
483         return this.state;
484     }
485 }