目录
- 《关于》
- Mac 网关的需要解决的难点
- 解决节点入网(Join-Req 帧和 Join-Accept 帧)
- 处理接收的 Join-Req 帧
- LoRaWAN 节点处理 Join-Req 帧
- Mac 网关处理 Join-Req 帧
- 处理 Join-Accept 帧
- LoRaWAN 节点处理 Join-Accept 帧流程
- Mac 网关侧处理 Join-Accept 帧
- 解决数据帧
- Mac 网关解决问题1:应答
- Mac 网关解决问题2:处理 Data Up 帧(验证 MIC 并解密负载)
- Data Up 帧验证 MIC
- Data Up 帧应用负载解密
- Mac 网关解决问题3:对下行的 Unconfirmed Data Down 帧进行数据加密与 MIC 计算
- Data Down 帧应用负载加密
- Data Down 帧计算 MIC
- 解决 Mac 网关的协议级处理
- 《最后》
- 2020/9/20
《关于》
《假设你已经读过了 LoRaWAN 1.0.3 规范》
《假设你已经深入学习并理解了官方 LoRaWAN 节点协议栈》
《本文章提及的 LoRaWAN 规范特指 1.0.3 版本》
Mac 网关的需要解决的难点
按我的上一篇文章所述,由于 LoRaWAN 规范定义,节点侧和 Mac 网关侧的 LoRaWAN 协议处理是不对称的,我们必须参考 LoRaWAN 规范和官方 LoRaWAN 节点协议栈,然后手动实现 Mac 网关侧的 Mac 层协议栈。
那么必须解决:
- 节点入网;
- 数据帧的加/解密;
- Mac 网关的协议级处理。
解决节点入网(Join-Req 帧和 Join-Accept 帧)
处理接收的 Join-Req 帧
LoRaWAN 节点处理 Join-Req 帧
根据 LoRaWAN 规范的 6.2 节,LoRaWAN 节点会发送 Join-Request 帧来启动 OTAA 入网。
且 Join-Request 帧不加密,只计算 MIC。
那么,很好,Mac 网关对 Join-Request 帧的处理只需要验证 MIC 即可。
参考 LoRaWAN 规范的 6.2.4 节,Join-Req 帧的 MIC 计算方法如下:

计算 Join-Req 帧的 MIC 需要: AppKey 密钥与帧负载进行 AES 加密运算。
这一步在官方 LoRaWAN 节点协议栈的如下函数中进行处理:
/*!
* Prepares the join-request message.
* It computes the mic and add it to the message.
*
* \param[IN/OUT] macMsg - Join-request message object
* \retval - Status of the operation
*/
LoRaMacCryptoStatus_t LoRaMacCryptoPrepareJoinRequest( LoRaMacMessageJoinRequest_t* macMsg );Mac 网关处理 Join-Req 帧
有了上面的知识积累,那么我们可以进行 Mac 网关侧的 Join-Req 处理。
在 Mac 网关侧我们只需要 验证 MIC 即可。
那么 Mac 网关处理 Join-Req 帧的逻辑如下:
根据接收到的 Join-Req 帧,重新计算该帧的 MIC ,然后与接收到的 Join-Req 帧的 MIC 进行比较,如果 MIC 相等,则认为接收的 Join-Req 帧没问题可以进行后续的协议级处理(节点入网注册)。
Mac 网关侧的处理实现如下:
/*********************************************************************************************************
** 函数名称: LoRaMacCryptoVerifyJoinRequest
** 功能描述: 处理 JoinRequest 帧,验证 MIC
** 输 入 : obj 加密对象
** macMsg JoinRequest 帧存储地址
** 输 出 : 加密 API 操作状态
** 全局变量:
** 调用模块:
*********************************************************************************************************/
LoRaMacCryptoStatus_t LoRaMacCryptoVerifyJoinRequest(LoRaMacCryptoCtx_t *obj,
LoRaMacMessageJoinRequest_t* macMsg);处理成功之后,Mac 网关可以进行后续的派生密钥、节点注册等 Mac 层操作。
处理 Join-Accept 帧
在 Join-Req 帧处理完毕之后 Mac 网关需要对节点进行注册,并按照规范(CN470),在 5 s 后下发 Join-Accept 帧。
LoRaWAN 节点处理 Join-Accept 帧流程
参考 LoRaWAN 规范 6.2.5 节,规范对LoRaWAN 节点处理 Join-Accept 帧的描述如下:

