/* * Copyright (C) 2014 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 MATTHIAS * RINGWALD 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__ "pb_adv.c" #include #include #include #include #include "mesh/adv_bearer.h" #include "mesh/beacon.h" #include "provisioning.h" #include "btstack_util.h" #include "btstack_debug.h" #include "btstack_event.h" #define PB_ADV_LINK_OPEN_RETRANSMIT_MS 1000 static void pb_adv_run(void); /* taps: 32 31 29 1; characteristic polynomial: x^32 + x^31 + x^29 + x + 1 */ #define LFSR(a) ((a >> 1) ^ (uint32_t)((0 - (a & 1u)) & 0xd0000001u)) // PB-ADV - Provisioning Bearer using Advertisement Bearer #define MESH_GENERIC_PROVISIONING_LINK_OPEN 0x00 #define MESH_GENERIC_PROVISIONING_LINK_ACK 0x01 #define MESH_GENERIC_PROVISIONING_LINK_CLOSE 0x02 #define MESH_GENERIC_PROVISIONING_TRANSACTION_TIMEOUT_MS 30000 #define MESH_PB_ADV_MAX_PDU_SIZE 100 #define MESH_PB_ADV_MAX_SEGMENTS 8 #define MESH_PB_ADV_START_PAYLOAD 20 #define MESH_PB_ADV_CONT_PAYLOAD 23 typedef enum mesh_gpcf_format { MESH_GPCF_TRANSACTION_START = 0, MESH_GPCF_TRANSACTION_ACK, MESH_GPCF_TRANSACTION_CONT, MESH_GPCF_PROV_BEARER_CONTROL, } mesh_gpcf_format_t; typedef enum { LINK_STATE_W4_OPEN, LINK_STATE_W2_SEND_ACK, LINK_STATE_W4_ACK, LINK_STATE_OPEN, LINK_STATE_CLOSING, } link_state_t; static link_state_t link_state; static const uint8_t * pb_adv_own_device_uuid; #ifdef ENABLE_MESH_PROVISIONER static const uint8_t * pb_adv_peer_device_uuid; #endif static uint8_t pb_adv_msg_in_buffer[MESH_PB_ADV_MAX_PDU_SIZE]; // TODO: how large are prov messages? // single adv link static uint16_t pb_adv_cid = 1; static uint8_t pb_adv_provisioner_role; // link state static uint32_t pb_adv_link_id; static uint8_t pb_adv_link_close_reason; static uint8_t pb_adv_link_close_countdown; // random delay for outgoing packets static uint32_t pb_adv_lfsr; static uint8_t pb_adv_random_delay_active; static btstack_timer_source_t pb_adv_random_delay_timer; // incoming message static uint8_t pb_adv_msg_in_transaction_nr_prev; static uint16_t pb_adv_msg_in_len; // static uint8_t pb_adv_msg_in_fcs; static uint8_t pb_adv_msg_in_last_segment; static uint8_t pb_adv_msg_in_segments_missing; // bitfield for segmentes 1-n static uint8_t pb_adv_msg_in_transaction_nr; static uint8_t pb_adv_msg_in_send_ack; // oputgoing message static uint8_t pb_adv_msg_out_active; static uint8_t pb_adv_msg_out_transaction_nr; static uint8_t pb_adv_msg_out_completed_transaction_nr; static uint16_t pb_adv_msg_out_len; static uint16_t pb_adv_msg_out_pos; static uint8_t pb_adv_msg_out_seg; static uint32_t pb_adv_msg_out_start; static const uint8_t * pb_adv_msg_out_buffer; static btstack_packet_handler_t pb_adv_packet_handler; // poor man's random number generator static uint32_t pb_adv_random(void){ pb_adv_lfsr = LFSR(pb_adv_lfsr); return pb_adv_lfsr; } static void pb_adv_emit_pdu_sent(uint8_t status){ uint8_t event[] = { HCI_EVENT_MESH_META, 2, MESH_SUBEVENT_PB_TRANSPORT_PDU_SENT, status}; pb_adv_packet_handler(HCI_EVENT_PACKET, 0, event, sizeof(event)); } static void pb_adv_emit_link_open(uint8_t status, uint16_t pb_adv_cid){ uint8_t event[7] = { HCI_EVENT_MESH_META, 5, MESH_SUBEVENT_PB_TRANSPORT_LINK_OPEN, status}; little_endian_store_16(event, 4, pb_adv_cid); event[6] = PB_TYPE_ADV; pb_adv_packet_handler(HCI_EVENT_PACKET, 0, event, sizeof(event)); } static void pb_adv_emit_link_close(uint16_t pb_adv_cid, uint8_t reason){ uint8_t event[5] = { HCI_EVENT_MESH_META, 3, MESH_SUBEVENT_PB_TRANSPORT_LINK_CLOSED}; little_endian_store_16(event, 4, pb_adv_cid); pb_adv_packet_handler(HCI_EVENT_PACKET, 0, event, sizeof(event)); } static void pb_adv_handle_bearer_control(uint32_t link_id, uint8_t transaction_nr, const uint8_t * pdu, uint16_t size){ uint8_t bearer_opcode = pdu[0] >> 2; uint8_t reason; switch (bearer_opcode){ case MESH_GENERIC_PROVISIONING_LINK_OPEN: // Open a session on a bearer with a device // does it match our device_uuid? if (!pb_adv_own_device_uuid) break; if (memcmp(&pdu[1], pb_adv_own_device_uuid, 16) != 0) break; switch(link_state){ case LINK_STATE_W4_OPEN: pb_adv_link_id = link_id; pb_adv_provisioner_role = 0; pb_adv_msg_in_transaction_nr = 0xff; // first transaction nr will be 0x00 pb_adv_msg_in_transaction_nr_prev = 0xff; log_info("link open, id %08x", pb_adv_link_id); printf("PB-ADV: Link Open %08x\n", pb_adv_link_id); link_state = LINK_STATE_W2_SEND_ACK; adv_bearer_request_can_send_now_for_pb_adv(); pb_adv_emit_link_open(0, pb_adv_cid); break; case LINK_STATE_OPEN: if (pb_adv_link_id != link_id) break; log_info("link open, resend ACK"); link_state = LINK_STATE_W2_SEND_ACK; adv_bearer_request_can_send_now_for_pb_adv(); break; default: break; } break; #ifdef ENABLE_MESH_PROVISIONER case MESH_GENERIC_PROVISIONING_LINK_ACK: // Acknowledge a session on a bearer if (link_state != LINK_STATE_W4_ACK) break; link_state = LINK_STATE_OPEN; pb_adv_msg_out_transaction_nr = 0; pb_adv_msg_in_transaction_nr = 0x7f; // first transaction nr will be 0x80 pb_adv_msg_in_transaction_nr_prev = 0x7f; btstack_run_loop_remove_timer(&pb_adv_random_delay_timer); log_info("link open, id %08x", pb_adv_link_id); printf("PB-ADV: Link Open %08x\n", pb_adv_link_id); pb_adv_emit_link_open(0, pb_adv_cid); break; #endif case MESH_GENERIC_PROVISIONING_LINK_CLOSE: // Close a session on a bearer // does it match link id if (link_id != pb_adv_link_id) break; reason = pdu[1]; link_state = LINK_STATE_W4_OPEN; log_info("link close, reason %x", reason); pb_adv_emit_link_close(pb_adv_cid, reason); break; default: log_info("BearerOpcode %x reserved for future use\n", bearer_opcode); break; } } static void pb_adv_pdu_complete(void){ // Verify FCS uint8_t pdu_crc = btstack_crc8_calc((uint8_t*)pb_adv_msg_in_buffer, pb_adv_msg_in_len); if (pdu_crc != pb_adv_msg_in_fcs){ printf("Incoming PDU: fcs %02x, calculated %02x -> drop packet\n", pb_adv_msg_in_fcs, btstack_crc8_calc(pb_adv_msg_in_buffer, pb_adv_msg_in_len)); return; } printf("PB-ADV: %02x complete\n", pb_adv_msg_in_transaction_nr); // transaction complete pb_adv_msg_in_transaction_nr_prev = pb_adv_msg_in_transaction_nr; if (pb_adv_provisioner_role){ pb_adv_msg_in_transaction_nr = 0x7f; // invalid } else { pb_adv_msg_in_transaction_nr = 0xff; // invalid } // Ack Transaction pb_adv_msg_in_send_ack = 1; pb_adv_run(); // Forward to Provisioning pb_adv_packet_handler(PROVISIONING_DATA_PACKET, 0, pb_adv_msg_in_buffer, pb_adv_msg_in_len); } static void pb_adv_handle_transaction_start(uint8_t transaction_nr, const uint8_t * pdu, uint16_t size){ // resend ack if packet from previous transaction received if (transaction_nr != 0xff && transaction_nr == pb_adv_msg_in_transaction_nr_prev){ printf("PB_ADV: %02x transaction complete, resending ack \n", transaction_nr); pb_adv_msg_in_send_ack = 1; return; } // new transaction? if (transaction_nr != pb_adv_msg_in_transaction_nr){ // check len uint16_t msg_len = big_endian_read_16(pdu, 1); if (msg_len > MESH_PB_ADV_MAX_PDU_SIZE){ // abort transaction return; } // check num segments uint8_t last_segment = pdu[0] >> 2; if (last_segment >= MESH_PB_ADV_MAX_SEGMENTS){ // abort transaction return; } printf("PB-ADV: %02x started\n", transaction_nr); pb_adv_msg_in_transaction_nr = transaction_nr; pb_adv_msg_in_len = msg_len; pb_adv_msg_in_fcs = pdu[3]; pb_adv_msg_in_last_segment = last_segment; // set bits for segments 1..n (segment 0 already received in this message) pb_adv_msg_in_segments_missing = (1 << last_segment) - 1; // store payload uint16_t payload_len = size - 4; memcpy(pb_adv_msg_in_buffer, &pdu[4], payload_len); // complete? if (pb_adv_msg_in_segments_missing == 0){ pb_adv_pdu_complete(); } } } static void pb_adv_handle_transaction_cont(uint8_t transaction_nr, const uint8_t * pdu, uint16_t size){ // check transaction nr if (transaction_nr != 0xff && transaction_nr == pb_adv_msg_in_transaction_nr_prev){ printf("PB_ADV: %02x transaction complete, resending resending ack\n", transaction_nr); pb_adv_msg_in_send_ack = 1; return; } if (transaction_nr != pb_adv_msg_in_transaction_nr){ printf("PB-ADV: %02x received msg for transaction nr %x\n", pb_adv_msg_in_transaction_nr, transaction_nr); return; } // validate seg nr uint8_t seg = pdu[0] >> 2; if (seg >= MESH_PB_ADV_MAX_SEGMENTS || seg == 0){ return; } // check if segment already received uint8_t seg_mask = 1 << (seg-1); if ((pb_adv_msg_in_segments_missing & seg_mask) == 0){ printf("PB-ADV: %02x, segment %u already received\n", transaction_nr, seg); return; } printf("PB-ADV: %02x, segment %u stored\n", transaction_nr, seg); // calculate offset and fragment size uint16_t msg_pos = MESH_PB_ADV_START_PAYLOAD + (seg-1) * MESH_PB_ADV_CONT_PAYLOAD; uint16_t fragment_size = size - 1; // check size if last segment if (seg == pb_adv_msg_in_last_segment && (msg_pos + fragment_size) != pb_adv_msg_in_len){ // last segment has invalid size return; } // store segment and mark as received memcpy(&pb_adv_msg_in_buffer[msg_pos], &pdu[1], fragment_size); pb_adv_msg_in_segments_missing &= ~seg_mask; // last segment if (pb_adv_msg_in_segments_missing == 0){ pb_adv_pdu_complete(); } } static void pb_adv_outgoing_transation_complete(uint8_t status){ // stop sending pb_adv_msg_out_active = 0; // emit done pb_adv_emit_pdu_sent(status); // keep track of ack'ed transactions pb_adv_msg_out_completed_transaction_nr = pb_adv_msg_out_transaction_nr; // increment outgoing transaction nr pb_adv_msg_out_transaction_nr++; if (pb_adv_msg_out_transaction_nr == 0x00){ // Device role pb_adv_msg_out_transaction_nr = 0x80; } if (pb_adv_msg_out_transaction_nr == 0x80){ // Provisioner role pb_adv_msg_out_transaction_nr = 0x00; } } static void pb_adv_handle_transaction_ack(uint8_t transaction_nr, const uint8_t * pdu, uint16_t size){ if (transaction_nr == pb_adv_msg_out_transaction_nr){ printf("PB-ADV: %02x ACK received\n", transaction_nr); pb_adv_outgoing_transation_complete(ERROR_CODE_SUCCESS); } else if (transaction_nr == pb_adv_msg_out_completed_transaction_nr){ // Transaction ack received again } else { printf("PB-ADV: %02x unexpected Transaction ACK %x recevied\n", pb_adv_msg_out_transaction_nr, transaction_nr); } } static int pb_adv_packet_to_send(void){ return pb_adv_msg_in_send_ack || pb_adv_msg_out_active || (link_state == LINK_STATE_W4_ACK); } static void pb_adv_timer_handler(btstack_timer_source_t * ts){ pb_adv_random_delay_active = 0; if (!pb_adv_packet_to_send()) return; adv_bearer_request_can_send_now_for_pb_adv(); } static void pb_adv_run(void){ if (!pb_adv_packet_to_send()) return; if (pb_adv_random_delay_active) return; // spec recommends 20-50 ms, we use 20-51 ms pb_adv_random_delay_active = 1; uint16_t random_delay_ms = 20 + (pb_adv_random() & 0x1f); log_info("random delay %u ms", random_delay_ms); btstack_run_loop_set_timer_handler(&pb_adv_random_delay_timer, &pb_adv_timer_handler); btstack_run_loop_set_timer(&pb_adv_random_delay_timer, random_delay_ms); btstack_run_loop_add_timer(&pb_adv_random_delay_timer); } static void pb_adv_handler(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size){ if (packet_type != HCI_EVENT_PACKET) return; const uint8_t * data; uint8_t length; uint32_t link_id; uint8_t transaction_nr; uint8_t generic_provisioning_control; switch(packet[0]){ case GAP_EVENT_ADVERTISING_REPORT: data = gap_event_advertising_report_get_data(packet); // PDB ADV PDU length = data[0]; link_id = big_endian_read_32(data, 2); transaction_nr = data[6]; // generic provision PDU generic_provisioning_control = data[7]; mesh_gpcf_format_t generic_provisioning_control_format = (mesh_gpcf_format_t) generic_provisioning_control & 3; // unless, we're waiting for LINK_OPEN, check link_id if (link_state != LINK_STATE_W4_OPEN){ if (link_id != pb_adv_link_id) break; } if (generic_provisioning_control_format == MESH_GPCF_PROV_BEARER_CONTROL){ pb_adv_handle_bearer_control(link_id, transaction_nr, &data[7], length-6); break; } // verify link id and link state if (link_state != LINK_STATE_OPEN) break; switch (generic_provisioning_control_format){ case MESH_GPCF_TRANSACTION_START: pb_adv_handle_transaction_start(transaction_nr, &data[7], length-6); break; case MESH_GPCF_TRANSACTION_CONT: pb_adv_handle_transaction_cont(transaction_nr, &data[7], length-6); break; case MESH_GPCF_TRANSACTION_ACK: pb_adv_handle_transaction_ack(transaction_nr, &data[7], length-6); break; default: break; } pb_adv_run(); break; case HCI_EVENT_MESH_META: switch(packet[2]){ case MESH_SUBEVENT_CAN_SEND_NOW: if (link_state == LINK_STATE_W4_ACK){ // build packet uint8_t buffer[22]; big_endian_store_32(buffer, 0, pb_adv_link_id); buffer[4] = 0; // Transaction ID = 0 buffer[5] = (0 << 2) | 3; // Link Open | Provisioning Bearer Control memcpy(&buffer[6], pb_adv_peer_device_uuid, 16); adv_bearer_send_pb_adv(buffer, sizeof(buffer)); log_info("link open %08x", pb_adv_link_id); printf("PB-ADV: Sending Link Open for device uuid: "); printf_hexdump(pb_adv_peer_device_uuid, 16); btstack_run_loop_set_timer_handler(&pb_adv_random_delay_timer, &pb_adv_timer_handler); btstack_run_loop_set_timer(&pb_adv_random_delay_timer, PB_ADV_LINK_OPEN_RETRANSMIT_MS); btstack_run_loop_add_timer(&pb_adv_random_delay_timer); break; } if (link_state == LINK_STATE_CLOSING){ log_info("link close %08x", pb_adv_link_id); printf("PB-ADV: Sending Link Close\n"); // build packet uint8_t buffer[7]; big_endian_store_32(buffer, 0, pb_adv_link_id); buffer[4] = 0; // Transaction ID = 0 buffer[5] = (2 << 2) | 3; // Link Close | Provisioning Bearer Control buffer[6] = pb_adv_link_close_reason; adv_bearer_send_pb_adv(buffer, sizeof(buffer)); pb_adv_link_close_countdown--; if (pb_adv_link_close_countdown) { adv_bearer_request_can_send_now_for_pb_adv(); } else { link_state = LINK_STATE_W4_OPEN; } break; } if (link_state == LINK_STATE_W2_SEND_ACK){ link_state = LINK_STATE_OPEN; pb_adv_msg_out_transaction_nr = 0x80; // build packet uint8_t buffer[6]; big_endian_store_32(buffer, 0, pb_adv_link_id); buffer[4] = 0; buffer[5] = (1 << 2) | 3; // Link Ack | Provisioning Bearer Control adv_bearer_send_pb_adv(buffer, sizeof(buffer)); log_info("link ack %08x", pb_adv_link_id); printf("PB-ADV: Sending Link Open Ack\n"); break; } if (pb_adv_msg_in_send_ack){ pb_adv_msg_in_send_ack = 0; uint8_t buffer[6]; big_endian_store_32(buffer, 0, pb_adv_link_id); buffer[4] = pb_adv_msg_in_transaction_nr_prev; buffer[5] = MESH_GPCF_TRANSACTION_ACK; adv_bearer_send_pb_adv(buffer, sizeof(buffer)); log_info("transaction ack %08x", pb_adv_link_id); printf("PB-ADV: %02x sending ACK\n", pb_adv_msg_in_transaction_nr_prev); pb_adv_run(); break; } if (pb_adv_msg_out_active){ // check timeout for outgoing message // since uint32_t is used and time now must be greater than pb_adv_msg_out_start, // this claculation is correct even when the run loop time overruns uint32_t transaction_time_ms = btstack_run_loop_get_time_ms() - pb_adv_msg_out_start; if (transaction_time_ms >= MESH_GENERIC_PROVISIONING_TRANSACTION_TIMEOUT_MS){ pb_adv_outgoing_transation_complete(ERROR_CODE_CONNECTION_TIMEOUT); return; } uint8_t buffer[29]; // ADV MTU big_endian_store_32(buffer, 0, pb_adv_link_id); buffer[4] = pb_adv_msg_out_transaction_nr; uint16_t bytes_left; uint16_t pos; if (pb_adv_msg_out_pos == 0){ // Transaction start int seg_n = pb_adv_msg_out_len / 24; pb_adv_msg_out_seg = 0; buffer[5] = seg_n << 2 | MESH_GPCF_TRANSACTION_START; big_endian_store_16(buffer, 6, pb_adv_msg_out_len); buffer[8] = btstack_crc8_calc((uint8_t*)pb_adv_msg_out_buffer, pb_adv_msg_out_len); pos = 9; bytes_left = 24 - 4; printf("PB-ADV: %02x Sending Start: ", pb_adv_msg_out_transaction_nr); } else { // Transaction continue buffer[5] = pb_adv_msg_out_seg << 2 | MESH_GPCF_TRANSACTION_CONT; pos = 6; bytes_left = 24 - 1; printf("PB-ADV: %02x Sending Cont: ", pb_adv_msg_out_transaction_nr); } pb_adv_msg_out_seg++; uint16_t bytes_to_copy = btstack_min(bytes_left, pb_adv_msg_out_len - pb_adv_msg_out_pos); memcpy(&buffer[pos], &pb_adv_msg_out_buffer[pb_adv_msg_out_pos], bytes_to_copy); pos += bytes_to_copy; printf("bytes %02u, pos %02u, len %02u: ", bytes_to_copy, pb_adv_msg_out_pos, pb_adv_msg_out_len); printf_hexdump(buffer, pos); pb_adv_msg_out_pos += bytes_to_copy; if (pb_adv_msg_out_pos == pb_adv_msg_out_len){ // done pb_adv_msg_out_pos = 0; } adv_bearer_send_pb_adv(buffer, pos); pb_adv_run(); break; } break; default: break; } default: break; } } void pb_adv_init(uint8_t * device_uuid){ pb_adv_own_device_uuid = device_uuid; adv_bearer_register_for_pb_adv(&pb_adv_handler); pb_adv_lfsr = 0x12345678; pb_adv_random(); } void pb_adv_register_packet_handler(btstack_packet_handler_t packet_handler){ pb_adv_packet_handler = packet_handler; } void pb_adv_send_pdu(uint16_t pb_adv_cid, const uint8_t * pdu, uint16_t size){ UNUSED(pb_adv_cid); printf("PB-ADV: Send packet "); printf_hexdump(pdu, size); pb_adv_msg_out_buffer = pdu; pb_adv_msg_out_len = size; pb_adv_msg_out_pos = 0; pb_adv_msg_out_start = btstack_run_loop_get_time_ms(); pb_adv_msg_out_active = 1; pb_adv_run(); } /** * Close Link * @param pb_adv_cid */ void pb_adv_close_link(uint16_t pb_adv_cid, uint8_t reason){ switch (link_state){ case LINK_STATE_W4_ACK: case LINK_STATE_OPEN: case LINK_STATE_W2_SEND_ACK: pb_adv_emit_link_close(pb_adv_cid, 0); link_state = LINK_STATE_CLOSING; pb_adv_link_close_countdown = 3; pb_adv_link_close_reason = reason; adv_bearer_request_can_send_now_for_pb_adv(); break; case LINK_STATE_W4_OPEN: case LINK_STATE_CLOSING: // nothing to do break; } } #ifdef ENABLE_MESH_PROVISIONER uint16_t pb_adv_create_link(const uint8_t * device_uuid){ if (link_state != LINK_STATE_W4_OPEN) return 0; pb_adv_peer_device_uuid = device_uuid; pb_adv_provisioner_role = 1; // create new 32-bit link id pb_adv_link_id = pb_adv_random(); // after sending OPEN, we wait for an ACK link_state = LINK_STATE_W4_ACK; // request outgoing adv_bearer_request_can_send_now_for_pb_adv(); // dummy pb_adv_cid return pb_adv_cid; } #endif