4. NAS消息编码与解码:从ASN.1到比特流的实战

各位同学,今天我们来聊聊NAS消息的编码与解码。说实话,这是5G核心网开发中最基础也最容易踩坑的地方。我刚开始做NAS层开发时,就因为在编码上少了一个字节,导致整个注册流程失败,排查了整整两天。嗯,从那以后,我对这块就格外上心。

4.1 ASN.1基础回顾:为什么非要用它?

ASN.1,全称是Abstract Syntax Notation One。说白了,它是一种描述数据结构的语言。你想想看,5G核心网里那么多网元——AMF、SMF、UPF,它们之间要互相通信,总得有个大家都认的"语法"吧?ASN.1就是干这个的。

我个人习惯把ASN.1理解成"数据结构的蓝图"。它不关心具体怎么传输,只关心数据长什么样。比如:

-- 一个简单的NAS消息定义
RegistrationRequest ::= SEQUENCE {
    extendedProtocolDiscriminator  ExtendedProtocolDiscriminator,
    pduSessionID                   PDU-Session-ID OPTIONAL,
    messageType                    MessageType,
    -- 后面跟着一堆IE...
}

这里要注意,ASN.1的编码规则有好几种——BER、DER、PER、UPER等。5G NAS层用的是UPER(Unaligned Packed Encoding Rules)。为什么选它?因为压缩率高,节省空口资源。我在项目中实测过,同样一条消息,用UPER编码比用BER能省30%左右的比特数。

核心要点:ASN.1是描述语言,UPER是编码规则。两者配合,才能把结构化的消息变成比特流。

4.2 NAS-PDU结构解析:从外到内看透它

一个完整的NAS-PDU,从结构上看分三层:

  1. NAS消息头——固定部分,告诉接收方"我是谁"
  2. NAS消息体——核心内容,包含各种IE
  3. 安全头部(可选)——完整性保护和加密用

我画了一张图,帮你直观理解这个结构:

NAS-PDU 结构层次图 NAS消息头 (NAS Message Header) Extended Protocol Discriminator (1字节) | PDU Session ID (1字节) | Message Type (1字节) NAS消息体 (NAS Message Body) IE1 (Type+Length+Value) | IE2 (Type+Length+Value) | ... | IEn 每个IE按TLV或TV格式组织,长度可变 安全头部 (Security Header) — 可选 Message Authentication Code (MAC) | Sequence Number 注:安全头部仅在NAS消息需要完整性保护或加密时出现

看到这张图,你应该能明白:NAS消息的编码,本质上就是把这些结构化的字段,按照UPER规则一个比特一个比特地拼起来。

4.3 NAS消息头编码:三个关键字段

消息头是NAS-PDU的"身份证"。我重点讲三个字段:

4.3.1 Extended Protocol Discriminator (EPD)

这个字段占1个字节,用来区分不同的协议。5G NAS里,EPD的值固定为0x2E(十进制46)。为什么是这个值?3GPP TS 24.007里定义的,记住就行。

小技巧:我在调试时,经常先看EPD值。如果收到的消息EPD不是0x2E,那基本可以断定是协议版本不匹配或者编码出错了。

4.3.2 PDU Session ID

这个字段标识PDU会话。取值范围0~15,占半个字节(4比特)。注意:不是所有NAS消息都有这个字段。比如Registration Request里就没有,但PDU Session Establishment Request里必须有。

编码规则很简单:

  • 如果消息涉及PDU会话,PSI占高4位,低4位保留为0
  • 如果不涉及,整个字节为0x00

4.3.3 Message Type

这是消息头的核心。1个字节,标识具体的NAS消息类型。比如:

消息类型 Message Type值 说明
Registration Request 0x41 终端发起注册
Registration Accept 0x42 网络接受注册
PDU Session Establishment Request 0xC1 终端请求建立PDU会话
PDU Session Establishment Accept 0xC2 网络接受建立
Deregistration Request 0x45 去注册请求

我曾经犯过一个低级错误:把Registration Request的Message Type写成了0x42。结果AMF一直给我回Registration Reject,日志里报"Message type not supported"。排查了半天才发现是编码写反了。所以,Message Type的值一定要和3GPP规范逐字节核对

4.4 IE编码规则:TLV格式详解

消息体由一个个IE(Information Element)组成。每个IE的编码格式,我总结为三种:

4.4.1 TLV格式(Type-Length-Value)

最常用的格式。Type占1字节,Length占1字节,Value占Length个字节。比如:

// 一个简单的IE编码示例
IE Type:  0x50  (1 byte)
IE Length: 0x03  (1 byte)  
IE Value:  0x01 0x02 0x03  (3 bytes)
// 总共:1+1+3 = 5 bytes

4.4.2 TV格式(Type-Value)

固定长度的IE用这种格式。Type占1字节,Value长度由规范固定。比如PDU Session ID就是TV格式,Type+Value一共2字节。

4.4.3 LV格式(Length-Value)

Type隐含在上下文中,只传Length和Value。这种格式在NAS里用得少,但某些特殊IE会用到。

注意:编码时最容易出错的是Length字段。我见过太多人把Length算错——多1字节或少1字节,接收方解析就会错位。我的建议是:先填充Value,再回头算Length。这样最保险。

4.5 实战:编码一个Registration Request

光说不练假把式。我们手写一个Registration Request的编码过程:

// 步骤1:消息头
byte[0] = 0x2E;  // Extended Protocol Discriminator
byte[1] = 0x00;  // PDU Session ID (不涉及,填0)
byte[2] = 0x41;  // Message Type (Registration Request)

// 步骤2:消息体 - 添加IE
// IE1: 5GS Registration Type
byte[3] = 0x50;  // Type
byte[4] = 0x01;  // Length
byte[5] = 0x01;  // Value (Initial Registration)

// IE2: 5GS Mobile Identity
byte[6] = 0x51;  // Type
byte[7] = 0x08;  // Length (SUCI长度)
byte[8] = 0x01;  // Value: SUPI format
byte[9] = 0x02;  // ... 后续字节省略

// 最终NAS-PDU: 2E 00 41 50 01 01 51 08 01 02 ...

你看,编码其实就是把规范里的定义,翻译成字节流。解码就是反过来——收到字节流,按照同样的规则拆解成结构体。

4.6 避坑指南:我踩过的三个坑

最后,分享几个实战中容易出问题的地方:

  1. 字节序问题:NAS消息统一用大端序(Big Endian)。我见过有人用x86的小端序去解析,结果所有字段都反了。
  2. 可选字段的处理:很多IE是OPTIONAL的。编码时如果某个IE不存在,千万别填0字节占位,直接跳过。解码时也要做好"字段不存在"的判断。
  3. 安全头部的长度:如果消息带了安全头部,记得在计算总长度时把MAC(4字节)和Sequence Number(2字节)算进去。我吃过这个亏,导致AMF一直报完整性校验失败。

总结一下:NAS消息编码解码,说白了就是"规范翻译机"。你只要把3GPP TS 24.501里的ASN.1定义,按照UPER规则翻译成字节流,就对了。多练几次,形成肌肉记忆,后面就顺了。


公众号:蓝海资料掘金营,微信deep3321