-
Notifications
You must be signed in to change notification settings - Fork 232
/
Copy pathHAPBLETransaction.c
314 lines (279 loc) · 13.4 KB
/
HAPBLETransaction.c
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
// Copyright (c) 2015-2019 The HomeKit ADK Contributors
//
// Licensed under the Apache License, Version 2.0 (the “License”);
// you may not use this file except in compliance with the License.
// See [CONTRIBUTORS.md] for the list of HomeKit ADK project authors.
#include "HAP+Internal.h"
static const HAPLogObject logObject = { .subsystem = kHAP_LogSubsystem, .category = "BLETransaction" };
void HAPBLETransactionCreate(HAPBLETransaction* bleTransaction, void* _Nullable bodyBytes, size_t numBodyBytes)
HAP_DIAGNOSE_ERROR(!bodyBytes && numBodyBytes, "empty buffer cannot have a length") {
HAPPrecondition(bleTransaction);
HAPPrecondition(!numBodyBytes || bodyBytes);
HAPRawBufferZero(bleTransaction, sizeof *bleTransaction);
bleTransaction->_.request.bodyBytes = bodyBytes;
bleTransaction->_.request.maxBodyBytes = numBodyBytes;
}
/**
* Appends a body fragment to the combined body in a transaction.
* If the transaction buffer is not large enough, input fragment is discarded.
*
* @param bleTransaction Transaction.
* @param fragmentBytes Buffer to append.
* @param numFragmentBytes Length of buffer.
*/
static void TryAppendBodyFragment(
HAPBLETransaction* bleTransaction,
const void* _Nullable const fragmentBytes,
size_t numFragmentBytes) {
HAPPrecondition(bleTransaction);
HAPPrecondition(!numFragmentBytes || fragmentBytes);
if (bleTransaction->_.request.totalBodyBytes > bleTransaction->_.request.maxBodyBytes) {
HAPLogInfo(
&logObject,
"Discarding body fragment as transaction buffer is not large enough (%lu/%lu).",
(unsigned long) bleTransaction->_.request.totalBodyBytes,
(unsigned long) bleTransaction->_.request.maxBodyBytes);
} else if (fragmentBytes) {
HAPAssert(bleTransaction->_.request.bodyBytes);
uint8_t* bodyBytes = bleTransaction->_.request.bodyBytes;
HAPRawBufferCopyBytes(&bodyBytes[bleTransaction->_.request.bodyOffset], fragmentBytes, numFragmentBytes);
}
bleTransaction->_.request.bodyOffset += numFragmentBytes;
}
HAP_RESULT_USE_CHECK
HAPError HAPBLETransactionHandleWrite(HAPBLETransaction* bleTransaction, const void* bytes, size_t numBytes) {
HAPPrecondition(bleTransaction);
HAPPrecondition(bytes);
HAPError err;
switch (bleTransaction->state) {
case kHAPBLETransactionState_WaitingForInitialWrite: {
bleTransaction->state = kHAPBLETransactionState_ReadingRequest;
// Read packet. It has to be a HAP Request.
// See HomeKit Accessory Protocol Specification R14
// Section 7.3.5 HAP Procedures
HAPBLEPDU pdu;
err = HAPBLEPDUDeserialize(&pdu, bytes, numBytes);
if (err) {
HAPAssert(err == kHAPError_InvalidData);
return err;
}
if (pdu.controlField.type != kHAPBLEPDUType_Request) {
HAPLog(&logObject, "Expected HAP-BLE request but got PDU with different type.");
return kHAPError_InvalidData;
}
// Cache request header. Potential continuations do not include it.
bleTransaction->_.request.opcode = pdu.fixedParams.request.opcode;
bleTransaction->_.request.tid = pdu.fixedParams.request.tid;
bleTransaction->_.request.iid = pdu.fixedParams.request.iid;
bleTransaction->_.request.totalBodyBytes = pdu.body.totalBodyBytes;
bleTransaction->_.request.bodyOffset = 0;
// Append body.
TryAppendBodyFragment(bleTransaction, pdu.body.bytes, pdu.body.numBytes);
return kHAPError_None;
}
case kHAPBLETransactionState_ReadingRequest: {
// Read continuation. It has the same tid as the previous fragments.
// See HomeKit Accessory Protocol Specification R14
// Section 7.3.3.5 HAP PDU Fragmentation Scheme
// See HomeKit Accessory Protocol Specification R14
// Section 7.3.5.6 HAP Fragmented Writes
HAPBLEPDU pdu;
err = HAPBLEPDUDeserializeContinuation(
&pdu,
bytes,
numBytes,
kHAPBLEPDUType_Request,
bleTransaction->_.request.totalBodyBytes,
bleTransaction->_.request.bodyOffset);
if (err) {
HAPAssert(err == kHAPError_InvalidData);
return err;
}
if (pdu.fixedParams.continuation.tid != bleTransaction->_.request.tid) {
HAPLog(&logObject, "Continuation fragment has different TID as the previous fragments.");
return kHAPError_InvalidData;
}
// Append body.
TryAppendBodyFragment(bleTransaction, pdu.body.bytes, pdu.body.numBytes);
return kHAPError_None;
}
case kHAPBLETransactionState_HandlingRequest:
case kHAPBLETransactionState_WaitingForInitialRead: {
// Full request received, response has been set.
// However, there may still be writes with empty fragments before the first read request.
HAPBLEPDU pdu;
err = HAPBLEPDUDeserializeContinuation(
&pdu,
bytes,
numBytes,
kHAPBLEPDUType_Request,
/* body_length: */ 0,
/* body_offset: */ 0);
if (err) {
HAPAssert(err == kHAPError_InvalidData);
return err;
}
if (pdu.fixedParams.continuation.tid != bleTransaction->_.response.tid) {
HAPLog(&logObject, "Continuation fragment has different TID as the previous fragments.");
return kHAPError_InvalidData;
}
return kHAPError_None;
}
case kHAPBLETransactionState_WritingResponse: {
HAPLog(&logObject, "Received write while writing response.");
return kHAPError_InvalidState;
}
}
HAPFatalError();
}
HAP_RESULT_USE_CHECK
bool HAPBLETransactionIsRequestAvailable(const HAPBLETransaction* bleTransaction) {
HAPPrecondition(bleTransaction);
return bleTransaction->state == kHAPBLETransactionState_ReadingRequest &&
bleTransaction->_.request.bodyOffset == bleTransaction->_.request.totalBodyBytes;
}
HAP_RESULT_USE_CHECK
HAPError HAPBLETransactionGetRequest(HAPBLETransaction* bleTransaction, HAPBLETransactionRequest* request) {
HAPPrecondition(bleTransaction);
HAPPrecondition(HAPBLETransactionIsRequestAvailable(bleTransaction));
HAPPrecondition(request);
bleTransaction->state = kHAPBLETransactionState_HandlingRequest;
if (bleTransaction->_.request.totalBodyBytes > bleTransaction->_.request.maxBodyBytes) {
HAPLog(&logObject,
"Transaction buffer was not large enough to hold request. (%lu/%lu).",
(unsigned long) bleTransaction->_.request.totalBodyBytes,
(unsigned long) bleTransaction->_.request.maxBodyBytes);
return kHAPError_OutOfResources;
}
request->opcode = bleTransaction->_.request.opcode;
request->iid = bleTransaction->_.request.iid;
HAPTLVReaderCreateWithOptions(
&request->bodyReader,
&(const HAPTLVReaderOptions) { .bytes = bleTransaction->_.request.bodyBytes,
.numBytes = bleTransaction->_.request.totalBodyBytes,
.maxBytes = bleTransaction->_.request.maxBodyBytes });
return kHAPError_None;
}
void HAPBLETransactionSetResponse(
HAPBLETransaction* bleTransaction,
HAPBLEPDUStatus status,
const HAPTLVWriterRef* _Nullable bodyWriter) {
HAPPrecondition(bleTransaction);
HAPPrecondition(bleTransaction->state == kHAPBLETransactionState_HandlingRequest);
void* bytes = NULL;
size_t numBytes = 0;
if (bodyWriter) {
HAPTLVWriterGetBuffer(HAPNonnull(bodyWriter), &bytes, &numBytes);
}
// Maximum HAP-BLE PDU Body length == UINT16_MAX.
HAPPrecondition(numBytes <= UINT16_MAX);
uint8_t tid = bleTransaction->_.request.tid;
bleTransaction->state = kHAPBLETransactionState_WaitingForInitialRead;
bleTransaction->_.response.tid = tid;
bleTransaction->_.response.status = status;
if (bodyWriter) {
HAPTLVWriterGetBuffer(
HAPNonnull(bodyWriter),
&bleTransaction->_.response.bodyBytes,
&bleTransaction->_.response.totalBodyBytes);
bleTransaction->_.response.bodyOffset = 0;
} else {
bleTransaction->_.response.bodyBytes = NULL;
bleTransaction->_.response.totalBodyBytes = 0;
bleTransaction->_.response.bodyOffset = 0;
}
}
HAP_RESULT_USE_CHECK
HAPError HAPBLETransactionHandleRead(
HAPBLETransaction* bleTransaction,
void* bytes,
size_t maxBytes,
size_t* numBytes,
bool* isFinalFragment) {
HAPPrecondition(bleTransaction);
HAPPrecondition(bytes);
HAPPrecondition(numBytes);
HAPPrecondition(isFinalFragment);
HAPError err;
switch (bleTransaction->state) {
case kHAPBLETransactionState_WaitingForInitialRead: {
bleTransaction->state = kHAPBLETransactionState_WritingResponse;
// Calculate header length.
size_t numHeaderBytes = kHAPBLEPDU_NumResponseHeaderBytes;
if (bleTransaction->_.response.totalBodyBytes) {
numHeaderBytes += kHAPBLEPDU_NumBodyHeaderBytes;
}
if (maxBytes < numHeaderBytes) {
HAPLog(&logObject, "Not enough capacity for Response PDU header.");
return kHAPError_OutOfResources;
}
// Calculate body fragment length.
size_t numFragmentBytes = bleTransaction->_.response.totalBodyBytes;
HAPAssert(numFragmentBytes <= UINT16_MAX);
if (maxBytes - numHeaderBytes < numFragmentBytes) {
numFragmentBytes = maxBytes - numHeaderBytes;
}
// Serialize HAP-BLE PDU.
HAPBLEPDU pdu;
HAPRawBufferZero(&pdu, sizeof pdu);
pdu.controlField.fragmentationStatus = kHAPBLEPDUFragmentationStatus_FirstFragment;
pdu.controlField.type = kHAPBLEPDUType_Response;
pdu.controlField.length = kHAPBLEPDUControlFieldLength_1Byte;
pdu.fixedParams.response.tid = bleTransaction->_.response.tid;
pdu.fixedParams.response.status = bleTransaction->_.response.status;
pdu.body.totalBodyBytes = (uint16_t) bleTransaction->_.response.totalBodyBytes;
pdu.body.bytes = bleTransaction->_.response.bodyBytes;
pdu.body.numBytes = (uint16_t) numFragmentBytes;
err = HAPBLEPDUSerialize(&pdu, bytes, maxBytes, numBytes);
if (err) {
HAPAssert(err == kHAPError_OutOfResources);
return err;
}
// Advance buffer.
bleTransaction->_.response.bodyOffset += numFragmentBytes;
*isFinalFragment = bleTransaction->_.response.bodyOffset == bleTransaction->_.response.totalBodyBytes;
return kHAPError_None;
}
case kHAPBLETransactionState_WritingResponse: {
// Send next response fragment.
// Calculate header length.
size_t numHeaderBytes = kHAPBLEPDU_NumContinuationHeaderBytes;
if (maxBytes < numHeaderBytes) {
HAPLog(&logObject, "Not enough capacity for Continuation PDU header.");
return kHAPError_OutOfResources;
}
// Calculate body fragment length.
size_t numFragmentBytes = bleTransaction->_.response.totalBodyBytes - bleTransaction->_.response.bodyOffset;
HAPAssert(numFragmentBytes <= UINT16_MAX);
if (maxBytes - numHeaderBytes < numFragmentBytes) {
numFragmentBytes = maxBytes - numHeaderBytes;
}
HAPAssert(bleTransaction->_.response.bodyBytes);
// Serialize HAP-BLE PDU.
HAPBLEPDU pdu;
HAPRawBufferZero(&pdu, sizeof pdu);
pdu.controlField.fragmentationStatus = kHAPBLEPDUFragmentationStatus_Continuation;
pdu.controlField.type = kHAPBLEPDUType_Response;
pdu.controlField.length = kHAPBLEPDUControlFieldLength_1Byte;
pdu.fixedParams.continuation.tid = bleTransaction->_.response.tid;
pdu.body.totalBodyBytes = (uint16_t) bleTransaction->_.response.totalBodyBytes;
pdu.body.bytes = (uint8_t*) bleTransaction->_.response.bodyBytes + bleTransaction->_.response.bodyOffset;
pdu.body.numBytes = (uint16_t) numFragmentBytes;
err = HAPBLEPDUSerialize(&pdu, bytes, maxBytes, numBytes);
if (err) {
HAPAssert(err == kHAPError_OutOfResources);
return err;
}
// Advance buffer.
bleTransaction->_.response.bodyOffset += numFragmentBytes;
*isFinalFragment = bleTransaction->_.response.bodyOffset == bleTransaction->_.response.totalBodyBytes;
return kHAPError_None;
}
case kHAPBLETransactionState_WaitingForInitialWrite:
case kHAPBLETransactionState_ReadingRequest:
case kHAPBLETransactionState_HandlingRequest: {
return kHAPError_InvalidState;
}
}
HAPFatalError();
}