blob: 86f809018062f56f65ccc1c295dbed9918180cb8 [file]
// 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 <maxk@qualcomm.com>
* Copyright (C) 2004-2005 Marcel Holtmann <marcel@holtmann.org>
*/
#include <linux/export.h>
#include <linux/skbuff.h>
#include <linux/unaligned.h>
#include <net/bluetooth/bluetooth.h>
#include <net/bluetooth/hci_core.h>
#include <net/bluetooth/hci_h4.h>
/* 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);