###1. nfc p2p mode
在nfc的 R/W 和 CE 模式中, nfc device 只能单向和 nfc tag交互, 即只能由nfc device发起读写操作
事实上, nfc技术实际上可以支持nfc device之间互相交互, 为此, nfc forum 定义了 P2P 模式
###2. LLCP
nfc 的P2P 协议栈的最高层为 LLC (Logic Link Control), 这一协议称为为 LLCP
LLC层考虑的的是物理寻址, 链路管理, 数据传输, LLCP向上提供了2种服务
- 面向连接的服务 : 类似于TCP, 须在LLC层先建立逻辑连接关系(类似于TCP的connect和accept), LLC层还会处理数据包丢失, 重传,接收确认
- 无连接的服务 : 类似于UDP, 双发无需建立逻辑连接关系即可收发数据
nfc forum 还在LLCP之上开发了更为友好的协议, 例如 基于LLCP的面向连接的服务开发了 SNDEP 和 CHP
####2.1 LLCP 数据包格式
LLC 层的数据封包格式如下
- DSAP : 目标服务接入点, 取值为 0x00 ~ 0x3f
- SSAP : 源服务接入点, 取值为 0x00 ~ 0x3f
DSAP 和 SSAP 类似于 TCP/IP 的端口号, LLCP中通过DSAP和SSAP即可唯一确定通信的双方, 因为两个点对点的nfc device设备只要进入通信距离即可开始通信, LLCP 上开发的不同的协议可以使用不同的SAP, 类似于基于tcp 的不同的应用协议使用不同的端口号, SAP的分配如下:
- 0x00 ~ 0x0f : WKS (well known service) 的端口号, 例如0x00为LLC 链路管理组件, 0x01 为SDP (Service Discover Protocol), 0x04 为 SNEP
- 0x10 ~ 0x1f : 本地服务的端口号, 远端设备可以通过SDP搜索到它们
- 0x20 ~ 0x3f : 本地服务的端口号, 远端设备不能通过SDP搜索到它们
在使用时, 可以直接指定具体的DSAP号, 也可以指定DSAP为0x01, 然后指定 service name, 通过SDP来搜索端口号
- PTYPE : 指明LLC包的类型, 类型如下
- 0x00 : SYMM, 不包含 Seq num 字段和 information 字段
- 0x01 : PAX, Parameter Exchange 用于交换 LLC 层配置信息, 不包含 Seq num 字段
- 0x02 : AGF, Aggregated Frame , 不包含 Seq num 字段
- 0x03 : UI, Unnumbered Information 无编号的数据帧, 不包含 Seq num 字段
- 0x04 : CONNECT, 不包含 Seq num 字段
- 0x05 : DISC, 不包含 Seq num 字段和 information 字段
- 0x06 : CC, Connection Complete, 不包含 Seq num 字段
- 0x07 : DM, Disconnected Mode, 不包含 Seq num 字段
- 0x08 : FRMR, Frame Reject, 不包含 Seq num 字段
- 0x09 : reserved
- 0x0a : reserved
- 0x0b : reserved
- 0x0c : I , Information, 包含 Seq num 字段, information 字段可为空
- 0x0d : RR, Receive Ready, 包含 Seq num 字段, 不包含information 字段
- 0x0e : RNR, Receive Not Ready, 包含 Seq num 字段, 不包含information 字段
- 0x0f : reserved
- Seq num : LLCP包的编号, 有些类型的LLC包不包含此字段
- Information : LLCP包的负载数据, 有些类型的LLC包不包含此字段
###3. 建立LLC的Logic link connection
来利用 LLC 提供的 “有连接”和”无连接” 的传输服务进行传输前,LLCP需要先和对端的设备建立 Logic Link Connection
####3.1 Link Activation(链路激活)
当nfc device的MAC层组件, 检测到有运行LLCP协议的nfc device进入了连接范围并且成功完成MAC层的连接,它将会通知LLCP,LLCP将会开始 Link Activation 阶段 (两端的nfc device都将进入链路激活阶段)
进入 Link Activation 后, 两端的nfc device将分别扮演 Initiator 和 Target, 这两个角色如何分配要靠两端的nfc device协商
###3.2 parameter Exchange(LLC 参数配置)
使用 PAX 包来交换 LLC 的参数, PAX 包在 Information 字段中保存 LLC 配置参数,参数的类型和作用如下
- VERSION : 0x01 指明LLCP的版本号,通过检查版本号可以判断两端的LLCP是否兼容
- MIUX : 0x02 MIU为指明nfc device能处理的的LLC数据包的Information字段的最大长度, 默认为128byte, MIUX=真实的MIU-128
- WKS : 0x03 Well Known service list, 用于告知对端的nfc device, 在0x00~0x0f SAP端口上, 那些端口上有模块在监听, 每一bit标识一个端口
- LTO : 0x04 Link Timeout, 基本单位为10ms
- OPT : 0x05 Options,目前只有 Link Service Class 选项, 表明本设备支持的数据传输的类型, 无连接, 有连接还是都支持
只有LLCP版本号兼容时, 才能成功完成 Parameter Exchange 过程, 继续后续的步骤, 否则, 将会停止 Link Activation 并且通知MAC层 Link Activation fail, 检查LLCP版本号的规则如下
- LLCP VERSION 由 Major num 和 Minor num 组成 (例如 2.3 ), 且两端都要进行检查
- 若两端的LLCP VERSION 的 Major num 和 Minor num都相同, 则版本匹配
- 若两端的LLCP VERSION 的 Major num 相同, Minor num 不同, 则版本匹配且最后协商的结果使用两个 Minor num中的较小值
- 若两端的LLCP VERSION 的 Major num 不相同,则由VERSION较大的一方来判断是否匹配, 若判定匹配, 则最后协商的结果是VERSION值较小的那一个
两端的设备使用默认的MIU(128byte), 若某一端能够支持大于128byte的MIU, 则可以在PAX包中携带MIUX参数
交换LLC配置参数时, Initiator 端的动作如下:
- 通过 PAX 类型的 LLC 包将自己的 LLC 配置参数发送给 Target, 然后等待 Target 端回复一个 PAX 包并且检查其中的参数
- 若检查到VERSION 匹配, 则协商MIU值,双方的 Logical Link 成功建立, 随后就可以进入正常的工作阶段
- 若检查到VERSION 不匹配,则停止 Link Activation 并且通知MAC层 Link Activation fail
交换LLC配置参数时, Target 端的动作如下:
- 等待Initiator端发送PAX包, 接收到后检查其中的参数
- 若检查到VERSION 匹配, 则协商MIU值, 然后发送一个PAX包到Initiator,携带LLC参数, 然后进入正常工作阶段
- 若检查到VERSION 不匹配,则停止 Link Activation 并且通知MAC层 Link Activation fail
####3.3 Normal Operation Phase (正常工作阶段)
在正常工作阶段, 可以进行如下的操作:
- 进行”无连接”的传输
- 进行“面向连接”的传输
- 聚合PDU 或者分解聚合的PDU
- 进行”对称”处理
- 进行 Link Deactivation
进入正常工作阶段后:
- Initiator 必须初始化其 “对称” 流程, 以准备好接收来自另一端的 LLCP PDU
- Target 必须初始化其 “对称” 流程, 以准备发送 LLCP PDU 到另一端
LLC 的对称性后文会解释, 即必须满足双方依次发送, 每次发送一个PDU
####3.4 Link Deactivation(链路断开)
如下事件会触发 Link Deactivation
- Link Timeout
- 发送 DISC 包给远端请求Link Deactivation
当2个建立了连接的NFC device离开有效传输距离时, 也会导致Link Deactivation, 暂时不清楚是通过那一种方式实现的
在LLCP开始执行 Link Deactivation 时, 首先通知到LLC之上的协议断开了连接, 然后执行 MAC 层的 Link Deactivation
###4. 无连接的传输
在LLC 和对端建立了Logic Link Connection之后, 只需要对应的SAP端口可用就可以使用无连接的传输服务,接收/发送 数据包
无连接的传输使用 UI 包,当接收端接收到UI包时,不会应答
有些条件下, 接收端可能会忽略掉接收到的合法的数据包, 不同的LLCP实现可以选择是否要通知上层的应用
###5. 有连接的传输
在LLC 和对端建立了Logic Link Connection之后, 有连接的传输还必须先建立 data link connection, 然后使用 I 包来传送数据
####5.1 连接相关的状态值
LLCP 为每一个 data link connection 维护一份状态值(这些值都是 模-16 因此取值范围为 0~15):
- V(S) : Send State variable 代表下一个将要发送的 I 包的seq num, 依次加1
- V(SA) : Send Acknowledgement State Variable, 为最近收到的 RR 包 或者 RNR 包的 seq num
- V(R) : Receive State Variable 代表下一个将要收到的 I 包的seq num, 依次加1
- V(RA) : Receive Acknowledgement State Variable 代表最后一次发送的 RR 包或者 RNR 包的seq num
另外, LLC 数据包的 Seq num 字段分为2部分
- N(S) : send Seq num
- N(R) : receive Seq num
####5.2 建立连接的过程
无论是 Initiator 还是 Target, 任意一端的LLCP都可以建立 data link, 通常, 将发起连接请求的一端称为 client 端,提供连接服务的一端称为server端 建立连接时, server 端的动作如下:
- 接收到 CONNECT 包后
- 如果不能处理 CONNECT 请求, 则恢复 DM 包并且携带 reason code
- 如果能够处理 CONNECT 请求, 则进行后续的步骤
- 处理 CONNECT 包中的参数(MIUX, RW, SN)
- 通知上层 service, 有 client 请求建立 data link
- 等待上层 service 回应是否接收连接请求
- 如果service拒绝连接请求,则回应 DM 包并且携带reason code
- 如果service接受连接请求, 则回应 CC 包并且携带参数(MIUX, RW, SN)
- 设置 V(S), V(R), V(SA), V(RA) 为0
- 进入数据传输阶段
建立连接时, client 端的动作如下:
- 向 server 端发送 CONNECT 包, 携带参数(MIUX, RW, SN), 等待server端的回应
- 若 接收到了一个 CC 包, 且在其中的SSAP值和自己发出的且还没有被CC包或者DM包回应的 CONNECT 包的DSAP值相同, 则
- 设置 V(S), V(R), V(SA), V(RA) 为0
- 处理 CC 包中携带的参数(MIUX, RW, SN)
- 进入数据传输阶段
- 若 接收到了一个 DM 包, 且在其中的SSAP值和自己发出的且还没有被CC包或者DM包回应的 CONNECT 包的DSAP值相同, 则终止建立连接的过程, 并且通知给上层
在使用某一个DSAP 发送 CONNECT 包后, 在该请求未接收到 CC 包或者 DM 包回复前, 不能使用该DSAP再此发送 CONNECT 包
####5.3 CONNECT 包和 CC 包的参数
CONNECT 包 和 CC 包中可以携带如下的参数
- MIUX : 0x01 同 Link Activation 阶段的MIUX
- RW : 0x5 和接收确认有关, RW=1时, 每接收到一个数据包就进行一次确认
- SN : 0x6 service name, LLCP 中 client 可以使用两种方法来连接 server
- 使用 DSAP 端口号, 例如 SNEP 的服务端口为 0x04
- 设置 DSAP为0x01, 使用服务名称来指定服务, 例如 SNEF 的服务名为 “urn:nfc:sn:snep”
####5.4 发送数据包
当该连接满足 V(S) == V(SA) + RW
时, 该连接上不能再继续发送数据包, 因为之前发送的数据包还没有收到回复
在发送数据包时, 发送端的LLCP按如下的步骤修改该连接的状态值
N(S) = V(S)
N(R) = V(R)
V(S) += 1
####5.5 接收数据包
在接收端, 当接收到数据包, 且满足 N(S) == V(R)
时, LLCP会将该数据包传递给上层协议, 并且 V(R) += 1
, 然后发送应答数据包
####5.6 发送应答
- 当接收端接收到了一个 I 包, 且自身也有I包等待发送,则可以在发送I包时进行应答 (即发送时设置 N(R) = V(R))
- 当接收端接收到了一个 I 包, 且自身没有I包等待发送, 且自身也不处于 receiver busy 状态, 则需要发送一个 RR 包来应答
- 当接收端接收到了一个 I 包, 且自身没有I包等待发送, 且自身也处于 receiver busy 状态, 则需要发送一个 RNR 包来应答
receiver busy: 在正常的传输阶段, LLC之上的协议有可能会暂时拒绝从LLC接收数据(比如忙于处理数据), 因此,LLCP将不能正常分发接收到的数据包, 因此LLCP将会发出一个 RNR 包通知对端自己处于 receiver busy 状态, 在处于 receiver busy 状态时, LLCP能够继续接收和缓冲 I 包, 处理控制请求, 发送 I 包和控制请求, 并且在退出 receiver busy状态时, 要发送 RR 包通知对端
####5.7 接收应答
在接收到的 I/RR/RNR 包中的 N(R) 字段表示该包的发送方成功接收到了编号为 N(R)-1 的包, 因此, 将 V(SA) 更新为 N(R)
- 当接收到 RNR 包时, 表明对端处于 receiver busy 的状态, 因此, 要停止向对端发送 I 包
- 当接收到一个来自处于 receiver busy 状态的LLCP的 RR 包,表明对端已经不再处于 receiver busy 状态, 因此可以继续正常向对端发送
####5.8 断开连接
当某一端LLCP希望断开连接时:
- 向对端发送 DISC 包, 然后进入 Disconnecting 阶段
- 在 Disconnecting 阶段, LLCP 不能在该连接上发送任何PDU, 并且忽略在该连接上接收到的包, 除了 DM 包
- 在接收到 DM 包后, 通知上层协议连接已经断开, 连接将被 close, 资源被释放, 随后接收到的数据将会被忽略
当某一端LLCP接收到 DISC 包后, 回应DM包, 并且通知上层协议连接已经断开, 然后 clsoe 连接
虽然断开了 data link connection, 但是logic connection还存在, 还是可以继续进行 无连接的传输, 无连接的传输和有连接的传输使用不同的数据包, 很容易区分
###6. 帧聚合
- LLCP 应当能够能接收并且分解聚合了的 AGF, 得到PDUs
- LLCP 应当能够聚合 PDUs 形成 AGF 包并且发送
帧聚合只能被 LLC 层的链路管理组件使用, LLC 链路管理组件应当按照PDUs的顺序来聚合帧形成AGF, 按照PDUs的顺序分解AGF, 即通过AGF来发送的PDUs的 发送/接收 顺序应当和它们单独发送一样
聚合的AGF不会被应答
###7. 对称处理
传统上,在NFC 的 MAC层只有发起者能开始发送数据和请求数据,而LLCP协议通过一种称为异步平衡模式(ABM)的机制来实现互相交互, ABM机制是通过对称机制来实现的
对称机制 : 当LLCP接收到来自对端的LLC PDU后, 就要准备发送一个 LLC PDU 到对端去(要在本端的link timeout限定的时间内),即双方必须依次互相发送 LLC PDU, 通过这种对称机制, 还可以检测两端的连接是否断开(即在超时时间内没有接收到LLC PDU)
应当是两端之间的Logic Link满足对称性要求, 而不是Logic Link之上的Data Link connection满足对称性要求
在LLCP中运行面向连接的传输时, 因为很多数据包都不许要被应答, 并且通信双方的数据流量通常是不对称的, 因此很多情况下不能够满足双发依次发送LLC PDU, 这个时候就需要使用 SYMM 包来充数
SYMM包的本意就是为了用于满足对称性的, 它应该被最后考虑
- 若某一端需要在超时时间之内发送一个 LLC PDU, 并且恰好需要发送除 SYMM 包之外的PDU, 则发送这些PDU中的一个
- 若某一端需要在超时时间之内发送一个 LLC PDU, 并且没有其它的包需要发送, 则发送 SYMM 包
###8. SNEP
直接使用 LLCP 协议来进行通信还是比较复杂, NFC Forum 定义了 SNEP 协议, 用于在两个P2P模式的NFC device之间传递 NDEF 消息
SNEP 在 WKS(Well-Known-Service)中的SAP为0x04, service name为“urn:nfc:sn:snep”
SNEP 协议本身非常简单, 使用 Request/Response 的工作方式:
- SNEP client发送 SNEP Request 消息到server端
- SNEP server回复 SNEP Response 消息给client端
其中 Request 表示请求类型, Response 表示处理结果,Lenth字段表示Information的长度, Information 字段用于承载NDEF消息
SNEP 当前支持的 Request 类型(取值范围为 0x00 ~ 0x7f)
- Continue : 0x00 继续发送分片SNEP数据
- Get : 0x01 客户端请求从服务端返回SNEP数据
- Put : 0x02 客服端发送一个SNEP数据到服务端
- Reject : 0x7F 停止发送分片SNEP消息
SNEP 当前支持的 Response 类型(取值范围为 0x80 ~ 0xff)
- Continue : 0x80 继续发送分片SNEP数据
- Success : 0x81 操作成功
- Not Found : 0xc0 未发现资源
- Excess Data : 0xc1 资源大小超出了限制
- Bad Request : 0xc2 错误的请求
- Not Implemented : 0xe0 不支持的请求
- Unsupported Version : 0xe1 不支持的协议版本
- reject : 0xff 停止发送分片SNEP消息