// SPDX-License-Identifier: GPL-2.0-or-later /* * Bluetooth HCI H:4 packet reassembly * * Copyright (C) 2000-2001 Qualcomm Incorporated * Copyright (C) 2002-2003 Maxim Krasnyansky * Copyright (C) 2004-2005 Marcel Holtmann */ #include #include #include #include #include #include /* h4_recv_skb - Reassemble H:4 framed packets * @hdev: HCI device the packets are received on * @alignment: optional packet alignment, NULL or 0 means no alignment * @padding: optional padding state carried over between calls * @skb: partially received packet from a previous call, may be NULL or an * ERR_PTR returned by a previous call * @buffer: buffer holding the received data * @count: number of bytes in @buffer * @pkts: table of supported packet types * @pkts_count: number of entries in @pkts * * Returns the partially received packet to be passed to the next call, or an * ERR_PTR on error. The returned value can be fed back into this function as * is, but must be checked with IS_ERR() before being freed. */ struct sk_buff *h4_recv_skb(struct hci_dev *hdev, u8 *alignment, u8 *padding, struct sk_buff *skb, const unsigned char *buffer, int count, const struct h4_recv_pkt *pkts, int pkts_count) { u8 align = alignment && *alignment ? *alignment : 1; /* Check for error from previous call */ if (IS_ERR(skb)) skb = NULL; while (count) { int i, len; /* remove padding bytes from buffer */ if (padding) { for (; (*padding) && count > 0; (*padding)--) { count--; buffer++; } } if (!count) break; if (!skb) { for (i = 0; i < pkts_count; i++) { if (buffer[0] != pkts[i].type) continue; skb = bt_skb_alloc(pkts[i].maxlen, GFP_ATOMIC); if (!skb) return ERR_PTR(-ENOMEM); hci_skb_pkt_type(skb) = pkts[i].type; hci_skb_expect(skb) = pkts[i].hlen; break; } /* Check for invalid packet type */ if (!skb) return ERR_PTR(-EILSEQ); count -= 1; buffer += 1; } len = min_t(uint, hci_skb_expect(skb) - skb->len, count); skb_put_data(skb, buffer, len); count -= len; buffer += len; /* Check for partial packet */ if (skb->len < hci_skb_expect(skb)) continue; for (i = 0; i < pkts_count; i++) { if (hci_skb_pkt_type(skb) == pkts[i].type) break; } if (i >= pkts_count) { kfree_skb(skb); return ERR_PTR(-EILSEQ); } if (skb->len == pkts[i].hlen) { u16 dlen; switch (pkts[i].lsize) { case 0: /* No variable data length */ dlen = 0; break; case 1: /* Single octet variable length */ dlen = skb->data[pkts[i].loff]; hci_skb_expect(skb) += dlen; if (skb_tailroom(skb) < dlen) { kfree_skb(skb); return ERR_PTR(-EMSGSIZE); } break; case 2: /* Double octet variable length */ dlen = get_unaligned_le16(skb->data + pkts[i].loff); hci_skb_expect(skb) += dlen; if (skb_tailroom(skb) < dlen) { kfree_skb(skb); return ERR_PTR(-EMSGSIZE); } break; default: /* Unsupported variable length */ kfree_skb(skb); return ERR_PTR(-EILSEQ); } if (!dlen) { if (padding) { *padding = (skb->len + 1) % align; *padding = (align - *padding) % align; } /* No more data, complete frame */ pkts[i].recv(hdev, skb); skb = NULL; } } else { if (padding) { *padding = (skb->len + 1) % align; *padding = (align - *padding) % align; } /* Complete frame */ pkts[i].recv(hdev, skb); skb = NULL; } } return skb; } EXPORT_SYMBOL_GPL(h4_recv_skb);