mirror of
https://github.com/bluekitchen/btstack.git
synced 2025-03-14 01:27:41 +00:00
obex: add stream-based obex parser and tests
This commit is contained in:
parent
eea7024143
commit
ac9a0d84d5
244
src/classic/obex_parser.c
Normal file
244
src/classic/obex_parser.c
Normal file
@ -0,0 +1,244 @@
|
||||
/*
|
||||
* Copyright (C) 2021 BlueKitchen GmbH
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* 3. Neither the name of the copyright holders nor the names of
|
||||
* contributors may be used to endorse or promote products derived
|
||||
* from this software without specific prior written permission.
|
||||
* 4. Any redistribution, use, or modification is done solely for
|
||||
* personal benefit and not for any commercial purpose or for
|
||||
* monetary gain.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY BLUEKITCHEN GMBH AND CONTRIBUTORS
|
||||
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
||||
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BLUEKITCHEN
|
||||
* GMBH OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
||||
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
|
||||
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
|
||||
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
|
||||
* THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||
* SUCH DAMAGE.
|
||||
*
|
||||
* Please inquire about commercial licensing options at
|
||||
* contact@bluekitchen-gmbh.com
|
||||
*
|
||||
*/
|
||||
|
||||
#define BTSTACK_FILE__ "obex_parser.c"
|
||||
|
||||
#include "btstack_config.h"
|
||||
|
||||
#include "classic/obex.h"
|
||||
#include "classic/obex_parser.h"
|
||||
#include <string.h>
|
||||
#include "btstack_debug.h"
|
||||
#include "btstack_util.h"
|
||||
|
||||
static uint16_t obex_parser_param_size_for_request(uint8_t opcode){
|
||||
switch (opcode) {
|
||||
case OBEX_OPCODE_SETPATH:
|
||||
return 4;
|
||||
case OBEX_OPCODE_CONNECT:
|
||||
return 6;
|
||||
default:
|
||||
return 2;
|
||||
}
|
||||
}
|
||||
|
||||
static uint16_t obex_parser_param_size_for_response(uint8_t opcode){
|
||||
switch (opcode) {
|
||||
case OBEX_OPCODE_CONNECT:
|
||||
return 6;
|
||||
default:
|
||||
return 2;
|
||||
}
|
||||
}
|
||||
|
||||
static void obex_parser_init(obex_parser_t * obex_parser, obex_parser_callback_t obex_parser_callback, void * user_data){
|
||||
memset(obex_parser, 0, sizeof(obex_parser_t));
|
||||
obex_parser->packet_size = 3;
|
||||
obex_parser->user_data = user_data;
|
||||
obex_parser->callback = obex_parser_callback;
|
||||
}
|
||||
|
||||
void obex_parser_init_for_request(obex_parser_t * obex_parser, obex_parser_callback_t obex_parser_callback, void * user_data){
|
||||
obex_parser_init(obex_parser, obex_parser_callback, user_data);
|
||||
obex_parser->state = OBEX_PARSER_STATE_W4_OPCODE;
|
||||
}
|
||||
|
||||
void obex_parser_init_for_response(obex_parser_t * obex_parser, uint8_t opcode, obex_parser_callback_t obex_parser_callback, void * user_data){
|
||||
obex_parser_init(obex_parser, obex_parser_callback, user_data);
|
||||
obex_parser->state = OBEX_PARSER_STATE_W4_RESPONSE_CODE;
|
||||
obex_parser->opcode = opcode;
|
||||
}
|
||||
|
||||
obex_parser_object_state_t obex_parser_process_data(obex_parser_t *obex_parser, const uint8_t *data_buffer, uint16_t data_len) {
|
||||
while (data_len){
|
||||
uint16_t bytes_to_consume = 1;
|
||||
uint8_t header_type;
|
||||
switch (obex_parser->state){
|
||||
case OBEX_PARSER_STATE_W4_OPCODE:
|
||||
obex_parser->opcode = *data_buffer;
|
||||
obex_parser->state = OBEX_PARSER_STATE_W4_PACKET_LEN;
|
||||
obex_parser->item_len = obex_parser_param_size_for_request(obex_parser->opcode);
|
||||
obex_parser->packet_size = 1 + obex_parser->item_len;
|
||||
break;
|
||||
case OBEX_PARSER_STATE_W4_RESPONSE_CODE:
|
||||
obex_parser->response_code = *data_buffer;
|
||||
obex_parser->state = OBEX_PARSER_STATE_W4_PACKET_LEN;
|
||||
obex_parser->item_len = obex_parser_param_size_for_response(obex_parser->opcode);
|
||||
obex_parser->packet_size = 1 + obex_parser->item_len;
|
||||
break;
|
||||
case OBEX_PARSER_STATE_W4_PACKET_LEN:
|
||||
bytes_to_consume = btstack_min(2 - obex_parser->item_pos, data_len);
|
||||
memcpy(&obex_parser->params[obex_parser->item_pos], data_buffer, bytes_to_consume);
|
||||
obex_parser->item_pos += bytes_to_consume;
|
||||
if (obex_parser->item_pos == 2){
|
||||
// validate packet large enough for header
|
||||
obex_parser->packet_size = big_endian_read_16(obex_parser->params, 0);
|
||||
if (obex_parser->packet_size < (obex_parser->item_len + 1)){
|
||||
// packet size smaller than opcode + params
|
||||
obex_parser->state = OBEX_PARSER_STATE_INVALID;
|
||||
break;
|
||||
}
|
||||
// params already complete?
|
||||
if (obex_parser->item_len == 2){
|
||||
obex_parser->state = OBEX_PARSER_STATE_W4_HEADER_ID;
|
||||
} else {
|
||||
obex_parser->state = OBEX_PARSER_STATE_W4_PARAMS;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case OBEX_PARSER_STATE_W4_PARAMS:
|
||||
bytes_to_consume = btstack_min(obex_parser->item_len - obex_parser->item_pos, data_len);
|
||||
memcpy(&obex_parser->params[obex_parser->item_pos], data_buffer, bytes_to_consume);
|
||||
obex_parser->item_pos += bytes_to_consume;
|
||||
if (obex_parser->item_pos == obex_parser->item_len){
|
||||
obex_parser->state = OBEX_PARSER_STATE_W4_HEADER_ID;
|
||||
}
|
||||
break;
|
||||
case OBEX_PARSER_STATE_W4_HEADER_ID:
|
||||
obex_parser->header_id = *data_buffer; // constants in obex.h encode type as well, so just use these as well
|
||||
header_type = *data_buffer >> 6;
|
||||
obex_parser->item_pos = 0;
|
||||
switch (header_type){
|
||||
case 0:
|
||||
case 1:
|
||||
// 16-bit length info prefixed
|
||||
obex_parser->item_len = 2;
|
||||
obex_parser->state = OBEX_PARSER_STATE_W4_HEADER_LEN_FIRST;
|
||||
break;
|
||||
case 2:
|
||||
// 8-bit value
|
||||
obex_parser->item_len = 1;
|
||||
obex_parser->state = OBEX_PARSER_STATE_W4_HEADER_VALUE;
|
||||
break;
|
||||
case 3:
|
||||
// 32-bit value
|
||||
obex_parser->item_len = 4;
|
||||
obex_parser->state = OBEX_PARSER_STATE_W4_HEADER_VALUE;
|
||||
break;
|
||||
default:
|
||||
// avoid compiler warning about unused cases (encoding in [0..3])
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case OBEX_PARSER_STATE_W4_HEADER_LEN_FIRST:
|
||||
obex_parser->item_len = *data_buffer << 8;
|
||||
obex_parser->state = OBEX_PARSER_STATE_W4_HEADER_LEN_SECOND;
|
||||
break;
|
||||
case OBEX_PARSER_STATE_W4_HEADER_LEN_SECOND:
|
||||
obex_parser->item_len = obex_parser->item_len + *data_buffer - 3;
|
||||
if ( obex_parser->item_len > 0){
|
||||
obex_parser->state = OBEX_PARSER_STATE_W4_HEADER_VALUE;
|
||||
} else {
|
||||
obex_parser->state = OBEX_PARSER_STATE_W4_HEADER_ID;
|
||||
}
|
||||
break;
|
||||
case OBEX_PARSER_STATE_W4_HEADER_VALUE:
|
||||
bytes_to_consume = btstack_min(obex_parser->item_len - obex_parser->item_pos, data_len);
|
||||
if (*obex_parser->callback != NULL){
|
||||
(*obex_parser->callback)(obex_parser->user_data, obex_parser->header_id, obex_parser->item_len, obex_parser->item_pos, data_buffer, bytes_to_consume);
|
||||
}
|
||||
obex_parser->item_pos += bytes_to_consume;
|
||||
if (obex_parser->item_pos == obex_parser->item_len){
|
||||
obex_parser->state = OBEX_PARSER_STATE_W4_HEADER_ID;
|
||||
}
|
||||
break;
|
||||
case OBEX_PARSER_STATE_COMPLETE:
|
||||
obex_parser->state = OBEX_PARSER_STATE_OVERRUN;
|
||||
break;
|
||||
case OBEX_PARSER_STATE_INVALID:
|
||||
break;
|
||||
default:
|
||||
btstack_unreachable();
|
||||
break;
|
||||
}
|
||||
|
||||
data_buffer += bytes_to_consume;
|
||||
data_len -= bytes_to_consume;
|
||||
obex_parser->packet_pos += bytes_to_consume;
|
||||
|
||||
// all bytes read? then check state
|
||||
if (obex_parser->packet_pos == obex_parser->packet_size){
|
||||
if (obex_parser->state == OBEX_PARSER_STATE_W4_HEADER_ID){
|
||||
obex_parser->state = OBEX_PARSER_STATE_COMPLETE;
|
||||
} else {
|
||||
obex_parser->state = OBEX_PARSER_STATE_INVALID;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
switch (obex_parser->state){
|
||||
case OBEX_PARSER_STATE_COMPLETE:
|
||||
return OBEX_PARSER_OBJECT_STATE_COMPLETE;
|
||||
case OBEX_PARSER_STATE_OVERRUN:
|
||||
return OBEX_PARSER_OBJECT_STATE_OVERRUN;
|
||||
case OBEX_PARSER_STATE_INVALID:
|
||||
return OBEX_PARSER_OBJECT_STATE_INVALID;
|
||||
default:
|
||||
return OBEX_PARSER_OBJECT_STATE_INCOMPLETE;
|
||||
}
|
||||
}
|
||||
|
||||
void obex_parser_get_operation_info(obex_parser_t * obex_parser, obex_parser_operation_info_t * obex_operation_info){
|
||||
memset(obex_operation_info, 0, sizeof(obex_parser_operation_info_t));
|
||||
obex_operation_info->opcode = obex_parser->opcode;
|
||||
obex_operation_info->response_code = obex_parser->response_code;
|
||||
switch (obex_parser->opcode){
|
||||
case OBEX_OPCODE_CONNECT:
|
||||
obex_operation_info->obex_version_number = obex_parser->params[2];
|
||||
obex_operation_info->flags = obex_parser->params[3];
|
||||
obex_operation_info->max_packet_length = big_endian_read_16(obex_parser->params, 4);
|
||||
break;
|
||||
case OBEX_OPCODE_SETPATH:
|
||||
obex_operation_info->flags = obex_parser->params[2];
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
obex_parser_header_state_t 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){
|
||||
uint16_t bytes_to_store = btstack_min(buffer_size - data_offset, data_len);
|
||||
memcpy(&header_buffer[data_offset], data_buffer, bytes_to_store);
|
||||
uint16_t new_offset = data_offset + bytes_to_store;
|
||||
if (new_offset > buffer_size){
|
||||
return OBEX_PARSER_HEADER_OVERRUN;
|
||||
} else if (new_offset == total_len) {
|
||||
return OBEX_PARSER_HEADER_COMPLETE;
|
||||
} else {
|
||||
return OBEX_PARSER_HEADER_INCOMPLETE;
|
||||
}
|
||||
}
|
168
src/classic/obex_parser.h
Normal file
168
src/classic/obex_parser.h
Normal file
@ -0,0 +1,168 @@
|
||||
/*
|
||||
* Copyright (C) 2021 BlueKitchen GmbH
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* 3. Neither the name of the copyright holders nor the names of
|
||||
* contributors may be used to endorse or promote products derived
|
||||
* from this software without specific prior written permission.
|
||||
* 4. Any redistribution, use, or modification is done solely for
|
||||
* personal benefit and not for any commercial purpose or for
|
||||
* monetary gain.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY BLUEKITCHEN GMBH AND CONTRIBUTORS
|
||||
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
||||
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BLUEKITCHEN
|
||||
* GMBH OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
||||
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
|
||||
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
|
||||
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
|
||||
* THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||
* SUCH DAMAGE.
|
||||
*
|
||||
* Please inquire about commercial licensing options at
|
||||
* contact@bluekitchen-gmbh.com
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* OBEX Parser
|
||||
* Parser incoming arbitrarily chunked OBEX object
|
||||
*/
|
||||
|
||||
#ifndef OBEX_PARSER_H
|
||||
#define OBEX_PARSER_H
|
||||
|
||||
#if defined __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
typedef enum {
|
||||
OBEX_PARSER_STATE_W4_OPCODE,
|
||||
OBEX_PARSER_STATE_W4_RESPONSE_CODE,
|
||||
OBEX_PARSER_STATE_W4_PACKET_LEN,
|
||||
OBEX_PARSER_STATE_W4_PARAMS,
|
||||
OBEX_PARSER_STATE_W4_HEADER_ID,
|
||||
OBEX_PARSER_STATE_W4_HEADER_LEN_FIRST,
|
||||
OBEX_PARSER_STATE_W4_HEADER_LEN_SECOND,
|
||||
OBEX_PARSER_STATE_W4_HEADER_VALUE,
|
||||
OBEX_PARSER_STATE_COMPLETE,
|
||||
OBEX_PARSER_STATE_OVERRUN,
|
||||
OBEX_PARSER_STATE_INVALID,
|
||||
} obex_parser_state_t;
|
||||
|
||||
/* API_START */
|
||||
|
||||
/**
|
||||
* Callback to process chunked data
|
||||
* @param user_data provided in obex_parser_init
|
||||
* @param header_id current OBEX header ID
|
||||
* @param total_len of header
|
||||
* @param data_offset
|
||||
* @param data_len
|
||||
* @param data_buffer
|
||||
*/
|
||||
typedef void (*obex_parser_callback_t)(void * user_data, uint8_t header_id, uint16_t total_len, uint16_t data_offset, const uint8_t * data_buffer, uint16_t data_len);
|
||||
|
||||
typedef enum {
|
||||
OBEX_PARSER_OBJECT_STATE_INCOMPLETE,
|
||||
OBEX_PARSER_OBJECT_STATE_COMPLETE,
|
||||
OBEX_PARSER_OBJECT_STATE_OVERRUN,
|
||||
OBEX_PARSER_OBJECT_STATE_INVALID,
|
||||
} obex_parser_object_state_t;
|
||||
|
||||
typedef enum {
|
||||
OBEX_PARSER_HEADER_INCOMPLETE,
|
||||
OBEX_PARSER_HEADER_COMPLETE,
|
||||
OBEX_PARSER_HEADER_OVERRUN,
|
||||
} obex_parser_header_state_t;
|
||||
|
||||
typedef struct {
|
||||
uint8_t opcode;
|
||||
uint8_t response_code;
|
||||
// Connect only
|
||||
uint8_t obex_version_number;
|
||||
uint16_t max_packet_length;
|
||||
// Connect + Set Path only
|
||||
uint8_t flags;
|
||||
} obex_parser_operation_info_t;
|
||||
|
||||
typedef struct {
|
||||
obex_parser_callback_t callback;
|
||||
void * user_data;
|
||||
uint16_t packet_size;
|
||||
uint16_t packet_pos;
|
||||
obex_parser_state_t state;
|
||||
uint8_t opcode;
|
||||
uint8_t response_code;
|
||||
uint16_t item_len;
|
||||
uint16_t item_pos;
|
||||
uint8_t params[6]; // for connect and set path
|
||||
uint8_t header_id;
|
||||
} obex_parser_t;
|
||||
|
||||
/**
|
||||
* Initialize OBEX Parser for next OBEX request
|
||||
* @param obex_parser
|
||||
* @param function to call for fields that are not registered
|
||||
* @param user_data provided to callback function
|
||||
*/
|
||||
void obex_parser_init_for_request(obex_parser_t * obex_parser, obex_parser_callback_t obex_parser_callback, void * user_data);
|
||||
|
||||
/**
|
||||
* Initialize OBEX Parser for next OBEX response
|
||||
* @param obex_parser
|
||||
* @param opcode of request - needed as responses with additional fields like connect and set path
|
||||
* @param function to call for fields that are not registered
|
||||
* @param user_data provided to callback function
|
||||
*/
|
||||
void obex_parser_init_for_response(obex_parser_t * obex_parser, uint8_t opcode, obex_parser_callback_t obex_parser_callback, void * user_data);
|
||||
|
||||
/**
|
||||
* Process OBEX data
|
||||
* @param obex_parser
|
||||
* @param data_len
|
||||
* @param data_buffer
|
||||
* @return OBEX_PARSER_OBJECT_STATE_COMPLETE if packet has been completely parsed
|
||||
*/
|
||||
obex_parser_object_state_t obex_parser_process_data(obex_parser_t *obex_parser, const uint8_t *data_buffer, uint16_t data_len);
|
||||
|
||||
/**
|
||||
* Get operation info for request/response packets
|
||||
* @param obex_parser
|
||||
* @return
|
||||
*/
|
||||
void obex_parser_get_operation_info(obex_parser_t * obex_parser, obex_parser_operation_info_t * obex_operation_info);
|
||||
|
||||
/**
|
||||
* Helper to collect header chunks in fixed-size header buffer
|
||||
* @param header_buffer
|
||||
* @param buffer_size of header_buffer
|
||||
* @param total_len of header value
|
||||
* @param data_offset of chunkc to store
|
||||
* @param data_buffer
|
||||
* @param data_len chunk length
|
||||
* @return OBEX_PARSER_HEADER_COMPLETE when header value complete
|
||||
*/
|
||||
obex_parser_header_state_t 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);
|
||||
|
||||
|
||||
/* API_END */
|
||||
|
||||
#if defined __cplusplus
|
||||
}
|
||||
#endif
|
||||
#endif
|
@ -18,8 +18,8 @@ VPATH += ${BTSTACK_ROOT}/platform/posix
|
||||
|
||||
COMMON = \
|
||||
btstack_util.c \
|
||||
hci_dump.c \
|
||||
obex_message_builder.c \
|
||||
obex_parser.c
|
||||
|
||||
CFLAGS_COVERAGE = ${CFLAGS} -fprofile-arcs -ftest-coverage
|
||||
CFLAGS_ASAN = ${CFLAGS} -fsanitize=address -DHAVE_ASSERT
|
||||
@ -31,7 +31,8 @@ LDFLAGS_ASAN = ${LDFLAGS} -fsanitize=address
|
||||
COMMON_OBJ_COVERAGE = $(addprefix build-coverage/,$(COMMON:.c=.o))
|
||||
COMMON_OBJ_ASAN = $(addprefix build-asan/, $(COMMON:.c=.o))
|
||||
|
||||
all: build-coverage/obex_message_builder_test build-asan/obex_message_builder_test
|
||||
all: build-coverage/obex_message_builder_test build-asan/obex_message_builder_test \
|
||||
build-coverage/obex_parser_test build-asan/obex_parser_test
|
||||
|
||||
build-%:
|
||||
mkdir -p $@
|
||||
@ -42,6 +43,12 @@ build-coverage/%.o: %.c | build-coverage
|
||||
build-asan/%.o: %.c | build-asan
|
||||
${CC} -c $(CFLAGS_ASAN) $< -o $@
|
||||
|
||||
build-coverage/%.cpp.o: %.cpp | build-coverage
|
||||
${CC} -c $(CFLAGS_COVERAGE) $< -o $@
|
||||
|
||||
build-asan/%.cpp.o: %.cpp | build-asan
|
||||
${CC} -c $(CFLAGS_ASAN) $< -o $@
|
||||
|
||||
|
||||
build-coverage/obex_message_builder_test: ${COMMON_OBJ_COVERAGE} build-coverage/obex_message_builder_test.o | build-coverage
|
||||
${CC} $^ ${LDFLAGS_COVERAGE} -o $@
|
||||
@ -49,12 +56,20 @@ build-coverage/obex_message_builder_test: ${COMMON_OBJ_COVERAGE} build-coverage/
|
||||
build-asan/obex_message_builder_test: ${COMMON_OBJ_ASAN} build-asan/obex_message_builder_test.o | build-asan
|
||||
${CC} $^ ${LDFLAGS_ASAN} -o $@
|
||||
|
||||
build-coverage/obex_parser_test: ${COMMON_OBJ_COVERAGE} build-coverage/obex_parser_test.cpp.o | build-coverage
|
||||
${CC} $^ ${LDFLAGS_COVERAGE} -o $@
|
||||
|
||||
build-asan/obex_parser_test: ${COMMON_OBJ_ASAN} build-asan/obex_parser_test.cpp.o | build-asan
|
||||
${CC} $^ ${LDFLAGS_ASAN} -o $@
|
||||
|
||||
test: all
|
||||
build-asan/obex_message_builder_test
|
||||
|
||||
build-asan/obex_parser_test
|
||||
|
||||
coverage: all
|
||||
rm -f build-coverage/*.gcda
|
||||
build-coverage/obex_message_builder_test
|
||||
build-coverage/obex_parser_test
|
||||
|
||||
clean:
|
||||
rm -rf build-coverage build-asan
|
||||
|
@ -6,6 +6,9 @@
|
||||
#include "classic/obex.h"
|
||||
#include "classic/obex_message_builder.h"
|
||||
|
||||
// mock hci_dump.c
|
||||
extern "C" void hci_dump_log(int log_level, const char * format, ...){}
|
||||
|
||||
static const uint8_t service_uuid[] = {0xbb, 0x58, 0x2b, 0x40, 0x42, 0xc, 0x11, 0xdb, 0xb0, 0xde, 0x8, 0x0, 0x20, 0xc, 0x9a, 0x66};
|
||||
static const uint8_t application_parameters[] = {0x29, 4, 0, 0, 0xFF, 0xFF};
|
||||
static const char path_element[] = "test";
|
||||
|
142
test/obex/obex_parser_test.cpp
Normal file
142
test/obex/obex_parser_test.cpp
Normal file
@ -0,0 +1,142 @@
|
||||
#include "CppUTest/TestHarness.h"
|
||||
#include "CppUTest/CommandLineTestRunner.h"
|
||||
#include "CppUTestExt/MockSupport.h"
|
||||
|
||||
#include "classic/obex.h"
|
||||
#include "classic/obex_parser.h"
|
||||
#include "classic/obex_message_builder.h"
|
||||
#include "btstack_util.h"
|
||||
|
||||
static const uint8_t flags = 1 << 1;
|
||||
static const uint16_t maximum_obex_packet_length = 0xFFFF;
|
||||
static const uint8_t obex_version_number = OBEX_VERSION;
|
||||
static const uint8_t target[] = { 1, 2, 3, 4};
|
||||
|
||||
// from parser_callback
|
||||
static uint8_t test_header_id;
|
||||
static uint8_t test_header_buffer[100];
|
||||
static uint16_t test_header_len;
|
||||
|
||||
// mock hci_dump.c
|
||||
extern "C" void hci_dump_log(int log_level, const char * format, ...){}
|
||||
|
||||
static void parser_callback(void * user_data, uint8_t header_id, uint16_t total_len, uint16_t data_offset, const uint8_t * data_buffer, uint16_t data_len){
|
||||
if (obex_parser_header_store(test_header_buffer, sizeof(test_header_buffer), total_len, data_offset, data_buffer, data_len) == OBEX_PARSER_HEADER_COMPLETE){
|
||||
test_header_len = total_len;
|
||||
test_header_id = header_id;
|
||||
}
|
||||
}
|
||||
|
||||
TEST_GROUP(OBEX_PARSER){
|
||||
obex_parser_t parser;
|
||||
uint8_t message[300];
|
||||
|
||||
void setup(void){
|
||||
test_header_id = 0;
|
||||
test_header_len = 0;
|
||||
}
|
||||
void teardown(void){
|
||||
}
|
||||
void parse_request(void){
|
||||
obex_parser_init_for_request(&parser, &parser_callback, NULL);
|
||||
uint16_t message_len = big_endian_read_16(message, 1);
|
||||
for (uint16_t i = 0; i < message_len - 1;i++){
|
||||
obex_parser_object_state_t parser_state = obex_parser_process_data(&parser, &message[i], 1);
|
||||
CHECK_EQUAL(OBEX_PARSER_OBJECT_STATE_INCOMPLETE, parser_state);
|
||||
}
|
||||
obex_parser_object_state_t parser_state = obex_parser_process_data(&parser, &message[message_len-1], 1);
|
||||
CHECK_EQUAL(OBEX_PARSER_OBJECT_STATE_COMPLETE, parser_state);
|
||||
}
|
||||
void parse_response(uint8_t opcode){
|
||||
obex_parser_init_for_response(&parser, opcode, &parser_callback, NULL);
|
||||
uint16_t message_len = big_endian_read_16(message, 1);
|
||||
for (uint16_t i = 0; i < message_len - 1;i++){
|
||||
obex_parser_object_state_t parser_state = obex_parser_process_data(&parser, &message[i], 1);
|
||||
CHECK_EQUAL(OBEX_PARSER_OBJECT_STATE_INCOMPLETE, parser_state);
|
||||
}
|
||||
obex_parser_object_state_t parser_state = obex_parser_process_data(&parser, &message[message_len-1], 1);
|
||||
CHECK_EQUAL(OBEX_PARSER_OBJECT_STATE_COMPLETE, parser_state);
|
||||
}
|
||||
};
|
||||
|
||||
TEST(OBEX_PARSER, RequestOverrun){
|
||||
(void) obex_message_builder_request_create_connect(message, sizeof(message), obex_version_number, flags, maximum_obex_packet_length);
|
||||
uint16_t message_len = big_endian_read_16(message, 1);
|
||||
for (uint16_t i = 0; i < message_len - 1;i++){
|
||||
obex_parser_object_state_t parser_state = obex_parser_process_data(&parser, &message[i], 1);
|
||||
CHECK_EQUAL(OBEX_PARSER_OBJECT_STATE_INCOMPLETE, parser_state);
|
||||
}
|
||||
obex_parser_object_state_t parser_state = obex_parser_process_data(&parser, &message[message_len-1], 1);
|
||||
CHECK_EQUAL(OBEX_PARSER_OBJECT_STATE_COMPLETE, parser_state);
|
||||
parser_state = obex_parser_process_data(&parser, &message[message_len], 1);
|
||||
CHECK_EQUAL(OBEX_PARSER_OBJECT_STATE_OVERRUN, parser_state);
|
||||
}
|
||||
|
||||
TEST(OBEX_PARSER, RequestInvalid){
|
||||
(void) obex_message_builder_request_create_connect(message, sizeof(message), obex_version_number, flags, maximum_obex_packet_length);
|
||||
// decrease packet len
|
||||
uint16_t message_len = big_endian_read_16(message, 1) - 1;
|
||||
big_endian_store_16(message, 1, message_len);
|
||||
for (uint16_t i = 0; i < message_len;i++){
|
||||
obex_parser_object_state_t parser_state = obex_parser_process_data(&parser, &message[i], 1);
|
||||
if (i < 2){
|
||||
CHECK_EQUAL(OBEX_PARSER_OBJECT_STATE_INCOMPLETE, parser_state);
|
||||
} else {
|
||||
CHECK_EQUAL(OBEX_PARSER_OBJECT_STATE_INVALID, parser_state);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST(OBEX_PARSER, ConnectRequest){
|
||||
(void) obex_message_builder_request_create_connect(message, sizeof(message), obex_version_number, flags, maximum_obex_packet_length);
|
||||
parse_request();
|
||||
obex_parser_operation_info_t op_info;
|
||||
obex_parser_get_operation_info(&parser, &op_info);
|
||||
CHECK_EQUAL(OBEX_OPCODE_CONNECT, op_info.opcode);
|
||||
CHECK_EQUAL(obex_version_number, op_info.obex_version_number);
|
||||
CHECK_EQUAL(flags, op_info.flags);
|
||||
CHECK_EQUAL(maximum_obex_packet_length, op_info.max_packet_length);
|
||||
}
|
||||
|
||||
TEST(OBEX_PARSER, ConnectRequestWithTarget){
|
||||
(void) obex_message_builder_request_create_connect(message, sizeof(message), obex_version_number, flags, maximum_obex_packet_length);
|
||||
(void) obex_message_builder_header_add_target(message, sizeof(message), target, sizeof(target));
|
||||
parse_request();
|
||||
obex_parser_operation_info_t op_info;
|
||||
obex_parser_get_operation_info(&parser, &op_info);
|
||||
CHECK_EQUAL(OBEX_HEADER_TARGET, test_header_id);
|
||||
CHECK_EQUAL(sizeof(target), test_header_len);
|
||||
MEMCMP_EQUAL(target, test_header_buffer, sizeof(target));
|
||||
}
|
||||
|
||||
TEST(OBEX_PARSER, ConnectResponse){
|
||||
// no create response yet, fake it
|
||||
(void) obex_message_builder_request_create_connect(message, sizeof(message), obex_version_number, flags, maximum_obex_packet_length);
|
||||
message[0] = OBEX_RESP_SUCCESS;
|
||||
parse_response(OBEX_OPCODE_CONNECT);
|
||||
obex_parser_operation_info_t op_info;
|
||||
obex_parser_get_operation_info(&parser, &op_info);
|
||||
CHECK_EQUAL(OBEX_RESP_SUCCESS, op_info.response_code);
|
||||
CHECK_EQUAL(obex_version_number, op_info.obex_version_number);
|
||||
CHECK_EQUAL(flags, op_info.flags);
|
||||
CHECK_EQUAL(maximum_obex_packet_length, op_info.max_packet_length);
|
||||
}
|
||||
|
||||
TEST(OBEX_PARSER, GetResponseWithSRM){
|
||||
// no get response yet, fake it
|
||||
(void) obex_message_builder_request_create_get(message, sizeof(message), 0x1234);
|
||||
obex_message_builder_header_add_srm_enable(message, sizeof(message));
|
||||
parse_request();
|
||||
obex_parser_operation_info_t op_info;
|
||||
obex_parser_get_operation_info(&parser, &op_info);
|
||||
}
|
||||
|
||||
TEST(OBEX_PARSER, SetPathResponse){
|
||||
const uint8_t set_path_response_success[] = { 0xa0, 0x00, 0x03};
|
||||
memcpy(message, set_path_response_success, sizeof(set_path_response_success));
|
||||
parse_response(OBEX_OPCODE_SETPATH);
|
||||
}
|
||||
|
||||
int main (int argc, const char * argv[]){
|
||||
return CommandLineTestRunner::RunAllTests(argc, argv);
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user