LoRaWAN 节点处理 Join-Accept 帧的顺序是:
- 先计算 Join-Accept 帧的 MIC;
- 再使用 加密 的方式进行解密。(根据规范,LoRaWAN 节点只存在加密操作,所以是不对称的)
LoRaWAN 节点处理 Join-Accept 帧的实现如下:
/*!
* Handles the join-accept message.
* It decrypts the message, verifies the MIC and if successful derives the session keys.
*
* \param[IN] joinReqType - Type of last join-request or rejoin which triggered the join-accept response
* \param[IN] joinEUI - Join server EUI (8 byte)
* \param[IN/OUT] macMsg - Join-accept message object
* \retval - Status of the operation
*/
LoRaMacCryptoStatus_t LoRaMacCryptoHandleJoinAccept( JoinReqIdentifier_t joinReqType,
uint8_t* joinEUI,
LoRaMacMessageJoinAccept_t* macMsg );Mac 网关侧处理 Join-Accept 帧
为了后续协议帧的处理,我们必须手动实现网关侧的解密操作。
通过阅读 LoRaWAN 规范,并参考官方 LoRaWAN 节点协议栈,节点侧的加密操作是通过 AES 函数库的加密函数实现的,如下:
/* Encrypt a single block of 16 bytes */
return_type aes_encrypt( const uint8_t in[N_BLOCK], uint8_t out[N_BLOCK], const aes_context ctx[1] );那么,相对应的网关侧的解密操作通过 AES 函数库的解密函数实现,如下:
/* Decrypt a single block of 16 bytes */
return_type aes_decrypt( const uint8_t in[N_BLOCK], uint8_t out[N_BLOCK], const aes_context ctx[1] )经过软件验证,方案可行。
则可以用上述的 AES 解密函数编写出 Mac 网关对 Join-Accept 帧的处理。
/*********************************************************************************************************
** 函数名称: LoRaMacCryptoPrepareJoinAccept
** 功能描述: JoinAccept 加密并组帧,先计算 MIC 再加密 数据
** 输 入 : obj 加密对象
** macMsg JoinReq 帧存储地址
** 输 出 : 加密 API 操作状态
** 全局变量:
** 调用模块:
*********************************************************************************************************/
LoRaMacCryptoStatus_t LoRaMacCryptoPrepareJoinAccept(LoRaMacCryptoCtx_t *obj,
LoRaMacMessageJoinAccept_t* macMsg);解决数据帧
Mac 网关对 Data Up 帧需要处理的问题有 3:
- 如果收到 Confirmed Data Up 帧, 则触发应答,Mac 网关需要在 1 s (CN470 地区规范)后下行发送应答帧。(目前暂不考虑在节点的 Rx2 窗口下发数据,不过要在 Rx2 下发的话,参考 Rx1 的下发代码重新实现即可)
- 对 Data Up 帧进行 MIC验证与数据解密。
- 对下行的 Unconfirmed Data Down 帧进行数据加密与 MIC 计算。(Mac 网关发送的下行帧固定使用 Unconfirmed Data Down)
Mac 网关解决问题1:应答
这个问题的解决在Mac 网关的接收协议级处理函数中(如果你不想自己写这个函数,那你可以参考我的代码实现):
/*********************************************************************************************************
** 函数名称: LoragwMacRxDoneprocess
** 功能描述: Mac 网关接收完成协议级处理,需要处理接收到的不同设备的数据
** 输 入 : NONE
** 输 出 : 成功返回LGW_MAC_SUCCESS,失败返回LGW_MAC_ERROR
** 全局变量:
** 调用模块:
*********************************************************************************************************/
static int LoragwMacRxDoneprocess(void);如果你不想自己写这个函数,你可以参考我的代码实现(下面的是片段,最好查看全部的代码):
case FRAME_TYPE_DATA_CONFIRMED_UP: /* 接收到的是 confirmed 上行 */
/*
* TODO confirmed 触发下行发送事件且要求应答, 遍历, 不要加break
*/
isdownlink = true;
downlinkType = LORAMAC_MSG_TYPE_DATA; /* 下行帧用来发送数据 unconfirmed*/
ackrequest = true;
case FRAME_TYPE_DATA_UNCONFIRMED_UP:
macMsgData.Buffer = payload; /* 填充解析结构串行化缓冲区 */
macMsgData.BufSize = size;
macMsgData.FRMPayload = datapayload;综上,Mac 网关对应答的处理(基于我的代码),是查询 MHDR 位域,然后设置标志位,通过标志位来触发下行应答事件。
Mac 网关解决问题2:处理 Data Up 帧(验证 MIC 并解密负载)
参考规范与官方 LoRaWAN 节点协议栈,节点对 Data Up 帧的处理顺序是:
- 数据加密;
- 计算 MIIC。
那么,Mac 网关对 Data Up 帧的处理顺序是:
- 验证 MIC;
- 数据解密。
Mac 网关对 Data Up 帧的处理的代码实现在:
/*********************************************************************************************************
** 函数名称: LoRaMacCryptoUnsecureMessage
** 功能描述: 网关对上行数据帧的解密和 MIC 校验,先验证MIC,再解密数据
** 输 入 : obj 加密对象
** addrID 地址标识符,标识使用那种解密方式,多播解密或者单播解密
** address 终端地址
** fCntID 数据帧计数器标识符(未使用)
** fCntUp 上行帧计数器
** macMsg 上行帧存储地址
** 输 出 : 加密 API 操作状态
** 全局变量:
** 调用模块:
*********************************************************************************************************/
LoRaMacCryptoStatus_t LoRaMacCryptoUnsecureMessage(LoRaMacCryptoCtx_t *obj, AddressIdentifier_t addrID,
uint32_t address, FCntIdentifier_t fCntID,
uint32_t fCntUp, LoRaMacMessageData_t* macMsg)Data Up 帧验证 MIC
参考规范 4.4 节,MIC 的计算如下图:

