(基于Nordic)Android蓝牙mesh协议栈问题修复之-分片问题

5 篇文章 0 订阅
4 篇文章 0 订阅

背景

  • 首先贴上官方github地址https://github.com/NordicSemiconductor/Android-nRF-Mesh-Library
  • 本次修改基于官方SDK 2.4.1版本.
  • 阅读此文章之前,我们默认您对蓝牙mesh协议已经有了一定了解.
  • 本次修复了两个问题 ,一个是组播分片导致的crash问题,另一个是分片重发导致片段丢失的问题. 下面会详细描述.

1.组播分片导致程序crash问题

问题分析

我们在使用SDK的时候发现,协议栈的代码偶尔会出现闪退,最终排查到是我们收到组播分片消息的时候,会向设备回复分片消息状态.代码如下:

    /**
     * Send immediate block acknowledgement
     *
     * @param seqZero seqzero of the message
     * @param ttl     ttl of the message
     * @param src     source address of the message
     * @param dst     destination address of the message
     * @param segN    total segment count
     */
    private void handleImmediateBlockAcks(final int seqZero, final int ttl, final int src, final int dst, final int segN) {
        cancelIncompleteTimer();
        sendBlockAck(seqZero, ttl, src, dst, segN);
    }

问题就出现在这块代码里面,因为组播分片消息是不需要回复block ack的,因此如果是组播的分片消息,走了这个方法之后,我们会用组播地址去寻找节点,从而导致crash.

修改内容

根据协议栈规定,我们知道,组播的地址范围是0xC000-0xFEFF,因此我们只要判断回复的消息不是组播消息,再回复block ack即可.代码如下:

  /**
     * Send immediate block acknowledgement
     *
     * @param seqZero seqzero of the message
     * @param ttl     ttl of the message
     * @param src     source address of the message
     * @param dst     destination address of the message
     * @param segN    total segment count
     */
    private void handleImmediateBlockAcks(final int seqZero, final int ttl, final int src, final int dst, final int segN) {
        cancelIncompleteTimer();
        if (src < 0xC000)
        {
            sendBlockAck(seqZero, ttl, src, dst, segN);
        }
    }

2.分片重发导致片段丢失的问题

问题分析

此外,我们在使用SDK的时候发现,单播的分片,重发的时候,总是会丢失某一片一小.这个在app上的表现就是,只要涉及到分片数据的时候,就是发送失败.(比如说虚拟按钮等).问题代码如下:

    @SuppressWarnings("ConstantConditions")
    @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED)
    final Message createRetransmitNetworkLayerPDU(@NonNull final Message message, final int segment) {
        final SecureUtils.K2Output k2Output = getK2Output(message);
        final int nid = k2Output.getNid();
        final byte[] encryptionKey = k2Output.getEncryptionKey();
        Log.v(TAG, "Encryption key: " + MeshParserUtils.bytesToHex(encryptionKey, false));

        final byte[] privacyKey = k2Output.getPrivacyKey();
        Log.v(TAG, "Privacy key: " + MeshParserUtils.bytesToHex(privacyKey, false));
        final int ctl = message.getCtl();
        final int ttl = message.getTtl();
        final int ivi = message.getIvIndex()[3] & 0x01; // least significant bit of IV Index
        final byte iviNID = (byte) ((ivi << 7) | nid);
        final byte ctlTTL = (byte) ((ctl << 7) | ttl);

        final int src = message.getSrc();
        final SparseArray<byte[]> lowerTransportPduMap;
        if (message instanceof AccessMessage) {
            lowerTransportPduMap = ((AccessMessage) message).getLowerTransportAccessPdu();
        } else {
            lowerTransportPduMap = ((ControlMessage) message).getLowerTransportControlPdu();
        }

        byte[] encryptedNetworkPayload = null;
        final int pduType = message.getPduType();
        if (message.getPduType() == MeshManagerApi.PDU_TYPE_NETWORK) {
            final ProvisionedMeshNode node = mUpperTransportLayerCallbacks.getNode(message.getSrc());
            final byte[] lowerTransportPdu = lowerTransportPduMap.get(segment);
            node.setSequenceNumber(MeshParserUtils.getSequenceNumber(message.getSequenceNumber()));
            //final int sequenceNumber = node.incrementSequenceNumber();//incrementSequenceNumber(mNetworkLayerCallbacks.getProvisioner(), message.getSequenceNumber());
            final byte[] sequenceNum = MeshParserUtils.getSequenceNumberBytes(node.incrementSequenceNumber());
            message.setSequenceNumber(sequenceNum);

            Log.v(TAG, "Sequence Number: " + MeshParserUtils.bytesToHex(sequenceNum, false));

            final byte[] nonce = createNetworkNonce(ctlTTL, sequenceNum, src, message.getIvIndex());
            encryptedNetworkPayload = encryptPdu(lowerTransportPdu, encryptionKey, nonce, message.getDst(), SecureUtils.getNetMicLength(message.getCtl()));
            if (encryptedNetworkPayload == null)
                return null;
            Log.v(TAG, "Encrypted Network payload: " + MeshParserUtils.bytesToHex(encryptedNetworkPayload, false));
        }

        final SparseArray<byte[]> pduArray = new SparseArray<>();
        if (encryptedNetworkPayload == null)
            return null;

        final byte[] privacyRandom = createPrivacyRandom(encryptedNetworkPayload);
        //Next we create the PECB
        final byte[] pecb = createPECB(message.getIvIndex(), privacyRandom, privacyKey);

        final byte[] header = obfuscateNetworkHeader(ctlTTL, message.getSequenceNumber(), src, pecb);
        final byte[] pdu = ByteBuffer.allocate(1 + 1 + header.length + encryptedNetworkPayload.length).order(ByteOrder.BIG_ENDIAN)
                .put((byte) pduType)
                .put(iviNID)
                .put(header)
                .put(encryptedNetworkPayload)
                .array();
        pduArray.put(segment, pdu);
        message.setNetworkLayerPdu(pduArray);
        return message;
    }

