xref: /btstack/src/classic/obex_parser.c (revision e7b3edb089b52f2a8face7a684cd9e6e1d6357fa)
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 #ifdef ENABLE_LOG_OBEX
49     #define RETURN_OBEX_STATE(state) { log_debug("RETURN_OBEX_STATE <%s>(%d)", #state, state); return state; }
50 #else
51     #define log_app_messaging(...)
52     #define RETURN_OBEX_STATE(state) return state
53 #endif
54 
obex_parser_param_size_for_request(uint8_t opcode)55 static uint16_t obex_parser_param_size_for_request(uint8_t opcode){
56     switch (opcode) {
57         case OBEX_OPCODE_SETPATH:
58             return 4;
59         case OBEX_OPCODE_CONNECT:
60             return 6;
61         default:
62             return 2;
63     }
64 }
65 
obex_parser_param_size_for_response(uint8_t opcode)66 static uint16_t obex_parser_param_size_for_response(uint8_t opcode){
67     switch (opcode) {
68         case OBEX_OPCODE_CONNECT:
69             return 6;
70         default:
71             return 2;
72     }
73 }
74 
obex_parser_init(obex_parser_t * obex_parser,obex_parser_callback_t obex_parser_callback,void * user_data)75 static void obex_parser_init(obex_parser_t * obex_parser, obex_parser_callback_t obex_parser_callback, void * user_data){
76     memset(obex_parser, 0, sizeof(obex_parser_t));
77     obex_parser->packet_size = 3;
78     obex_parser->user_data = user_data;
79     obex_parser->callback = obex_parser_callback;
80 }
81 
obex_parser_init_for_request(obex_parser_t * obex_parser,obex_parser_callback_t obex_parser_callback,void * user_data)82 void obex_parser_init_for_request(obex_parser_t * obex_parser, obex_parser_callback_t obex_parser_callback, void * user_data){
83     obex_parser_init(obex_parser, obex_parser_callback, user_data);
84     obex_parser->state = OBEX_PARSER_STATE_W4_OPCODE;
85 }
86 
obex_parser_init_for_response(obex_parser_t * obex_parser,uint8_t opcode,obex_parser_callback_t obex_parser_callback,void * user_data)87 void obex_parser_init_for_response(obex_parser_t * obex_parser, uint8_t opcode, obex_parser_callback_t obex_parser_callback, void * user_data){
88     obex_parser_init(obex_parser, obex_parser_callback, user_data);
89     obex_parser->state = OBEX_PARSER_STATE_W4_RESPONSE_CODE;
90     obex_parser->opcode = opcode;
91 }
92 
obex_parser_process_data(obex_parser_t * obex_parser,const uint8_t * data_buffer,uint16_t data_len)93 obex_parser_object_state_t obex_parser_process_data(obex_parser_t *obex_parser, const uint8_t *data_buffer, uint16_t data_len) {
94     while (data_len){
95         uint16_t bytes_to_consume = 1;
96         uint8_t  header_type;
97         switch (obex_parser->state){
98             case OBEX_PARSER_STATE_W4_OPCODE:
99                 obex_parser->opcode = *data_buffer;
100                 obex_parser->state = OBEX_PARSER_STATE_W4_PACKET_LEN;
101                 obex_parser->item_len = obex_parser_param_size_for_request(obex_parser->opcode);
102                 obex_parser->packet_size = 1 + obex_parser->item_len;
103                 break;
104             case OBEX_PARSER_STATE_W4_RESPONSE_CODE:
105                 obex_parser->response_code = *data_buffer;
106                 obex_parser->state = OBEX_PARSER_STATE_W4_PACKET_LEN;
107                 obex_parser->item_len = obex_parser_param_size_for_response(obex_parser->opcode);
108                 obex_parser->packet_size = 1 + obex_parser->item_len;
109                 break;
110             case OBEX_PARSER_STATE_W4_PACKET_LEN:
111                 bytes_to_consume = btstack_min(2 - obex_parser->item_pos, data_len);
112                 memcpy(&obex_parser->params[obex_parser->item_pos], data_buffer, bytes_to_consume);
113                 obex_parser->item_pos += bytes_to_consume;
114                 if (obex_parser->item_pos == 2){
115                     // validate packet large enough for header
116                     obex_parser->packet_size = big_endian_read_16(obex_parser->params, 0);
117                     if (obex_parser->packet_size < (obex_parser->item_len + 1)){
118                         // packet size smaller than opcode + params
119                         obex_parser->state = OBEX_PARSER_STATE_INVALID;
120                         break;
121                     }
122                     // params already complete?
123                     if (obex_parser->item_len == 2){
124                         obex_parser->state = OBEX_PARSER_STATE_W4_HEADER_ID;
125                     } else {
126                         obex_parser->state = OBEX_PARSER_STATE_W4_PARAMS;
127                     }
128                 }
129                 break;
130             case OBEX_PARSER_STATE_W4_PARAMS:
131                 bytes_to_consume = btstack_min(obex_parser->item_len - obex_parser->item_pos, data_len);
132                 memcpy(&obex_parser->params[obex_parser->item_pos], data_buffer, bytes_to_consume);
133                 obex_parser->item_pos += bytes_to_consume;
134                 if (obex_parser->item_pos == obex_parser->item_len){
135                     obex_parser->state = OBEX_PARSER_STATE_W4_HEADER_ID;
136                 }
137                 break;
138             case OBEX_PARSER_STATE_W4_HEADER_ID:
139                 obex_parser->header_id = *data_buffer; // constants in obex.h encode type as well, so just use these as well
140                 header_type = *data_buffer >> 6;
141                 obex_parser->item_pos = 0;
142                 switch (header_type){
143                     case 0:
144                     case 1:
145                         // 16-bit length info prefixed
146                         obex_parser->item_len = 2;
147                         obex_parser->state = OBEX_PARSER_STATE_W4_HEADER_LEN_FIRST;
148                         break;
149                     case 2:
150                         // 8-bit value
151                         obex_parser->item_len = 1;
152                         obex_parser->state = OBEX_PARSER_STATE_W4_HEADER_VALUE;
153                         break;
154                     case 3:
155                         // 32-bit value
156                         obex_parser->item_len = 4;
157                         obex_parser->state = OBEX_PARSER_STATE_W4_HEADER_VALUE;
158                         break;
159                     default:
160                         // avoid compiler warning about unused cases (encoding in [0..3])
161                         break;
162                 }
163                 break;
164             case OBEX_PARSER_STATE_W4_HEADER_LEN_FIRST:
165                 obex_parser->item_len = *data_buffer << 8;
166                 obex_parser->state = OBEX_PARSER_STATE_W4_HEADER_LEN_SECOND;
167                 break;
168             case OBEX_PARSER_STATE_W4_HEADER_LEN_SECOND:
169                 obex_parser->item_len = obex_parser->item_len + *data_buffer;
170                 if (obex_parser->item_len < 3){
171                     // len to small to even cover header
172                     obex_parser->state = OBEX_PARSER_STATE_INVALID;
173                     break;
174                 };
175                 if (obex_parser->item_len == 3){
176                     // borderline: empty value
177                     obex_parser->state = OBEX_PARSER_STATE_W4_HEADER_ID;
178                     break;
179                 }
180                 obex_parser->item_len -= 3;
181                 obex_parser->state = OBEX_PARSER_STATE_W4_HEADER_VALUE;
182                 break;
183             case OBEX_PARSER_STATE_W4_HEADER_VALUE:
184                 bytes_to_consume = btstack_min(obex_parser->item_len - obex_parser->item_pos, data_len);
185                 if (*obex_parser->callback != NULL){
186                     (*obex_parser->callback)(obex_parser->user_data, obex_parser->header_id, obex_parser->item_len, obex_parser->item_pos, data_buffer, bytes_to_consume);
187                 }
188                 obex_parser->item_pos += bytes_to_consume;
189                 if (obex_parser->item_pos == obex_parser->item_len){
190                     obex_parser->state = OBEX_PARSER_STATE_W4_HEADER_ID;
191                 }
192                 break;
193             case OBEX_PARSER_STATE_COMPLETE:
194                 obex_parser->state = OBEX_PARSER_STATE_OVERRUN;
195                 break;
196             case OBEX_PARSER_STATE_INVALID:
197                 break;
198             default:
199                 btstack_unreachable();
200                 break;
201         }
202 
203         data_buffer += bytes_to_consume;
204         data_len    -= bytes_to_consume;
205         obex_parser->packet_pos += bytes_to_consume;
206 
207         // all bytes read? then check state
208         if (obex_parser->packet_pos == obex_parser->packet_size){
209             if (obex_parser->state == OBEX_PARSER_STATE_W4_HEADER_ID){
210                 obex_parser->state = OBEX_PARSER_STATE_COMPLETE;
211             } else {
212                 obex_parser->state = OBEX_PARSER_STATE_INVALID;
213             }
214         }
215     }
216 
217     switch (obex_parser->state){
218         case OBEX_PARSER_STATE_COMPLETE:
219             RETURN_OBEX_STATE(OBEX_PARSER_OBJECT_STATE_COMPLETE);
220         case OBEX_PARSER_STATE_OVERRUN:
221             RETURN_OBEX_STATE(OBEX_PARSER_OBJECT_STATE_OVERRUN);
222         case OBEX_PARSER_STATE_INVALID:
223             RETURN_OBEX_STATE(OBEX_PARSER_OBJECT_STATE_INVALID);
224         default:
225             RETURN_OBEX_STATE(OBEX_PARSER_OBJECT_STATE_INCOMPLETE);
226     }
227 }
228 
obex_parser_get_operation_info(obex_parser_t * obex_parser,obex_parser_operation_info_t * obex_operation_info)229 void obex_parser_get_operation_info(obex_parser_t * obex_parser, obex_parser_operation_info_t * obex_operation_info){
230     memset(obex_operation_info, 0, sizeof(obex_parser_operation_info_t));
231     obex_operation_info->opcode = obex_parser->opcode;
232     obex_operation_info->response_code = obex_parser->response_code;
233     switch (obex_parser->opcode){
234         case OBEX_OPCODE_CONNECT:
235             obex_operation_info->obex_version_number = obex_parser->params[2];
236             obex_operation_info->flags = obex_parser->params[3];
237             obex_operation_info->max_packet_length = big_endian_read_16(obex_parser->params, 4);
238             break;
239         case OBEX_OPCODE_SETPATH:
240             obex_operation_info->flags = obex_parser->params[2];
241             break;
242         default:
243             break;
244     }
245 }
obex_parser_header_store(uint8_t * header_buffer,uint16_t buffer_size,uint16_t total_len,uint16_t data_offset,const uint8_t * data_buffer,uint16_t data_len)246 obex_parser_header_state_t obex_parser_header_store(uint8_t * header_buffer, uint16_t buffer_size, uint16_t total_len,
247                                                     uint16_t data_offset,  const uint8_t * data_buffer, uint16_t data_len){
248     uint16_t bytes_to_store = btstack_min(buffer_size - data_offset, data_len);
249     memcpy(&header_buffer[data_offset], data_buffer, bytes_to_store);
250     uint16_t new_offset = data_offset + bytes_to_store;
251     if (new_offset > buffer_size){
252         RETURN_OBEX_STATE(OBEX_PARSER_HEADER_OVERRUN);
253     } else if (new_offset == total_len) {
254         RETURN_OBEX_STATE(OBEX_PARSER_HEADER_COMPLETE);
255     } else {
256         RETURN_OBEX_STATE(OBEX_PARSER_HEADER_INCOMPLETE);
257     }
258 }
259 
260 
261 /* OBEX App Param Parser */
262 
obex_app_param_parser_init(obex_app_param_parser_t * parser,obex_app_param_parser_callback_t callback,uint16_t param_size,void * user_data)263 void obex_app_param_parser_init(obex_app_param_parser_t * parser, obex_app_param_parser_callback_t callback, uint16_t param_size, void * user_data){
264     parser->state = OBEX_APP_PARAM_PARSER_STATE_W4_TYPE;
265     parser->callback = callback;
266     parser->user_data = user_data;
267     parser->param_size = param_size;
268     parser->param_pos = 0;
269 }
270 
obex_app_param_parser_process_data(obex_app_param_parser_t * parser,const uint8_t * data_buffer,uint16_t data_len)271 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){
272     while ((data_len > 0) && (parser->param_pos < parser->param_size)){
273         uint16_t bytes_to_consume = 1;
274         switch(parser->state){
275             case OBEX_APP_PARAM_PARSER_STATE_INVALID:
276                 RETURN_OBEX_STATE(OBEX_APP_PARAM_PARSER_PARAMS_STATE_INVALID);
277             case OBEX_APP_PARAM_PARSER_STATE_W4_TYPE:
278                 parser->tag_id = *data_buffer;
279                 parser->state = OBEX_APP_PARAM_PARSER_STATE_W4_LEN;
280                 break;
281             case OBEX_APP_PARAM_PARSER_STATE_W4_LEN:
282                 parser->tag_len = *data_buffer;
283                 if ((parser->param_pos + parser->tag_len) > parser->param_size){
284                     parser->state = OBEX_APP_PARAM_PARSER_STATE_INVALID;
285                     RETURN_OBEX_STATE(OBEX_APP_PARAM_PARSER_PARAMS_STATE_INVALID);
286                 }
287                 parser->tag_pos = 0;
288                 parser->state = OBEX_APP_PARAM_PARSER_STATE_W4_VALUE;
289                 break;
290             case OBEX_APP_PARAM_PARSER_STATE_W4_VALUE:
291                 bytes_to_consume = btstack_min(parser->tag_len - parser->tag_pos, data_len);
292 				// param_size is uint8_t
293                 (*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);
294                 parser->tag_pos   += bytes_to_consume;
295                 if (parser->tag_pos == parser->tag_len){
296                     parser->state = OBEX_APP_PARAM_PARSER_STATE_W4_TYPE;
297                 }
298                 break;
299             default:
300                 btstack_unreachable();
301                 break;
302         }
303 
304         data_buffer += bytes_to_consume;
305         data_len    -= bytes_to_consume;
306         parser->param_pos += bytes_to_consume;
307 
308         // all bytes read? then check state
309         if (parser->param_pos == parser->param_size){
310             if (parser->state == OBEX_APP_PARAM_PARSER_STATE_W4_TYPE){
311                 parser->state = OBEX_APP_PARAM_PARSER_STATE_COMPLETE;
312             } else {
313                 parser->state = OBEX_APP_PARAM_PARSER_STATE_INVALID;
314             }
315         }
316     }
317 
318     if (data_len > 0){
319         RETURN_OBEX_STATE(OBEX_APP_PARAM_PARSER_PARAMS_STATE_OVERRUN);
320     }
321 
322     switch (parser->state){
323         case OBEX_APP_PARAM_PARSER_STATE_COMPLETE:
324             RETURN_OBEX_STATE(OBEX_APP_PARAM_PARSER_PARAMS_STATE_COMPLETE);
325         case OBEX_APP_PARAM_PARSER_STATE_INVALID:
326             RETURN_OBEX_STATE(OBEX_APP_PARAM_PARSER_PARAMS_STATE_INVALID);
327         default:
328             RETURN_OBEX_STATE(OBEX_APP_PARAM_PARSER_PARAMS_STATE_INCOMPLETE);
329     }
330 }
331 
332 
obex_app_param_parser_tag_store(uint8_t * header_buffer,uint8_t buffer_size,uint8_t total_len,uint8_t data_offset,const uint8_t * data_buffer,uint8_t data_len)333 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,
334                                                                          uint8_t data_offset, const uint8_t * data_buffer, uint8_t data_len){
335     uint16_t bytes_to_store = btstack_min(buffer_size - data_offset, data_len);
336     memcpy(&header_buffer[data_offset], data_buffer, bytes_to_store);
337     uint16_t new_offset = data_offset + bytes_to_store;
338     if (new_offset > buffer_size){
339         RETURN_OBEX_STATE(OBEX_APP_PARAM_PARSER_TAG_OVERRUN);
340     } else if (new_offset == total_len) {
341         RETURN_OBEX_STATE(OBEX_APP_PARAM_PARSER_TAG_COMPLETE);
342     } else {
343         RETURN_OBEX_STATE(OBEX_APP_PARAM_PARSER_TAG_INCOMPLETE);
344     }
345 }
346