in nimble/host/mesh/src/transport.c [1275:1547]
static int trans_seg(struct os_mbuf *buf, struct bt_mesh_net_rx *net_rx,
enum bt_mesh_friend_pdu_type *pdu_type, uint64_t *seq_auth,
uint8_t *seg_count)
{
struct bt_mesh_rpl *rpl = NULL;
struct seg_rx *rx;
uint8_t *hdr = buf->om_data;
uint16_t seq_zero;
uint32_t auth_seqnum;
uint8_t seg_n;
uint8_t seg_o;
int err;
if (buf->om_len < 5) {
BT_ERR("Too short segmented message (len %u)", buf->om_len);
return -EINVAL;
}
if (bt_mesh_rpl_check(net_rx, &rpl)) {
BT_WARN("Replay: src 0x%04x dst 0x%04x seq 0x%06x",
net_rx->ctx.addr, net_rx->ctx.recv_dst, net_rx->seq);
return -EINVAL;
}
BT_DBG("ASZMIC %u AKF %u AID 0x%02x", ASZMIC(hdr), AKF(hdr), AID(hdr));
net_buf_simple_pull(buf, 1);
seq_zero = net_buf_simple_pull_be16(buf);
seg_o = (seq_zero & 0x03) << 3;
seq_zero = (seq_zero >> 2) & TRANS_SEQ_ZERO_MASK;
seg_n = net_buf_simple_pull_u8(buf);
seg_o |= seg_n >> 5;
seg_n &= 0x1f;
BT_DBG("SeqZero 0x%04x SegO %u SegN %u", seq_zero, seg_o, seg_n);
if (seg_o > seg_n) {
BT_ERR("SegO greater than SegN (%u > %u)", seg_o, seg_n);
return -EINVAL;
}
/* According to Mesh 1.0 specification:
* "The SeqAuth is composed of the IV Index and the sequence number
* (SEQ) of the first segment"
*
* Therefore we need to calculate very first SEQ in order to find
* seqAuth. We can calculate as below:
*
* SEQ(0) = SEQ(n) - (delta between seqZero and SEQ(n) by looking into
* 14 least significant bits of SEQ(n))
*
* Mentioned delta shall be >= 0, if it is not then seq_auth will
* be broken and it will be verified by the code below.
*/
*seq_auth = SEQ_AUTH(BT_MESH_NET_IVI_RX(net_rx),
(net_rx->seq -
((((net_rx->seq & BIT_MASK(14)) - seq_zero)) &
BIT_MASK(13))));
auth_seqnum = *seq_auth & BIT_MASK(24);
*seg_count = seg_n + 1;
/* Look for old RX sessions */
rx = seg_rx_find(net_rx, seq_auth);
if (rx) {
/* Discard old SeqAuth packet */
if (rx->seq_auth > *seq_auth) {
BT_WARN("Ignoring old SeqAuth");
return -EINVAL;
}
if (!seg_rx_is_valid(rx, net_rx, hdr, seg_n)) {
return -EINVAL;
}
if (rx->in_use) {
BT_DBG("Existing RX context. Block 0x%08x",
(unsigned) rx->block);
goto found_rx;
}
if (rx->block == BLOCK_COMPLETE(rx->seg_n)) {
BT_DBG("Got segment for already complete SDU");
send_ack(net_rx->sub, net_rx->ctx.recv_dst,
net_rx->ctx.addr, net_rx->ctx.send_ttl,
seq_auth, rx->block, rx->obo);
if (rpl) {
bt_mesh_rpl_update(rpl, net_rx);
}
return -EALREADY;
}
/* We ignore instead of sending block ack 0 since the
* ack timer is always smaller than the incomplete
* timer, i.e. the sender is misbehaving.
*/
BT_WARN("Got segment for canceled SDU");
return -EINVAL;
}
/* Bail out early if we're not ready to receive such a large SDU */
if (!sdu_len_is_ok(net_rx->ctl, seg_n)) {
BT_ERR("Too big incoming SDU length");
send_ack(net_rx->sub, net_rx->ctx.recv_dst, net_rx->ctx.addr,
net_rx->ctx.send_ttl, seq_auth, 0,
net_rx->friend_match);
return -EMSGSIZE;
}
/* Verify early that there will be space in the Friend Queue(s) in
* case this message is destined to an LPN of ours.
*/
if (IS_ENABLED(CONFIG_BT_MESH_FRIEND) &&
net_rx->friend_match && !net_rx->local_match &&
!bt_mesh_friend_queue_has_space(net_rx->sub->net_idx,
net_rx->ctx.addr,
net_rx->ctx.recv_dst, seq_auth,
*seg_count)) {
BT_ERR("No space in Friend Queue for %u segments", *seg_count);
send_ack(net_rx->sub, net_rx->ctx.recv_dst, net_rx->ctx.addr,
net_rx->ctx.send_ttl, seq_auth, 0,
net_rx->friend_match);
return -ENOBUFS;
}
/* Keep track of the received SeqAuth values received from this address
* and discard segmented messages that are not newer, as described in
* the Bluetooth Mesh specification section 3.5.3.4.
*
* The logic on the first segmented receive is a bit special, since the
* initial value of rpl->seg is 0, which would normally fail the
* comparison check with auth_seqnum:
* - If this is the first time we receive from this source, rpl->src
* will be 0, and we can skip this check.
* - If this is the first time we receive from this source on the new IV
* index, rpl->old_iv will be set, and the check is also skipped.
* - If this is the first segmented message on the new IV index, but we
* have received an unsegmented message already, the unsegmented
* message will have reset rpl->seg to 0, and this message's SeqAuth
* cannot be zero.
*/
if (rpl && rpl->src && auth_seqnum <= rpl->seg &&
(!rpl->old_iv || net_rx->old_iv)) {
BT_WARN("Ignoring old SeqAuth 0x%06x", auth_seqnum);
return -EALREADY;
}
/* Look for free slot for a new RX session */
rx = seg_rx_alloc(net_rx, hdr, seq_auth, seg_n);
if (!rx) {
/* Warn but don't cancel since the existing slots willl
* eventually be freed up and we'll be able to process
* this one.
*/
BT_WARN("No free slots for new incoming segmented messages");
return -ENOMEM;
}
rx->obo = net_rx->friend_match;
found_rx:
if (BIT(seg_o) & rx->block) {
BT_DBG("Received already received fragment");
return -EALREADY;
}
/* All segments, except the last one, must either have 8 bytes of
* payload (for 64bit Net MIC) or 12 bytes of payload (for 32bit
* Net MIC).
*/
if (seg_o == seg_n) {
/* Set the expected final buffer length */
rx->len = seg_n * seg_len(rx->ctl) + buf->om_len;
BT_DBG("Target len %u * %u + %u = %u", seg_n, seg_len(rx->ctl),
buf->om_len, rx->len);
if (rx->len > BT_MESH_RX_SDU_MAX) {
BT_ERR("Too large SDU len");
send_ack(net_rx->sub, net_rx->ctx.recv_dst,
net_rx->ctx.addr, net_rx->ctx.send_ttl,
seq_auth, 0, rx->obo);
seg_rx_reset(rx, true);
return -EMSGSIZE;
}
} else {
if (buf->om_len != seg_len(rx->ctl)) {
BT_ERR("Incorrect segment size for message type");
return -EINVAL;
}
}
/* Reset the Incomplete Timer */
rx->last = k_uptime_get_32();
if (!bt_mesh_lpn_established()) {
int32_t timeout = ack_timeout(rx);
/* Should only start ack timer if it isn't running already: */
k_work_schedule(&rx->ack, K_MSEC(timeout));
}
/* Allocated segment here */
err = k_mem_slab_alloc(&segs, &rx->seg[seg_o]);
if (err) {
BT_WARN("Unable allocate buffer for Seg %u", seg_o);
return -ENOBUFS;
}
os_mbuf_copydata(buf, 0, buf->om_len, rx->seg[seg_o]);
BT_DBG("copied %s", bt_hex(rx->seg[seg_o], rx->len));
BT_DBG("Received %u/%u", seg_o, seg_n);
/* Mark segment as received */
rx->block |= BIT(seg_o);
if (rx->block != BLOCK_COMPLETE(seg_n)) {
*pdu_type = BT_MESH_FRIEND_PDU_PARTIAL;
return 0;
}
BT_DBG("Complete SDU");
if (rpl) {
bt_mesh_rpl_update(rpl, net_rx);
/* Update the seg, unless it has already been surpassed:
* This needs to happen after rpl_update to ensure that the IV
* update reset logic inside rpl_update doesn't overwrite the
* change.
*/
rpl->seg = MAX(rpl->seg, auth_seqnum);
}
*pdu_type = BT_MESH_FRIEND_PDU_COMPLETE;
/* If this fails, the work handler will either exit early because the
* block is fully received, or rx->in_use is false.
*/
(void)k_work_cancel_delayable(&rx->ack);
send_ack(net_rx->sub, net_rx->ctx.recv_dst, net_rx->ctx.addr,
net_rx->ctx.send_ttl, seq_auth, rx->block, rx->obo);
if (net_rx->ctl) {
struct os_mbuf *sdu = NET_BUF_SIMPLE(BT_MESH_RX_CTL_MAX);
seg_rx_assemble(rx, sdu, 0U);
err = ctl_recv(net_rx, *hdr, sdu, seq_auth);
} else if (rx->len < 1 + APP_MIC_LEN(ASZMIC(hdr))) {
BT_ERR("Too short SDU + MIC");
err = -EINVAL;
} else {
struct os_mbuf *seg_buf = NET_BUF_SIMPLE(BT_MESH_RX_SDU_MAX);
struct os_mbuf *sdu;
/* Decrypting in place to avoid creating two assembly buffers.
* We'll reassemble the buffer from the segments before each
* decryption attempt.
*/
net_buf_simple_init(seg_buf, 0);
sdu = NET_BUF_SIMPLE(rx->len - APP_MIC_LEN(ASZMIC(hdr)));
net_buf_simple_init_with_data(
sdu, seg_buf->om_data, rx->len - APP_MIC_LEN(ASZMIC(hdr)));
err = sdu_recv(net_rx, *hdr, ASZMIC(hdr), seg_buf, sdu, rx);
os_mbuf_free_chain(seg_buf);
}
seg_rx_reset(rx, false);
return err;
}