This repository was archived by the owner on Oct 12, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 26
Expand file tree
/
Copy pathtlsio_arduino.c
More file actions
684 lines (628 loc) · 31.7 KB
/
tlsio_arduino.c
File metadata and controls
684 lines (628 loc) · 31.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
#include <stdlib.h>
#include <stddef.h>
#include <stdint.h>
#include <ctype.h>
#include <stdio.h>
#include <string.h>
#include <stdbool.h>
#include <time.h>
#include "tlsio_arduino.h"
#include "sslClient_arduino.h"
#include "azure_c_shared_utility/xio.h"
#include "azure_c_shared_utility/gballoc.h"
#include "azure_c_shared_utility/xlogging.h"
#include "azure_c_shared_utility/agenttime.h"
#include "azure_c_shared_utility/singlylinkedlist.h"
#include "azure_c_shared_utility/crt_abstractions.h"
#include "azure_c_shared_utility/tlsio_options.h"
#include "azure_c_shared_utility/strings.h"
#include "azure_c_shared_utility/tlsio.h"
typedef struct
{
unsigned char* bytes;
size_t size;
size_t unsent_size;
ON_SEND_COMPLETE on_send_complete;
void* callback_context;
} PENDING_TRANSMISSION;
#define MAX_VALID_PORT 0xffff
const char WEBSOCKET_HEADER_START[] = "GET /$iothub/websocket";
const char WEBSOCKET_HEADER_NO_CERT_PARAM[] = "?iothub-no-client-cert=true";
const size_t WEBSOCKET_HEADER_START_SIZE = sizeof(WEBSOCKET_HEADER_START) - 1;
const size_t WEBSOCKET_HEADER_NO_CERT_PARAM_SIZE = sizeof(WEBSOCKET_HEADER_NO_CERT_PARAM) - 1;
// The TLSIO_RECEIVE_BUFFER_SIZE has very little effect on performance, and is kept small
// to minimize memory consumption.
#define TLSIO_RECEIVE_BUFFER_SIZE 64
typedef enum TLSIO_STATE_TAG
{
TLSIO_STATE_CLOSED,
TLSIO_STATE_OPENING_WAITING_SOCKET,
TLSIO_STATE_OPENING_WAITING_SSL,
TLSIO_STATE_OPEN,
TLSIO_STATE_ERROR,
} TLSIO_STATE;
bool is_an_opening_state(TLSIO_STATE state)
{
return state == TLSIO_STATE_OPENING_WAITING_SOCKET ||
state == TLSIO_STATE_OPENING_WAITING_SSL;
}
typedef struct TLS_IO_INSTANCE_TAG
{
ON_BYTES_RECEIVED on_bytes_received;
ON_IO_ERROR on_io_error;
ON_IO_OPEN_COMPLETE on_open_complete;
void* on_bytes_received_context;
void* on_io_error_context;
void* on_open_complete_context;
TLSIO_STATE tlsio_state;
STRING_HANDLE hostname;
uint16_t port;
SINGLYLINKEDLIST_HANDLE pending_transmission_list;
TLSIO_OPTIONS options;
} TLS_IO_INSTANCE;
/* Codes_SRS_TLSIO_30_005: [ The phrase "enter TLSIO_STATE_EXT_ERROR" means the adapter shall call the on_io_error function and pass the on_io_error_context that was supplied in tlsio_open_async. ]*/
static void enter_tlsio_error_state(TLS_IO_INSTANCE* tls_io_instance)
{
if (tls_io_instance->tlsio_state != TLSIO_STATE_ERROR)
{
LogInfo("Calling error callback");
tls_io_instance->tlsio_state = TLSIO_STATE_ERROR;
tls_io_instance->on_io_error(tls_io_instance->on_io_error_context);
}
}
/* Codes_SRS_TLSIO_30_005: [ When the adapter enters TLSIO_STATE_EXT_ERROR it shall call the on_io_error function and pass the on_io_error_context that were supplied in tlsio_open . ]*/
static void enter_open_error_state(TLS_IO_INSTANCE* tls_io_instance)
{
// save instance variables in case the framework destroys this object before we exit
ON_IO_OPEN_COMPLETE on_open_complete = tls_io_instance->on_open_complete;
void* on_open_complete_context = tls_io_instance->on_open_complete_context;
enter_tlsio_error_state(tls_io_instance);
on_open_complete(on_open_complete_context, IO_OPEN_ERROR);
}
// Return true if a message was available to remove
static bool process_and_destroy_head_message(TLS_IO_INSTANCE* tls_io_instance, IO_SEND_RESULT send_result)
{
bool result;
LIST_ITEM_HANDLE head_pending_io;
if (send_result == IO_SEND_ERROR)
{
/* Codes_SRS_TLSIO_30_095: [ If the send process fails before sending all of the bytes in an enqueued message, the tlsio_dowork shall call the message's on_send_complete along with its associated callback_context and IO_SEND_ERROR. ]*/
enter_tlsio_error_state(tls_io_instance);
}
head_pending_io = singlylinkedlist_get_head_item(tls_io_instance->pending_transmission_list);
if (head_pending_io != NULL)
{
PENDING_TRANSMISSION* head_message = (PENDING_TRANSMISSION*)singlylinkedlist_item_get_value(head_pending_io);
if (singlylinkedlist_remove(tls_io_instance->pending_transmission_list, head_pending_io) != 0)
{
// This particular situation is a bizarre and unrecoverable internal error
/* Codes_SRS_TLSIO_30_094: [ If the send process encounters an internal error or calls on_send_complete with IO_SEND_ERROR due to either failure or timeout, it shall also call on_io_error and pass in the associated on_io_error_context. ]*/
enter_tlsio_error_state(tls_io_instance);
LogError("Failed to remove message from list");
}
// on_send_complete is checked for NULL during PENDING_TRANSMISSION creation
/* Codes_SRS_TLSIO_30_095: [ If the send process fails before sending all of the bytes in an enqueued message, the tlsio_dowork shall call the message's on_send_complete along with its associated callback_context and IO_SEND_ERROR. ]*/
head_message->on_send_complete(head_message->callback_context, send_result);
free(head_message->bytes);
free(head_message);
result = true;
}
else
{
result = false;
}
return result;
}
static void internal_close(TLS_IO_INSTANCE* tls_io_instance)
{
/* Codes_SRS_TLSIO_30_009: [ The phrase "enter TLSIO_STATE_EXT_CLOSING" means the adapter shall iterate through any unsent messages in the queue and shall delete each message after calling its on_send_complete with the associated callback_context and IO_SEND_CANCELLED. ]*/
/* Codes_SRS_TLSIO_30_006: [ The phrase "enter TLSIO_STATE_EXT_CLOSED" means the adapter shall forcibly close any existing connections then call the on_io_close_complete function and pass the on_io_close_complete_context that was supplied in tlsio_close_async. ]*/
/* Codes_SRS_TLSIO_30_051: [ On success, if the underlying TLS does not support asynchronous closing, then the adapter shall enter TLSIO_STATE_EXT_CLOSED immediately after entering TLSIO_STATE_EX_CLOSING. ]*/
if (tls_io_instance->tlsio_state == TLSIO_STATE_OPEN)
{
sslClient_stop();
}
while (process_and_destroy_head_message(tls_io_instance, IO_SEND_CANCELLED));
// singlylinkedlist_destroy gets called in the main destroy
tls_io_instance->on_bytes_received = NULL;
tls_io_instance->on_io_error = NULL;
tls_io_instance->on_bytes_received_context = NULL;
tls_io_instance->on_io_error_context = NULL;
tls_io_instance->tlsio_state = TLSIO_STATE_CLOSED;
tls_io_instance->on_open_complete = NULL;
tls_io_instance->on_open_complete_context = NULL;
}
static void tlsio_arduino_destroy(CONCRETE_IO_HANDLE tls_io)
{
if (tls_io == NULL)
{
/* Codes_SRS_TLSIO_30_020: [ If tlsio_handle is NULL, tlsio_destroy shall do nothing. ]*/
LogError("NULL tlsio");
}
else
{
TLS_IO_INSTANCE* tls_io_instance = (TLS_IO_INSTANCE*)tls_io;
if (tls_io_instance->tlsio_state != TLSIO_STATE_CLOSED)
{
/* Codes_SRS_TLSIO_30_022: [ If the adapter is in any state other than TLSIO_STATE_EX_CLOSED when tlsio_destroy is called, the adapter shall enter TLSIO_STATE_EX_CLOSING and then enter TLSIO_STATE_EX_CLOSED before completing the destroy process. ]*/
LogError("tlsio_arduino_destroy called while not in TLSIO_STATE_CLOSED.");
internal_close(tls_io_instance);
}
/* Codes_SRS_TLSIO_30_021: [ The tlsio_destroy shall release all allocated resources and then release tlsio_handle. ]*/
if (tls_io_instance->hostname != NULL)
{
STRING_delete(tls_io_instance->hostname);
}
tlsio_options_release_resources(&tls_io_instance->options);
if (tls_io_instance->pending_transmission_list != NULL)
{
/* Pending messages were cleared in internal_close */
singlylinkedlist_destroy(tls_io_instance->pending_transmission_list);
}
free(tls_io_instance);
}
}
/* Codes_SRS_TLSIO_30_010: [ The tlsio_create shall allocate and initialize all necessary resources and return an instance of the tlsio_arduino_compact. ]*/
static CONCRETE_IO_HANDLE tlsio_arduino_create(void* io_create_parameters)
{
TLS_IO_INSTANCE* result;
if (io_create_parameters == NULL)
{
/* Codes_SRS_TLSIO_30_013: [ If the io_create_parameters value is NULL, tlsio_create shall log an error and return NULL. ]*/
LogError("NULL tls_io_config");
result = NULL;
}
else
{
/* Codes_SRS_TLSIO_30_012: [ The tlsio_create shall receive the connection configuration as a TLSIO_CONFIG* in io_create_parameters. ]*/
TLSIO_CONFIG* tls_io_config = (TLSIO_CONFIG*)io_create_parameters;
if (tls_io_config->hostname == NULL)
{
/* Codes_SRS_TLSIO_30_014: [ If the hostname member of io_create_parameters value is NULL, tlsio_create shall log an error and return NULL. ]*/
LogError("NULL tls_io_config->hostname");
result = NULL;
}
else
{
if (tls_io_config->port < 0 || tls_io_config->port > MAX_VALID_PORT)
{
/* Codes_SRS_TLSIO_30_015: [ If the port member of io_create_parameters value is less than 0 or greater than 0xffff, tlsio_create shall log an error and return NULL. ]*/
LogError("tls_io_config->port out of range");
result = NULL;
}
else
{
result = malloc(sizeof(TLS_IO_INSTANCE));
if (result == NULL)
{
/* Codes_SRS_TLSIO_30_011: [ If any resource allocation fails, tlsio_create shall return NULL. ]*/
LogError("malloc failed");
}
else
{
memset(result, 0, sizeof(TLS_IO_INSTANCE));
result->hostname = NULL;
result->port = (uint16_t)tls_io_config->port;
result->tlsio_state = TLSIO_STATE_CLOSED;
result->pending_transmission_list = NULL;
tlsio_options_initialize(&result->options, TLSIO_OPTION_BIT_TRUSTED_CERTS);
/* Codes_SRS_TLSIO_30_016: [ tlsio_create shall make a copy of the hostname member of io_create_parameters to allow deletion of hostname immediately after the call. ]*/
if (NULL == (result->hostname = STRING_construct(tls_io_config->hostname)))
{
/* Codes_SRS_TLSIO_30_011: [ If any resource allocation fails, tlsio_create shall return NULL. ]*/
LogError("Failed to allocate hostname");
tlsio_arduino_destroy(result);
result = NULL;
}
else
{
// Create the message queue
result->pending_transmission_list = singlylinkedlist_create();
if (result->pending_transmission_list == NULL)
{
/* Codes_SRS_TLSIO_30_011: [ If any resource allocation fails, tlsio_create shall return NULL. ]*/
LogError("Failed singlylinkedlist_create");
tlsio_arduino_destroy(result);
result = NULL;
}
}
}
}
}
}
return (CONCRETE_IO_HANDLE)result;
}
static int tlsio_arduino_open_async(CONCRETE_IO_HANDLE tls_io,
ON_IO_OPEN_COMPLETE on_io_open_complete, void* on_io_open_complete_context,
ON_BYTES_RECEIVED on_bytes_received, void* on_bytes_received_context,
ON_IO_ERROR on_io_error, void* on_io_error_context)
{
int result;
if (on_io_open_complete == NULL)
{
/* Codes_SRS_TLSIO_30_031: [ If the on_io_open_complete parameter is NULL, tlsio_open shall log an error and return FAILURE. ]*/
LogError("Required parameter on_io_open_complete is NULL");
result = MU_FAILURE;
}
else
{
if (tls_io == NULL)
{
/* Codes_SRS_TLSIO_30_030: [ If the tlsio_handle parameter is NULL, tlsio_open shall log an error and return FAILURE. ]*/
result = MU_FAILURE;
LogError("NULL tlsio");
}
else
{
if (on_bytes_received == NULL)
{
/* Codes_SRS_TLSIO_30_032: [ If the on_bytes_received parameter is NULL, tlsio_open shall log an error and return FAILURE. ]*/
LogError("Required parameter on_bytes_received is NULL");
result = MU_FAILURE;
}
else
{
if (on_io_error == NULL)
{
/* Codes_SRS_TLSIO_30_033: [ If the on_io_error parameter is NULL, tlsio_open shall log an error and return FAILURE. ]*/
LogError("Required parameter on_io_error is NULL");
result = MU_FAILURE;
}
else
{
TLS_IO_INSTANCE* tls_io_instance = (TLS_IO_INSTANCE*)tls_io;
if (tls_io_instance->tlsio_state != TLSIO_STATE_CLOSED)
{
/* Codes_SRS_TLSIO_30_037: [ If the adapter is in any state other than TLSIO_STATE_EXT_CLOSED when tlsio_open is called, it shall log an error, and return FAILURE. ]*/
LogError("Invalid tlsio_state. Expected state is TLSIO_STATE_CLOSED.");
result = MU_FAILURE;
}
else
{
/* Codes_SRS_TLSIO_30_034: [ The tlsio_open shall store the provided on_bytes_received, on_bytes_received_context, on_io_error, on_io_error_context, on_io_open_complete, and on_io_open_complete_context parameters for later use as specified and tested per other line entries in this document. ]*/
tls_io_instance->on_bytes_received = on_bytes_received;
tls_io_instance->on_bytes_received_context = on_bytes_received_context;
tls_io_instance->on_io_error = on_io_error;
tls_io_instance->on_io_error_context = on_io_error_context;
tls_io_instance->on_open_complete = on_io_open_complete;
tls_io_instance->on_open_complete_context = on_io_open_complete_context;
/* Codes_SRS_TLSIO_30_035: [ On tlsio_open success the adapter shall enter TLSIO_STATE_EX_OPENING and return 0. ]*/
// All the real work happens in dowork
tls_io_instance->tlsio_state = TLSIO_STATE_OPENING_WAITING_SOCKET;
result = 0;
}
}
}
}
/* Codes_SRS_TLSIO_30_039: [ On failure, tlsio_open_async shall not call on_io_open_complete. ]*/
}
return result;
}
// This implementation does not have asynchronous close, but uses the _async name for consistencty with the spec
static int tlsio_arduino_close_async(CONCRETE_IO_HANDLE tls_io, ON_IO_CLOSE_COMPLETE on_io_close_complete, void* callback_context)
{
int result;
if (tls_io == NULL)
{
/* Codes_SRS_TLSIO_30_050: [ If the tlsio_handle parameter is NULL, tlsio_arduino_close_async shall log an error and return FAILURE. ]*/
LogError("NULL tlsio");
result = MU_FAILURE;
}
else
{
if (on_io_close_complete == NULL)
{
/* Codes_SRS_TLSIO_30_055: [ If the on_io_close_complete parameter is NULL, tlsio_arduino_close_async shall log an error and return FAILURE. ]*/
LogError("NULL on_io_close_complete");
result = MU_FAILURE;
}
else
{
TLS_IO_INSTANCE* tls_io_instance = (TLS_IO_INSTANCE*)tls_io;
if (tls_io_instance->tlsio_state != TLSIO_STATE_OPEN &&
tls_io_instance->tlsio_state != TLSIO_STATE_ERROR)
{
/* Codes_SRS_TLSIO_30_053: [ If the adapter is in any state other than TLSIO_STATE_EXT_OPEN or TLSIO_STATE_EXT_ERROR then tlsio_close_async shall log that tlsio_close_async has been called and then continue normally. ]*/
// LogInfo rather than LogError because this is an unusual but not erroneous situation
LogInfo("tlsio_arduino_close has been called when in neither TLSIO_STATE_OPEN nor TLSIO_STATE_ERROR.");
}
if (is_an_opening_state(tls_io_instance->tlsio_state))
{
/* Codes_SRS_TLSIO_30_057: [ On success, if the adapter is in TLSIO_STATE_EXT_OPENING, it shall call on_io_open_complete with the on_io_open_complete_context supplied in tlsio_open_async and IO_OPEN_CANCELLED. This callback shall be made before changing the internal state of the adapter. ]*/
tls_io_instance->on_open_complete(tls_io_instance->on_open_complete_context, IO_OPEN_CANCELLED);
}
// This adapter does not support asynchronous closing
/* Codes_SRS_TLSIO_30_056: [ On success the adapter shall enter TLSIO_STATE_EX_CLOSING. ]*/
/* Codes_SRS_TLSIO_30_051: [ On success, if the underlying TLS does not support asynchronous closing, then the adapter shall enter TLSIO_STATE_EX_CLOSED immediately after entering TLSIO_STATE_EX_CLOSING. ]*/
/* Codes_SRS_TLSIO_30_052: [ On success tlsio_close shall return 0. ]*/
internal_close(tls_io_instance);
on_io_close_complete(callback_context);
result = 0;
}
}
/* Codes_SRS_TLSIO_30_054: [ On failure, the adapter shall not call on_io_close_complete. ]*/
return result;
}
static void dowork_read(TLS_IO_INSTANCE* tls_io_instance)
{
// TRANSFER_BUFFER_SIZE is not very important because if the message is bigger
// then the framework just calls dowork repeatedly until it gets everything. So
// a bigger buffer would just use memory without buying anything.
// Putting this buffer in a small function also allows it to exist on the stack
// rather than adding to heap fragmentation.
uint8_t buffer[TLSIO_RECEIVE_BUFFER_SIZE];
int rcv_bytes;
if (tls_io_instance->tlsio_state == TLSIO_STATE_OPEN)
{
if (0 == sslClient_connected())
{
enter_tlsio_error_state(tls_io_instance);
}
else
{
while (tls_io_instance->tlsio_state == TLSIO_STATE_OPEN && 0 != sslClient_available())
{
rcv_bytes = sslClient_read(buffer, TLSIO_RECEIVE_BUFFER_SIZE);
LogInfo("Received %d bytes", rcv_bytes);
if (rcv_bytes > 0)
{
// tls_io_instance->on_bytes_received was already checked for NULL
// in the call to tlsio_arduino_open_async
/* Codes_SRS_TLSIO_30_100: [ As long as the TLS connection is able to provide received data, tlsio_dowork shall repeatedly read this data and call on_bytes_received with the pointer to the buffer containing the data, the number of bytes received, and the on_bytes_received_context. ]*/
tls_io_instance->on_bytes_received(tls_io_instance->on_bytes_received_context, buffer, rcv_bytes);
}
else if (rcv_bytes < 0)
{
LogError("Communications error while reading");
enter_tlsio_error_state(tls_io_instance);
}
else
{
/* Codes_SRS_TLSIO_30_102: [ If the TLS connection receives no data then tlsio_dowork shall not call the on_bytes_received
callback. ]*/
break;
}
}
}
}
}
static void dowork_send(TLS_IO_INSTANCE* tls_io_instance)
{
LIST_ITEM_HANDLE first_pending_io = singlylinkedlist_get_head_item(tls_io_instance->pending_transmission_list);
if (first_pending_io != NULL)
{
PENDING_TRANSMISSION* pending_message = (PENDING_TRANSMISSION*)singlylinkedlist_item_get_value(first_pending_io);
uint8_t* buffer = ((uint8_t*)pending_message->bytes) + pending_message->size - pending_message->unsent_size;
size_t write_result = sslClient_write(buffer, pending_message->unsent_size);
if (write_result >= 0)
{
pending_message->unsent_size -= write_result;
if (pending_message->unsent_size == 0)
{
/* Codes_SRS_TLSIO_30_091: [ If tlsio_arduino_compact_dowork is able to send all the bytes in an enqueued message, it shall call the messages's on_send_complete along with its associated callback_context and IO_SEND_OK. ]*/
// The whole message has been sent successfully
process_and_destroy_head_message(tls_io_instance, IO_SEND_OK);
}
else
{
/* Codes_SRS_TLSIO_30_093: [ If the TLS connection was not able to send an entire enqueued message at once, subsequent calls to tlsio_dowork shall continue to send the remaining bytes. ]*/
// Repeat the send on the next pass with the rest of the message
// This empty else compiles to nothing but helps readability
}
}
else
{
// The write returned non-success. It may be busy, or it may be broken
/* Codes_SRS_TLSIO_30_002: [ The phrase "destroy the failed message" means that the adapter shall remove the message from the queue and destroy it after calling the message's on_send_complete along with its associated callback_context and IO_SEND_ERROR. ]*/
/* Codes_SRS_TLSIO_30_005: [ When the adapter enters TLSIO_STATE_EXT_ERROR it shall call the on_io_error function and pass the on_io_error_context that were supplied in tlsio_open . ]*/
/* Codes_SRS_TLSIO_30_095: [ If the send process fails before sending all of the bytes in an enqueued message, tlsio_dowork shall destroy the failed message and enter TLSIO_STATE_EX_ERROR. ]*/
// This is an unexpected error, and we need to bail out. Probably lost internet connection.
LogError("Write failed");
process_and_destroy_head_message(tls_io_instance, IO_SEND_ERROR);
}
}
else
{
/* Codes_SRS_TLSIO_30_096: [ If there are no enqueued messages available, tlsio_arduino_compact_dowork shall do nothing. ]*/
}
}
static void dowork_poll_socket(TLS_IO_INSTANCE* tls_io_instance)
{
// Nothing to do here
tls_io_instance->tlsio_state = TLSIO_STATE_OPENING_WAITING_SSL;
}
static void dowork_poll_open_ssl(TLS_IO_INSTANCE* tls_io_instance)
{
int connect_success = sslClient_connect(STRING_c_str(tls_io_instance->hostname), tls_io_instance->port);
if (connect_success)
{
/* Codes_SRS_TLSIO_30_080: [ The tlsio_dowork shall establish a TLS connection using the hostName and port provided during tlsio_open. ]*/
// Connect succeeded
tls_io_instance->tlsio_state = TLSIO_STATE_OPEN;
/* Codes_SRS_TLSIO_30_007: [ The phrase "enter TLSIO_STATE_EXT_OPEN" means the adapter shall call the on_io_open_complete function and pass IO_OPEN_OK and the on_io_open_complete_context that was supplied in tlsio_open . ]*/
/* Codes_SRS_TLSIO_30_083: [ If tlsio_dowork successfully opens the TLS connection it shall enter TLSIO_STATE_EX_OPEN. ]*/
tls_io_instance->on_open_complete(tls_io_instance->on_open_complete_context, IO_OPEN_OK);
}
else
{
LogError("Error opening socket %d", connect_success);
enter_open_error_state(tls_io_instance);
}
}
static void tlsio_arduino_dowork(CONCRETE_IO_HANDLE tls_io)
{
if (tls_io == NULL)
{
/* Codes_SRS_TLSIO_30_070: [ If the tlsio_handle parameter is NULL, tlsio_dowork shall do nothing except log an error. ]*/
LogError("NULL tlsio");
}
else
{
TLS_IO_INSTANCE* tls_io_instance = (TLS_IO_INSTANCE*)tls_io;
// This switch statement handles all of the state transitions during the opening process
switch (tls_io_instance->tlsio_state)
{
case TLSIO_STATE_CLOSED:
LogInfo("dowork TLSIO_STATE_CLOSED");
/* Codes_SRS_TLSIO_30_075: [ If the adapter is in TLSIO_STATE_EXT_CLOSED then tlsio_dowork shall do nothing. ]*/
// Waiting to be opened, nothing to do
break;
case TLSIO_STATE_OPENING_WAITING_SOCKET:
LogInfo("dowork TLSIO_STATE_OPENING_WAITING_SOCKET");
dowork_poll_socket(tls_io_instance);
break;
case TLSIO_STATE_OPENING_WAITING_SSL:
LogInfo("dowork TLSIO_STATE_OPENING_WAITING_SSL");
dowork_poll_open_ssl(tls_io_instance);
break;
case TLSIO_STATE_OPEN:
dowork_read(tls_io_instance);
dowork_send(tls_io_instance);
break;
case TLSIO_STATE_ERROR:
LogInfo("dowork TLSIO_STATE_ERROR");
/* Codes_SRS_TLSIO_30_071: [ If the adapter is in TLSIO_STATE_EXT_ERROR then tlsio_dowork shall do nothing. ]*/
// There's nothing valid to do here but wait to be retried
break;
default:
LogError("Unexpected internal tlsio state: %d", tls_io_instance->tlsio_state);
break;
}
}
}
static int tlsio_arduino_setoption(CONCRETE_IO_HANDLE tls_io, const char* optionName, const void* value)
{
TLS_IO_INSTANCE* tls_io_instance = (TLS_IO_INSTANCE*)tls_io;
/* Codes_SRS_TLSIO_30_120: [ If the tlsio_handle parameter is NULL, tlsio_arduino_setoption shall do nothing except log an error and return FAILURE. ]*/
int result;
if (tls_io_instance == NULL)
{
LogError("NULL tlsio");
result = MU_FAILURE;
}
else
{
/* Codes_SRS_TLSIO_30_121: [ If the optionName parameter is NULL, tlsio_arduino_setoption shall do nothing except log an error and return FAILURE. ]*/
/* Codes_SRS_TLSIO_30_122: [ If the value parameter is NULL, tlsio_arduino_setoption shall do nothing except log an error and return FAILURE. ]*/
/* Codes_SRS_TLSIO_OPENSSL_COMPACT_30_520 [ The tlsio_setoption shall do nothing and return FAILURE. ]*/
TLSIO_OPTIONS_RESULT options_result = tlsio_options_set(&tls_io_instance->options, optionName, value);
if (options_result != TLSIO_OPTIONS_RESULT_SUCCESS)
{
LogError("Failed tlsio_options_set");
result = MU_FAILURE;
}
else
{
result = 0;
}
}
return result;
}
static int tlsio_arduino_send_async(CONCRETE_IO_HANDLE tls_io, const void* buffer, size_t size, ON_SEND_COMPLETE on_send_complete, void* callback_context)
{
int result;
if (on_send_complete == NULL || tls_io == NULL || buffer == NULL || size == 0 || on_send_complete == NULL)
{
/* Codes_SRS_TLSIO_30_062: [ If the on_send_complete is NULL, tlsio_arduino_compact_send shall log the error and return FAILURE. ]*/
result = MU_FAILURE;
LogError("Invalid parameter specified: tls_io: %p, buffer: %p, size: %zu, on_send_complete: %p", tls_io, buffer, size, on_send_complete);
}
else
{
TLS_IO_INSTANCE* tls_io_instance = (TLS_IO_INSTANCE*)tls_io;
if (tls_io_instance->tlsio_state != TLSIO_STATE_OPEN)
{
/* Codes_SRS_TLSIO_30_060: [ If the tlsio_handle parameter is NULL, tlsio_arduino_compact_send shall log an error and return FAILURE. ]*/
/* Codes_SRS_TLSIO_30_065: [ If tlsio_arduino_compact_open has not been called or the opening process has not been completed, tlsio_arduino_compact_send shall log an error and return FAILURE. ]*/
result = MU_FAILURE;
LogError("tlsio_arduino_send_async without a prior successful open");
}
else
{
PENDING_TRANSMISSION* pending_transmission = (PENDING_TRANSMISSION*)malloc(sizeof(PENDING_TRANSMISSION));
if (pending_transmission == NULL)
{
/* Codes_SRS_TLSIO_30_064: [ If the supplied message cannot be enqueued for transmission, tlsio_arduino_compact_send shall log an error and return FAILURE. ]*/
result = MU_FAILURE;
LogError("malloc failed");
}
else
{
/* Codes_SRS_TLSIO_30_063: [ The tlsio_arduino_compact_send shall enqueue for transmission the on_send_complete, the callback_context, the size, and the contents of buffer. ]*/
if ((pending_transmission->bytes = (unsigned char*)malloc(size)) == NULL)
{
/* Codes_SRS_TLSIO_30_064: [ If the supplied message cannot be enqueued for transmission, tlsio_arduino_compact_send shall log an error and return FAILURE. ]*/
LogError("malloc failed");
free(pending_transmission);
result = MU_FAILURE;
}
else
{
pending_transmission->size = size;
pending_transmission->unsent_size = size;
pending_transmission->on_send_complete = on_send_complete;
pending_transmission->callback_context = callback_context;
(void)memcpy(pending_transmission->bytes, buffer, size);
if (singlylinkedlist_add(tls_io_instance->pending_transmission_list, pending_transmission) == NULL)
{
/* Codes_SRS_TLSIO_30_064: [ If the supplied message cannot be enqueued for transmission, tlsio_arduino_compact_send shall log an error and return FAILURE. ]*/
LogError("Unable to add socket to pending list.");
free(pending_transmission->bytes);
free(pending_transmission);
result = MU_FAILURE;
}
else
{
/* Codes_SRS_TLSIO_30_063: [ On success, tlsio_send shall enqueue for transmission the on_send_complete , the callback_context , the size , and the contents of buffer and then return 0. ]*/
result = 0;
dowork_send(tls_io_instance);
}
}
}
}
/* Codes_SRS_TLSIO_30_066: [ On failure, on_send_complete shall not be called. ]*/
}
return result;
}
/* Codes_SRS_TLSIO_APPLEIOS_COMPACT_30_560: [ The tlsio_retrieveoptions shall do nothing and return NULL. ]*/
static OPTIONHANDLER_HANDLE tlsio_arduino_retrieveoptions(CONCRETE_IO_HANDLE tls_io)
{
TLS_IO_INSTANCE* tls_io_instance = (TLS_IO_INSTANCE*)tls_io;
/* Codes_SRS_TLSIO_30_160: [ If the tlsio_handle parameter is NULL, tlsio_arduino_retrieveoptions shall do nothing except log an error and return FAILURE. ]*/
OPTIONHANDLER_HANDLE result;
if (tls_io_instance == NULL)
{
LogError("NULL tlsio");
result = NULL;
}
else
{
result = tlsio_options_retrieve_options(&tls_io_instance->options, tlsio_arduino_setoption);
}
return result;
}
/* Codes_SRS_TLSIO_30_008: [ The tlsio_get_interface_description shall return the VTable IO_INTERFACE_DESCRIPTION. ]*/
static const IO_INTERFACE_DESCRIPTION tlsio_arduino_interface_description =
{
tlsio_arduino_retrieveoptions,
tlsio_arduino_create,
tlsio_arduino_destroy,
tlsio_arduino_open_async,
tlsio_arduino_close_async,
tlsio_arduino_send_async,
tlsio_arduino_dowork,
tlsio_arduino_setoption
};
/* Codes_SRS_TLSIO_30_001: [ The tlsio_arduino_compact shall implement and export all the Concrete functions in the VTable IO_INTERFACE_DESCRIPTION defined in the xio.h. ]*/
const IO_INTERFACE_DESCRIPTION* tlsio_arduino_get_interface_description(void)
{
return &tlsio_arduino_interface_description;
}
#ifndef ARDUINO_ARCH_ESP32
const IO_INTERFACE_DESCRIPTION* socketio_get_interface_description(void)
{
return NULL;
}
#endif