MIC 的计算需要使用 NwkSKey 密钥与Data Up 帧(不包括 MIC 的其他位域),进行 AES128 的 CMAC 计算,并取 CMAC 的前四个字节做为 MIC。
综上,Mac 网关的验证需要使用接收到的 Data Up 帧(不包括 MIC)重新计算 MIC,并将计算出来的 MIC 位域与 Data Up 帧的 MIC 位域进行比较,如果相等,则验证通过。
这个函数可以直接使用 LoRaWAN 节点协议栈接口,因为都只是计算 CMAC,并没有什么实质上的不同。代码实现如下:
/*********************************************************************************************************
** 函数名称: VerifyCmacB0
** 功能描述: 在前面添加 B0 块来验证 CMAC。即验证MIC
** 输 入 : obj 加密对象
** msg 需要计算完整性代码的数据存储地址
** len 数据长度
** keyID 密钥标识符
** isAck 是否为应答数据帧
** dir 数据帧方向( Uplink:0, Downlink:1 )
** devAddr 当前终端地址
** fCnt 帧计数器
** expectedCmac 期望的 MIC
** 输 出 : 加密 API 操作状态
** 全局变量:
** 调用模块:
*********************************************************************************************************/
static LoRaMacCryptoStatus_t VerifyCmacB0(LoRaMacCryptoCtx_t *obj, uint8_t* msg, uint16_t len,
KeyIdentifier_t keyID, bool isAck, uint8_t dir,
uint32_t devAddr, uint32_t fCnt, uint32_t expectedCmac);Data Up 帧应用负载解密
参考规范 4.3.3 节,节点侧数据加密如下:

分析加密流程可知,应用负载不直接参与 AES 运算,我们通过 NwkSKey / AppSKey (通过 FPort 位域选择)与 Ai 块来进行 AES 的加密运算,获得 Si 块,然后通过 Si 块与应用负载异或得到加密后的应用负载。
综上,Mac 网关侧的数据解密只需计算出相同的 Si 块,然后与加密后的应用负载再次异或,即可解密出原始的应用负载。
直接使用 LoRaWAN 节点协议栈的加密 API:
/*********************************************************************************************************
** 函数名称: PayloadEncrypt
** 功能描述: 加密数据帧
** 输 入 : obj 加密对象
** buffer 数据帧存储地址
** size 数据帧长度
** keyID 用于加密的密钥
** address 终端地址
** dir 数据帧方向
** frameCounter 帧计数器
** 输 出 : 加密 API 操作状态
** 全局变量:
** 调用模块:
*********************************************************************************************************/
static LoRaMacCryptoStatus_t PayloadEncrypt(LoRaMacCryptoCtx_t *obj, uint8_t* buffer, int16_t size,
KeyIdentifier_t keyID, uint32_t address, uint8_t dir,
uint32_t frameCounter);Mac 网关解决问题3:对下行的 Unconfirmed Data Down 帧进行数据加密与 MIC 计算
在规范 6.2.5 节中,我们可以看到如下说明:

