diff options
Diffstat (limited to 'net/bluetooth/l2cap_core.c')
-rw-r--r-- | net/bluetooth/l2cap_core.c | 243 |
1 files changed, 80 insertions, 163 deletions
diff --git a/net/bluetooth/l2cap_core.c b/net/bluetooth/l2cap_core.c index 1611b35..12b1e74 100644 --- a/net/bluetooth/l2cap_core.c +++ b/net/bluetooth/l2cap_core.c @@ -3128,102 +3128,104 @@ static int l2cap_add_to_srej_queue(struct l2cap_chan *chan, struct sk_buff *skb, return 0; } -static int l2cap_ertm_reassembly_sdu(struct l2cap_chan *chan, struct sk_buff *skb, u16 control) +static void append_skb_frag(struct sk_buff *skb, + struct sk_buff *new_frag, struct sk_buff **last_frag) { - struct sk_buff *_skb; - int err; + /* skb->len reflects data in skb as well as all fragments + * skb->data_len reflects only data in fragments + */ + if (!skb_has_frag_list(skb)) + skb_shinfo(skb)->frag_list = new_frag; + + new_frag->next = NULL; + + (*last_frag)->next = new_frag; + *last_frag = new_frag; + + skb->len += new_frag->len; + skb->data_len += new_frag->len; + skb->truesize += new_frag->truesize; +} + +static int l2cap_reassemble_sdu(struct l2cap_chan *chan, struct sk_buff *skb, u16 control) +{ + int err = -EINVAL; switch (control & L2CAP_CTRL_SAR) { case L2CAP_SDU_UNSEGMENTED: - if (test_bit(CONN_SAR_SDU, &chan->conn_state)) - goto drop; + if (chan->sdu) + break; - return chan->ops->recv(chan->data, skb); + err = chan->ops->recv(chan->data, skb); + break; case L2CAP_SDU_START: - if (test_bit(CONN_SAR_SDU, &chan->conn_state)) - goto drop; + if (chan->sdu) + break; chan->sdu_len = get_unaligned_le16(skb->data); + skb_pull(skb, 2); - if (chan->sdu_len > chan->imtu) - goto disconnect; - - chan->sdu = bt_skb_alloc(chan->sdu_len, GFP_ATOMIC); - if (!chan->sdu) - return -ENOMEM; + if (chan->sdu_len > chan->imtu) { + err = -EMSGSIZE; + break; + } - /* pull sdu_len bytes only after alloc, because of Local Busy - * condition we have to be sure that this will be executed - * only once, i.e., when alloc does not fail */ - skb_pull(skb, 2); + if (skb->len >= chan->sdu_len) + break; - memcpy(skb_put(chan->sdu, skb->len), skb->data, skb->len); + chan->sdu = skb; + chan->sdu_last_frag = skb; - set_bit(CONN_SAR_SDU, &chan->conn_state); - chan->partial_sdu_len = skb->len; + skb = NULL; + err = 0; break; case L2CAP_SDU_CONTINUE: - if (!test_bit(CONN_SAR_SDU, &chan->conn_state)) - goto disconnect; - if (!chan->sdu) - goto disconnect; + break; - chan->partial_sdu_len += skb->len; - if (chan->partial_sdu_len > chan->sdu_len) - goto drop; + append_skb_frag(chan->sdu, skb, + &chan->sdu_last_frag); + skb = NULL; - memcpy(skb_put(chan->sdu, skb->len), skb->data, skb->len); + if (chan->sdu->len >= chan->sdu_len) + break; + err = 0; break; case L2CAP_SDU_END: - if (!test_bit(CONN_SAR_SDU, &chan->conn_state)) - goto disconnect; - if (!chan->sdu) - goto disconnect; - - chan->partial_sdu_len += skb->len; - - if (chan->partial_sdu_len > chan->imtu) - goto drop; + break; - if (chan->partial_sdu_len != chan->sdu_len) - goto drop; + append_skb_frag(chan->sdu, skb, + &chan->sdu_last_frag); + skb = NULL; - memcpy(skb_put(chan->sdu, skb->len), skb->data, skb->len); + if (chan->sdu->len != chan->sdu_len) + break; - _skb = skb_clone(chan->sdu, GFP_ATOMIC); - if (!_skb) { - return -ENOMEM; - } + err = chan->ops->recv(chan->data, chan->sdu); - err = chan->ops->recv(chan->data, _skb); - if (err < 0) { - kfree_skb(_skb); - return err; + if (!err) { + /* Reassembly complete */ + chan->sdu = NULL; + chan->sdu_last_frag = NULL; + chan->sdu_len = 0; } - - clear_bit(CONN_SAR_SDU, &chan->conn_state); - - kfree_skb(chan->sdu); break; } - kfree_skb(skb); - return 0; - -drop: - kfree_skb(chan->sdu); - chan->sdu = NULL; + if (err) { + kfree_skb(skb); + kfree_skb(chan->sdu); + chan->sdu = NULL; + chan->sdu_last_frag = NULL; + chan->sdu_len = 0; + } -disconnect: - l2cap_send_disconn_req(chan->conn, chan, ECONNRESET); - kfree_skb(skb); - return 0; + return err; } static void l2cap_ertm_enter_local_busy(struct l2cap_chan *chan) @@ -3277,99 +3279,6 @@ void l2cap_chan_busy(struct l2cap_chan *chan, int busy) } } -static int l2cap_streaming_reassembly_sdu(struct l2cap_chan *chan, struct sk_buff *skb, u16 control) -{ - struct sk_buff *_skb; - int err = -EINVAL; - - /* - * TODO: We have to notify the userland if some data is lost with the - * Streaming Mode. - */ - - switch (control & L2CAP_CTRL_SAR) { - case L2CAP_SDU_UNSEGMENTED: - if (test_bit(CONN_SAR_SDU, &chan->conn_state)) { - kfree_skb(chan->sdu); - break; - } - - err = chan->ops->recv(chan->data, skb); - if (!err) - return 0; - - break; - - case L2CAP_SDU_START: - if (test_bit(CONN_SAR_SDU, &chan->conn_state)) { - kfree_skb(chan->sdu); - break; - } - - chan->sdu_len = get_unaligned_le16(skb->data); - skb_pull(skb, 2); - - if (chan->sdu_len > chan->imtu) { - err = -EMSGSIZE; - break; - } - - chan->sdu = bt_skb_alloc(chan->sdu_len, GFP_ATOMIC); - if (!chan->sdu) { - err = -ENOMEM; - break; - } - - memcpy(skb_put(chan->sdu, skb->len), skb->data, skb->len); - - set_bit(CONN_SAR_SDU, &chan->conn_state); - chan->partial_sdu_len = skb->len; - err = 0; - break; - - case L2CAP_SDU_CONTINUE: - if (!test_bit(CONN_SAR_SDU, &chan->conn_state)) - break; - - memcpy(skb_put(chan->sdu, skb->len), skb->data, skb->len); - - chan->partial_sdu_len += skb->len; - if (chan->partial_sdu_len > chan->sdu_len) - kfree_skb(chan->sdu); - else - err = 0; - - break; - - case L2CAP_SDU_END: - if (!test_bit(CONN_SAR_SDU, &chan->conn_state)) - break; - - memcpy(skb_put(chan->sdu, skb->len), skb->data, skb->len); - - clear_bit(CONN_SAR_SDU, &chan->conn_state); - chan->partial_sdu_len += skb->len; - - if (chan->partial_sdu_len > chan->imtu) - goto drop; - - if (chan->partial_sdu_len == chan->sdu_len) { - _skb = skb_clone(chan->sdu, GFP_ATOMIC); - err = chan->ops->recv(chan->data, _skb); - if (err < 0) - kfree_skb(_skb); - } - err = 0; - -drop: - kfree_skb(chan->sdu); - break; - } - - kfree_skb(skb); - return err; -} - static void l2cap_check_srej_gap(struct l2cap_chan *chan, u8 tx_seq) { struct sk_buff *skb; @@ -3384,7 +3293,7 @@ static void l2cap_check_srej_gap(struct l2cap_chan *chan, u8 tx_seq) skb = skb_dequeue(&chan->srej_q); control = bt_cb(skb)->sar << L2CAP_CTRL_SAR_SHIFT; - err = l2cap_ertm_reassembly_sdu(chan, skb, control); + err = l2cap_reassemble_sdu(chan, skb, control); if (err < 0) { l2cap_send_disconn_req(chan->conn, chan, ECONNRESET); @@ -3544,7 +3453,7 @@ expected: return 0; } - err = l2cap_ertm_reassembly_sdu(chan, skb, rx_control); + err = l2cap_reassemble_sdu(chan, skb, rx_control); chan->buffer_seq = (chan->buffer_seq + 1) % 64; if (err < 0) { l2cap_send_disconn_req(chan->conn, chan, ECONNRESET); @@ -3860,12 +3769,20 @@ static inline int l2cap_data_channel(struct l2cap_conn *conn, u16 cid, struct sk tx_seq = __get_txseq(control); - if (chan->expected_tx_seq == tx_seq) - chan->expected_tx_seq = (chan->expected_tx_seq + 1) % 64; - else - chan->expected_tx_seq = (tx_seq + 1) % 64; + if (chan->expected_tx_seq != tx_seq) { + /* Frame(s) missing - must discard partial SDU */ + kfree_skb(chan->sdu); + chan->sdu = NULL; + chan->sdu_last_frag = NULL; + chan->sdu_len = 0; - l2cap_streaming_reassembly_sdu(chan, skb, control); + /* TODO: Notify userland of missing data */ + } + + chan->expected_tx_seq = (tx_seq + 1) % 64; + + if (l2cap_reassemble_sdu(chan, skb, control) == -EMSGSIZE) + l2cap_send_disconn_req(chan->conn, chan, ECONNRESET); goto done; |