背景
- 首先贴上官方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