LoRaWan 终端以负载加密的方式实现负载解密,这样终端只需要实现负载加密操作,而不需要实现负载解密操作。
那么,Mac 网关以负载解密的方式来对下行帧的应用负载 (如果存在的话) 进行加密。
阅读 LoRaWAN 官方节点协议栈对下行帧的处理函数 LoRaMacCryptoUnsecureMessage(),可以推断出 Mac 网关处理 Data Down 帧的流程,即:
- 进行应用负载加密;
- 计算 MIC。
代码实现如下:
/*********************************************************************************************************
** 函数名称: LoRaMacCryptoSecureMessage
** 功能描述: 网关对下行数据帧加密和MIC计算,先加密数据再计算MIC
** 输 入 : obj 加密对象
** fCntDown 下行帧计数器
** txDr 传输数据速率(未使用)
** txCh 上行信道索引(未使用)
** macMsg 数据帧存储地址
** 输 出 : 加密 API 操作状态
** 全局变量:
** 调用模块:
*********************************************************************************************************/
LoRaMacCryptoStatus_t LoRaMacCryptoSecureMessage(LoRaMacCryptoCtx_t *obj, uint32_t fCntDown, uint8_t txDr,
uint8_t txCh, LoRaMacMessageData_t* macMsg);Data Down 帧应用负载加密
有了前文的知识积累,Mac 网关对 Data Down 帧应用负载加密与对 Data 帧的解密其实是一样的,可以直接使用 LoRaWAN 节点协议栈的加密 API:
/*********************************************************************************************************
** 函数名称: PayloadEncrypt
** 功能描述: 加密数据帧
** 输 入 : obj 加密对象
** buffer 数据帧存储地址
** size 数据帧长度
** keyID 用于加密的密钥
** address 终端地址
** dir 数据帧方向
** frameCounter 帧计数器
** 输 出 : 加密 API 操作状态
** 全局变量:
** 调用模块:
*********************************************************************************************************/
static LoRaMacCryptoStatus_t PayloadEncrypt(LoRaMacCryptoCtx_t *obj, uint8_t* buffer, int16_t size,
KeyIdentifier_t keyID, uint32_t address, uint8_t dir,
uint32_t frameCounter);Data Down 帧计算 MIC
参考规范 4.4 节与前文,Mac 网关计算 Data Down 帧的 MIC其实与终端计算 Data Up 帧的 MIC 是一样的。
阅读 LoRaWAN 官方节点协议栈,我们可以使用下面的函数来计算 MIC:
/*********************************************************************************************************
** 函数名称: ComputeCmacB0
** 功能描述: 在前面添加 B0 块来计算 CMAC,即MIC
** 输 入 : obj 加密对象
** msg 需要计算完整性代码的数据存储地址
** len 数据长度
** keyID 密钥标识符
** isAck 是否为应答数据帧
** dir 数据帧方向( Uplink:0, Downlink:1 )
** devAddr 当前终端地址
** fCnt 帧计数器
** cmac 计算出来的 CMAC 存储地址
** 输 出 : 加密 API 操作状态
** 全局变量:
** 调用模块:
*********************************************************************************************************/
static LoRaMacCryptoStatus_t ComputeCmacB0(LoRaMacCryptoCtx_t *obj, uint8_t* msg, uint16_t len,
KeyIdentifier_t keyID, bool isAck, uint8_t dir,
uint32_t devAddr, uint32_t fCnt, uint32_t* cmac);解决 Mac 网关的协议级处理
Mac 网关的协议级处理主要包括:
- LoRaWAN 接收完成协议级处理;
- 下行发送协议处理。
这一部分,在文件:/Mac/LoragwMac.c 和 /Mac/LoragwMac.h 中,主要涉及到一些数据对象的抽象设计,比如 Mac 网关对象、LoRaWAN 节点对象等,这部分需要参照源代码才方便讲解。
后续再更新吧,thanks。
















