xref: /btstack/src/classic/obex_parser.c (revision cbf509016daa1295a6a8b22d7314db50b88c6c9a)
1 /*
2  * Copyright (C) 2021 BlueKitchen GmbH
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  *
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  * 3. Neither the name of the copyright holders nor the names of
14  *    contributors may be used to endorse or promote products derived
15  *    from this software without specific prior written permission.
16  * 4. Any redistribution, use, or modification is done solely for
17  *    personal benefit and not for any commercial purpose or for
18  *    monetary gain.
19  *
20  * THIS SOFTWARE IS PROVIDED BY BLUEKITCHEN GMBH AND CONTRIBUTORS
21  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
23  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BLUEKITCHEN
24  * GMBH OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
25  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
26  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
27  * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
28  * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
29  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
30  * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31  * SUCH DAMAGE.
32  *
33  * Please inquire about commercial licensing options at
34  * [email protected]
35  *
36  */
37 
38 #define BTSTACK_FILE__ "obex_parser.c"
39 
40 #include "btstack_config.h"
41 
42 #include "classic/obex.h"
43 #include "classic/obex_parser.h"
44 #include <string.h>
45 #include "btstack_debug.h"
46 #include "btstack_util.h"
47 
48 static uint16_t obex_parser_param_size_for_request(uint8_t opcode){
49     switch (opcode) {
50         case OBEX_OPCODE_SETPATH:
51             return 4;
52         case OBEX_OPCODE_CONNECT:
53             return 6;
54         default:
55             return 2;
56     }
57 }
58 
59 static uint16_t obex_parser_param_size_for_response(uint8_t opcode){
60     switch (opcode) {
61         case OBEX_OPCODE_CONNECT:
62             return 6;
63         default:
64             return 2;
65     }
66 }
67 
68 static void obex_parser_init(obex_parser_t * obex_parser, obex_parser_callback_t obex_parser_callback, void * user_data){
69     memset(obex_parser, 0, sizeof(obex_parser_t));
70     obex_parser->packet_size = 3;
71     obex_parser->user_data = user_data;
72     obex_parser->callback = obex_parser_callback;
73 }
74 
75 void obex_parser_init_for_request(obex_parser_t * obex_parser, obex_parser_callback_t obex_parser_callback, void * user_data){
76     obex_parser_init(obex_parser, obex_parser_callback, user_data);
77     obex_parser->state = OBEX_PARSER_STATE_W4_OPCODE;
78 }
79 
80 void obex_parser_init_for_response(obex_parser_t * obex_parser, uint8_t opcode, obex_parser_callback_t obex_parser_callback, void * user_data){
81     obex_parser_init(obex_parser, obex_parser_callback, user_data);
82     obex_parser->state = OBEX_PARSER_STATE_W4_RESPONSE_CODE;
83     obex_parser->opcode = opcode;
84 }
85 
86 obex_parser_object_state_t obex_parser_process_data(obex_parser_t *obex_parser, const uint8_t *data_buffer, uint16_t data_len) {
87     while (data_len){
88         uint16_t bytes_to_consume = 1;
89         uint8_t  header_type;
90         switch (obex_parser->state){
91             case OBEX_PARSER_STATE_W4_OPCODE:
92                 obex_parser->opcode = *data_buffer;
93                 obex_parser->state = OBEX_PARSER_STATE_W4_PACKET_LEN;
94                 obex_parser->item_len = obex_parser_param_size_for_request(obex_parser->opcode);
95                 obex_parser->packet_size = 1 + obex_parser->item_len;
96                 break;
97             case OBEX_PARSER_STATE_W4_RESPONSE_CODE:
98                 obex_parser->response_code = *data_buffer;
99                 obex_parser->state = OBEX_PARSER_STATE_W4_PACKET_LEN;
100                 obex_parser->item_len = obex_parser_param_size_for_response(obex_parser->opcode);
101                 obex_parser->packet_size = 1 + obex_parser->item_len;
102                 break;
103             case OBEX_PARSER_STATE_W4_PACKET_LEN:
104                 bytes_to_consume = btstack_min(2 - obex_parser->item_pos, data_len);
105                 memcpy(&obex_parser->params[obex_parser->item_pos], data_buffer, bytes_to_consume);
106                 obex_parser->item_pos += bytes_to_consume;
107                 if (obex_parser->item_pos == 2){
108                     // validate packet large enough for header
109                     obex_parser->packet_size = big_endian_read_16(obex_parser->params, 0);
110                     if (obex_parser->packet_size < (obex_parser->item_len + 1)){
111                         // packet size smaller than opcode + params
112                         obex_parser->state = OBEX_PARSER_STATE_INVALID;
113                         break;
114                     }
115                     // params already complete?
116                     if (obex_parser->item_len == 2){
117                         obex_parser->state = OBEX_PARSER_STATE_W4_HEADER_ID;
118                     } else {
119                         obex_parser->state = OBEX_PARSER_STATE_W4_PARAMS;
120                     }
121                 }
122                 break;
123             case OBEX_PARSER_STATE_W4_PARAMS:
124                 bytes_to_consume = btstack_min(obex_parser->item_len - obex_parser->item_pos, data_len);
125                 memcpy(&obex_parser->params[obex_parser->item_pos], data_buffer, bytes_to_consume);
126                 obex_parser->item_pos += bytes_to_consume;
127                 if (obex_parser->item_pos == obex_parser->item_len){
128                     obex_parser->state = OBEX_PARSER_STATE_W4_HEADER_ID;
129                 }
130                 break;
131             case OBEX_PARSER_STATE_W4_HEADER_ID:
132                 obex_parser->header_id = *data_buffer; // constants in obex.h encode type as well, so just use these as well
133                 header_type = *data_buffer >> 6;
134                 obex_parser->item_pos = 0;
135                 switch (header_type){
136                     case 0:
137                     case 1:
138                         // 16-bit length info prefixed
139                         obex_parser->item_len = 2;
140                         obex_parser->state = OBEX_PARSER_STATE_W4_HEADER_LEN_FIRST;
141                         break;
142                     case 2:
143                         // 8-bit value
144                         obex_parser->item_len = 1;
145                         obex_parser->state = OBEX_PARSER_STATE_W4_HEADER_VALUE;
146                         break;
147                     case 3:
148                         // 32-bit value
149                         obex_parser->item_len = 4;
150                         obex_parser->state = OBEX_PARSER_STATE_W4_HEADER_VALUE;
151                         break;
152                     default:
153                         // avoid compiler warning about unused cases (encoding in [0..3])
154                         break;
155                 }
156                 break;
157             case OBEX_PARSER_STATE_W4_HEADER_LEN_FIRST:
158                 obex_parser->item_len = *data_buffer << 8;
159                 obex_parser->state = OBEX_PARSER_STATE_W4_HEADER_LEN_SECOND;
160                 break;
161             case OBEX_PARSER_STATE_W4_HEADER_LEN_SECOND:
162                 obex_parser->item_len = obex_parser->item_len + *data_buffer;
163                 if (obex_parser->item_len < 3){
164                     // len to small to even cover header
165                     obex_parser->state = OBEX_PARSER_STATE_INVALID;
166                     break;
167                 };
168                 if (obex_parser->item_len == 3){
169                     // borderline: empty value
170                     obex_parser->state = OBEX_PARSER_STATE_W4_HEADER_ID;
171                     break;
172                 }
173                 obex_parser->item_len -= 3;
174                 obex_parser->state = OBEX_PARSER_STATE_W4_HEADER_VALUE;
175                 break;
176             case OBEX_PARSER_STATE_W4_HEADER_VALUE:
177                 bytes_to_consume = btstack_min(obex_parser->item_len - obex_parser->item_pos, data_len);
178                 if (*obex_parser->callback != NULL){
179                     (*obex_parser->callback)(obex_parser->user_data, obex_parser->header_id, obex_parser->item_len, obex_parser->item_pos, data_buffer, bytes_to_consume);
180                 }
181                 obex_parser->item_pos += bytes_to_consume;
182                 if (obex_parser->item_pos == obex_parser->item_len){
183                     obex_parser->state = OBEX_PARSER_STATE_W4_HEADER_ID;
184                 }
185                 break;
186             case OBEX_PARSER_STATE_COMPLETE:
187                 obex_parser->state = OBEX_PARSER_STATE_OVERRUN;
188                 break;
189             case OBEX_PARSER_STATE_INVALID:
190                 break;
191             default:
192                 btstack_unreachable();
193                 break;
194         }
195 
196         data_buffer += bytes_to_consume;
197         data_len    -= bytes_to_consume;
198         obex_parser->packet_pos += bytes_to_consume;
199 
200         // all bytes read? then check state
201         if (obex_parser->packet_pos == obex_parser->packet_size){
202             if (obex_parser->state == OBEX_PARSER_STATE_W4_HEADER_ID){
203                 obex_parser->state = OBEX_PARSER_STATE_COMPLETE;
204             } else {
205                 obex_parser->state = OBEX_PARSER_STATE_INVALID;
206             }
207         }
208     }
209 
210     switch (obex_parser->state){
211         case OBEX_PARSER_STATE_COMPLETE:
212             return OBEX_PARSER_OBJECT_STATE_COMPLETE;
213         case OBEX_PARSER_STATE_OVERRUN:
214             return OBEX_PARSER_OBJECT_STATE_OVERRUN;
215         case OBEX_PARSER_STATE_INVALID:
216             return OBEX_PARSER_OBJECT_STATE_INVALID;
217         default:
218             return OBEX_PARSER_OBJECT_STATE_INCOMPLETE;
219     }
220 }
221 
222 void obex_parser_get_operation_info(obex_parser_t * obex_parser, obex_parser_operation_info_t * obex_operation_info){
223     memset(obex_operation_info, 0, sizeof(obex_parser_operation_info_t));
224     obex_operation_info->opcode = obex_parser->opcode;
225     obex_operation_info->response_code = obex_parser->response_code;
226     switch (obex_parser->opcode){
227         case OBEX_OPCODE_CONNECT:
228             obex_operation_info->obex_version_number = obex_parser->params[2];
229             obex_operation_info->flags = obex_parser->params[3];
230             obex_operation_info->max_packet_length = big_endian_read_16(obex_parser->params, 4);
231             break;
232         case OBEX_OPCODE_SETPATH:
233             obex_operation_info->flags = obex_parser->params[2];
234             break;
235         default:
236             break;
237     }
238 }
239 obex_parser_header_state_t obex_parser_header_store(uint8_t * header_buffer, uint16_t buffer_size, uint16_t total_len,
240                                                     uint16_t data_offset,  const uint8_t * data_buffer, uint16_t data_len){
241     uint16_t bytes_to_store = btstack_min(buffer_size - data_offset, data_len);
242     memcpy(&header_buffer[data_offset], data_buffer, bytes_to_store);
243     uint16_t new_offset = data_offset + bytes_to_store;
244     if (new_offset > buffer_size){
245         return OBEX_PARSER_HEADER_OVERRUN;
246     } else if (new_offset == total_len) {
247         return OBEX_PARSER_HEADER_COMPLETE;
248     } else {
249         return OBEX_PARSER_HEADER_INCOMPLETE;
250     }
251 }
252 
253 
254 /* OBEX App Param Parser */
255 
256 void obex_app_param_parser_init(obex_app_param_parser_t * parser, obex_app_param_parser_callback_t callback, uint8_t param_size, void * user_data){
257     parser->state = OBEX_APP_PARAM_PARSER_STATE_W4_TYPE;
258     parser->callback = callback;
259     parser->user_data = user_data;
260     parser->param_size = param_size;
261     parser->param_pos = 0;
262 }
263 
264 obex_app_param_parser_params_state_t obex_app_param_parser_process_data(obex_app_param_parser_t *parser, const uint8_t *data_buffer, uint16_t data_len){
265     while ((data_len > 0) && (parser->param_pos < parser->param_size)){
266         uint16_t bytes_to_consume = 1;
267         switch(parser->state){
268             case OBEX_APP_PARAM_PARSER_STATE_INVALID:
269                 return OBEX_APP_PARAM_PARSER_PARAMS_STATE_INVALID;
270             case OBEX_APP_PARAM_PARSER_STATE_W4_TYPE:
271                 parser->tag_id = *data_buffer;
272                 parser->state = OBEX_APP_PARAM_PARSER_STATE_W4_LEN;
273                 break;
274             case OBEX_APP_PARAM_PARSER_STATE_W4_LEN:
275                 parser->tag_len = *data_buffer;
276                 if ((parser->param_pos + parser->tag_len) > parser->param_size){
277                     parser->state = OBEX_APP_PARAM_PARSER_STATE_INVALID;
278                     return OBEX_APP_PARAM_PARSER_PARAMS_STATE_INVALID;
279                 }
280                 parser->tag_pos = 0;
281                 parser->state = OBEX_APP_PARAM_PARSER_STATE_W4_VALUE;
282                 break;
283             case OBEX_APP_PARAM_PARSER_STATE_W4_VALUE:
284                 bytes_to_consume = btstack_min(parser->tag_len - parser->tag_pos, data_len);
285 				// param_size is uint8_t
286                 (*parser->callback)(parser->user_data, parser->tag_id, (uint8_t) parser->tag_len, (uint8_t) parser->tag_pos, data_buffer, (uint8_t) bytes_to_consume);
287                 parser->tag_pos   += bytes_to_consume;
288                 if (parser->tag_pos == parser->tag_len){
289                     parser->state = OBEX_APP_PARAM_PARSER_STATE_W4_TYPE;
290                 }
291                 break;
292             default:
293                 btstack_unreachable();
294                 break;
295         }
296 
297         data_buffer += bytes_to_consume;
298         data_len    -= bytes_to_consume;
299         parser->param_pos += bytes_to_consume;
300 
301         // all bytes read? then check state
302         if (parser->param_pos == parser->param_size){
303             if (parser->state == OBEX_APP_PARAM_PARSER_STATE_W4_TYPE){
304                 parser->state = OBEX_APP_PARAM_PARSER_STATE_COMPLETE;
305             } else {
306                 parser->state = OBEX_APP_PARAM_PARSER_STATE_INVALID;
307             }
308         }
309     }
310 
311     if (data_len > 0){
312         return OBEX_APP_PARAM_PARSER_PARAMS_STATE_OVERRUN;
313     }
314 
315     switch (parser->state){
316         case OBEX_APP_PARAM_PARSER_STATE_COMPLETE:
317             return OBEX_APP_PARAM_PARSER_PARAMS_STATE_COMPLETE;
318         case OBEX_APP_PARAM_PARSER_STATE_INVALID:
319             return OBEX_APP_PARAM_PARSER_PARAMS_STATE_INVALID;
320         default:
321             return OBEX_APP_PARAM_PARSER_PARAMS_STATE_INCOMPLETE;
322     }
323 }
324 
325 
326 obex_app_param_parser_tag_state_t obex_app_param_parser_tag_store(uint8_t * header_buffer, uint8_t buffer_size, uint8_t total_len,
327                                                                          uint8_t data_offset, const uint8_t * data_buffer, uint8_t data_len){
328     uint16_t bytes_to_store = btstack_min(buffer_size - data_offset, data_len);
329     memcpy(&header_buffer[data_offset], data_buffer, bytes_to_store);
330     uint16_t new_offset = data_offset + bytes_to_store;
331     if (new_offset > buffer_size){
332         return OBEX_APP_PARAM_PARSER_TAG_OVERRUN;
333     } else if (new_offset == total_len) {
334         return OBEX_APP_PARAM_PARSER_TAG_COMPLETE;
335     } else {
336         return OBEX_APP_PARAM_PARSER_TAG_INCOMPLETE;
337     }
338 }
339