目录

  • 《关于》
  • 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 层协议栈

那么必须解决:

  1. 节点入网;
  2. 数据帧的加/解密;
  3. 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 计算方法如下:

mac 配置 mysql mac 配置网关_Data


计算 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 帧的描述如下:

mac 配置 mysql mac 配置网关_lora_02


LoRaWAN 节点处理 Join-Accept 帧的顺序是:

  1. 先计算 Join-Accept 帧的 MIC;
  2. 再使用 加密 的方式进行解密。(根据规范,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:

  1. 如果收到 Confirmed Data Up 帧, 则触发应答,Mac 网关需要在 1 s (CN470 地区规范)后下行发送应答帧。(目前暂不考虑在节点的 Rx2 窗口下发数据,不过要在 Rx2 下发的话,参考 Rx1 的下发代码重新实现即可)
  2. 对 Data Up 帧进行 MIC验证与数据解密。
  3. 对下行的 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 帧的处理顺序是:

  1. 数据加密;
  2. 计算 MIIC。

那么,Mac 网关对 Data Up 帧的处理顺序是:

  1. 验证 MIC;
  2. 数据解密。

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 的计算如下图:

mac 配置 mysql mac 配置网关_Mac_03


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 节,节点侧数据加密如下:

mac 配置 mysql mac 配置网关_Data_04


分析加密流程可知,应用负载不直接参与 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 节中,我们可以看到如下说明:

mac 配置 mysql mac 配置网关_Data_05


LoRaWan 终端以负载加密的方式实现负载解密,这样终端只需要实现负载加密操作,而不需要实现负载解密操作。

那么,Mac 网关以负载解密的方式来对下行帧的应用负载 (如果存在的话) 进行加密。

阅读 LoRaWAN 官方节点协议栈对下行帧的处理函数 LoRaMacCryptoUnsecureMessage(),可以推断出 Mac 网关处理 Data Down 帧的流程,即:

  1. 进行应用负载加密;
  2. 计算 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 网关的协议级处理主要包括:

  1. LoRaWAN 接收完成协议级处理;
  2. 下行发送协议处理。

这一部分,在文件:/Mac/LoragwMac.c 和 /Mac/LoragwMac.h 中,主要涉及到一些数据对象的抽象设计,比如 Mac 网关对象、LoRaWAN 节点对象等,这部分需要参照源代码才方便讲解。

后续再更新吧,thanks。