/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ #include #include #include #include #include #include #include #include #include #define HCI_H4_SM_W4_PKT_TYPE 0 #define HCI_H4_SM_W4_HEADER 1 #define HCI_H4_SM_W4_PAYLOAD 2 #define HCI_H4_SM_COMPLETED 3 const struct hci_h4_allocators hci_h4_allocs_from_ll = { .acl = ble_transport_alloc_acl_from_ll, .evt = ble_transport_alloc_evt, }; const struct hci_h4_allocators hci_h4_allocs_from_hs = { .cmd = ble_transport_alloc_cmd, .acl = ble_transport_alloc_acl_from_hs, }; struct hci_h4_input_buffer { const uint8_t *buf; uint16_t len; }; static void hci_h4_frame_start(struct hci_h4_sm *rxs, uint8_t pkt_type) { rxs->pkt_type = pkt_type; rxs->len = 0; rxs->exp_len = 0; switch (rxs->pkt_type) { case HCI_H4_CMD: rxs->min_len = 3; break; case HCI_H4_ACL: rxs->min_len = 4; break; case HCI_H4_EVT: rxs->min_len = 2; break; default: /* XXX sync loss */ assert(0); break; } } static int hci_h4_ib_consume(struct hci_h4_input_buffer *ib, uint16_t len) { assert(ib->len >= len); ib->buf += len; ib->len -= len; return len; } static int hci_h4_ib_pull_min_len(struct hci_h4_sm *rxs, struct hci_h4_input_buffer *ib) { uint16_t len; len = min(ib->len, rxs->min_len - rxs->len); memcpy(&rxs->hdr[rxs->len], ib->buf, len); rxs->len += len; hci_h4_ib_consume(ib, len); return rxs->len != rxs->min_len; } static int hci_h4_sm_w4_header(struct hci_h4_sm *h4sm, struct hci_h4_input_buffer *ib) { int rc; rc = hci_h4_ib_pull_min_len(h4sm, ib); if (rc) { /* need more data */ return 1; } switch (h4sm->pkt_type) { case HCI_H4_CMD: assert(h4sm->allocs && h4sm->allocs->cmd); h4sm->buf = h4sm->allocs->cmd(); if (!h4sm->buf) { return -1; } memcpy(h4sm->buf, h4sm->hdr, h4sm->len); h4sm->exp_len = h4sm->hdr[2] + 3; break; case HCI_H4_ACL: assert(h4sm->allocs && h4sm->allocs->acl); h4sm->om = h4sm->allocs->acl(); if (!h4sm->om) { return -1; } os_mbuf_append(h4sm->om, h4sm->hdr, h4sm->len); h4sm->exp_len = get_le16(&h4sm->hdr[2]) + 4; break; case HCI_H4_EVT: if (h4sm->hdr[0] == BLE_HCI_EVCODE_LE_META) { /* For LE Meta event we need 3 bytes to parse header */ h4sm->min_len = 3; rc = hci_h4_ib_pull_min_len(h4sm, ib); if (rc) { /* need more data */ return 1; } } assert(h4sm->allocs && h4sm->allocs->evt); /* We can drop legacy advertising events if there's no free buffer in * discardable pool. */ if (h4sm->hdr[2] == BLE_HCI_LE_SUBEV_ADV_RPT) { h4sm->buf = h4sm->allocs->evt(1); } else { h4sm->buf = h4sm->allocs->evt(0); if (!h4sm->buf) { return -1; } } if (h4sm->buf) { memcpy(h4sm->buf, h4sm->hdr, h4sm->len); } h4sm->exp_len = h4sm->hdr[1] + 2; break; default: assert(0); break; } return 0; } static int hci_h4_sm_w4_payload(struct hci_h4_sm *h4sm, struct hci_h4_input_buffer *ib) { uint16_t mbuf_len; uint16_t len; int rc; len = min(ib->len, h4sm->exp_len - h4sm->len); switch (h4sm->pkt_type) { case HCI_H4_CMD: case HCI_H4_EVT: if (h4sm->buf) { memcpy(&h4sm->buf[h4sm->len], ib->buf, len); } break; case HCI_H4_ACL: assert(h4sm->om); mbuf_len = OS_MBUF_PKTLEN(h4sm->om); rc = os_mbuf_append(h4sm->om, ib->buf, len); if (rc) { /* Some data may already be appended so need to adjust h4sm only by * the size of appended data. */ len = OS_MBUF_PKTLEN(h4sm->om) - mbuf_len; h4sm->len += len; hci_h4_ib_consume(ib, len); return -1; } break; default: assert(0); break; } h4sm->len += len; hci_h4_ib_consume(ib, len); /* return 1 if need more data */ return h4sm->len != h4sm->exp_len; } static void hci_h4_sm_completed(struct hci_h4_sm *h4sm) { int rc; switch (h4sm->pkt_type) { case HCI_H4_CMD: case HCI_H4_EVT: if (h4sm->buf) { assert(h4sm->frame_cb); rc = h4sm->frame_cb(h4sm->pkt_type, h4sm->buf); if (rc != 0) { ble_transport_free(h4sm->buf); } h4sm->buf = NULL; } break; case HCI_H4_ACL: if (h4sm->om) { assert(h4sm->frame_cb); rc = h4sm->frame_cb(h4sm->pkt_type, h4sm->om); if (rc != 0) { os_mbuf_free_chain(h4sm->om); } h4sm->om = NULL; } break; default: assert(0); break; } } int hci_h4_sm_rx(struct hci_h4_sm *h4sm, const uint8_t *buf, uint16_t len) { struct hci_h4_input_buffer ib = { .buf = buf, .len = len, }; int rc = 0; while (ib.len && (rc >= 0)) { rc = 0; switch (h4sm->state) { case HCI_H4_SM_W4_PKT_TYPE: hci_h4_frame_start(h4sm, ib.buf[0]); hci_h4_ib_consume(&ib, 1); h4sm->state = HCI_H4_SM_W4_HEADER; /* no break */ case HCI_H4_SM_W4_HEADER: rc = hci_h4_sm_w4_header(h4sm, &ib); assert(rc >= 0); if (rc) { break; } h4sm->state = HCI_H4_SM_W4_PAYLOAD; /* no break */ case HCI_H4_SM_W4_PAYLOAD: rc = hci_h4_sm_w4_payload(h4sm, &ib); assert(rc >= 0); if (rc) { break; } h4sm->state = HCI_H4_SM_COMPLETED; /* no break */ case HCI_H4_SM_COMPLETED: hci_h4_sm_completed(h4sm); h4sm->state = HCI_H4_SM_W4_PKT_TYPE; break; default: assert(0); break; } } /* Calculate consumed bytes * * Note: we should always consume some bytes unless there is an oom error. * It's also possible that we have an oom error but already consumed some * data, in such case just return success and error will be returned on next * pass. */ len = len - ib.len; if (len == 0) { assert(rc < 0); return -1; } return len; } void hci_h4_sm_init(struct hci_h4_sm *h4sm, const struct hci_h4_allocators *allocs, hci_h4_frame_cb *frame_cb) { memset(h4sm, 0, sizeof(*h4sm)); h4sm->allocs = allocs; h4sm->frame_cb = frame_cb; }