经过对上述底层协议栈的代码定位,我们发现,在一条Message消息中,内部的分片PUD数组,总是会被重发的分片数据包给覆盖掉,这就是会导致之前的分片消息丢失了,这是一个很严重的问题.

修改内容

因此为了避免一条Message的分片消息丢失,我们只需要略微改动一下代码即可解决问题.改动如下:

    @SuppressWarnings("ConstantConditions")
    @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED)
    final Message createRetransmitNetworkLayerPDU(@NonNull final Message message, final int segment) {
        final SecureUtils.K2Output k2Output = getK2Output(message);
        final int nid = k2Output.getNid();
        final byte[] encryptionKey = k2Output.getEncryptionKey();
        Log.v(TAG, "Encryption key: " + MeshParserUtils.bytesToHex(encryptionKey, false));

        final byte[] privacyKey = k2Output.getPrivacyKey();
        Log.v(TAG, "Privacy key: " + MeshParserUtils.bytesToHex(privacyKey, false));
        final int ctl = message.getCtl();
        final int ttl = message.getTtl();
        final int ivi = message.getIvIndex()[3] & 0x01; // least significant bit of IV Index
        final byte iviNID = (byte) ((ivi << 7) | nid);
        final byte ctlTTL = (byte) ((ctl << 7) | ttl);

        final int src = message.getSrc();
        final SparseArray<byte[]> lowerTransportPduMap;
        if (message instanceof AccessMessage) {
            lowerTransportPduMap = ((AccessMessage) message).getLowerTransportAccessPdu();
        } else {
            lowerTransportPduMap = ((ControlMessage) message).getLowerTransportControlPdu();
        }

        byte[] encryptedNetworkPayload = null;
        final int pduType = message.getPduType();
        if (message.getPduType() == MeshManagerApi.PDU_TYPE_NETWORK) {
            final ProvisionedMeshNode node = mUpperTransportLayerCallbacks.getNode(message.getSrc());
            final byte[] lowerTransportPdu = lowerTransportPduMap.get(segment);
            node.setSequenceNumber(MeshParserUtils.getSequenceNumber(message.getSequenceNumber()));
            //final int sequenceNumber = node.incrementSequenceNumber();//incrementSequenceNumber(mNetworkLayerCallbacks.getProvisioner(), message.getSequenceNumber());
            final byte[] sequenceNum = MeshParserUtils.getSequenceNumberBytes(node.incrementSequenceNumber());
            message.setSequenceNumber(sequenceNum);

            Log.v(TAG, "Sequence Number: " + MeshParserUtils.bytesToHex(sequenceNum, false));

            final byte[] nonce = createNetworkNonce(ctlTTL, sequenceNum, src, message.getIvIndex());
            encryptedNetworkPayload = encryptPdu(lowerTransportPdu, encryptionKey, nonce, message.getDst(), SecureUtils.getNetMicLength(message.getCtl()));
            if (encryptedNetworkPayload == null)
                return null;
            Log.v(TAG, "Encrypted Network payload: " + MeshParserUtils.bytesToHex(encryptedNetworkPayload, false));
        }

        if (encryptedNetworkPayload == null)
            return null;

        final byte[] privacyRandom = createPrivacyRandom(encryptedNetworkPayload);
        //Next we create the PECB
        final byte[] pecb = createPECB(message.getIvIndex(), privacyRandom, privacyKey);

        final byte[] header = obfuscateNetworkHeader(ctlTTL, message.getSequenceNumber(), src, pecb);
        final byte[] pdu = ByteBuffer.allocate(1 + 1 + header.length + encryptedNetworkPayload.length).order(ByteOrder.BIG_ENDIAN)
                .put((byte) pduType)
                .put(iviNID)
                .put(header)
                .put(encryptedNetworkPayload)
                .array();
        message.getNetworkLayerPdu().put(segment,pdu);
        return message;
    }

从修改后的代码我们可以看到改动了两处
1.去除创建了新的Pdu Array

//移除
final SparseArray<byte[]> pduArray = new SparseArray<>();

2.对Message里面的pdu不直接覆盖,而且获取到再添加

//获取后再添加
  message.getNetworkLayerPdu().put(segment,pdu);

因此,问题得到迎刃而解.

如果任何疑问,请联系邮箱:569133338@qq.com

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值