SOME/IP介绍

目录

1、介绍

2,首字母缩略词

3、协议规范

4 SOM/IP消息格式规范

4.1Message ID消息ID[32位]

4.2Length长度32位

 4.3Request ID客户端ID32位

4.4ProtocolVersion协议版本8位

4.5InterfaceVersion接口版本[8位]

4.6MessageType消息类型[8位]

 4.7ReturnCode返回码8位

4.8Payload负载

4.9字节序

4.10数据结构的序列化

4.11基本数据类型

4.12结构化数据类型(structs)

4.13 结构化数据类型和带标识符和可选成员的参数

4.14 请求/响应通信

4.15火炬通信

4.16 通知事件

4.17 发送通知的策略

4.18 字段

4.19 错误处理

 4.20 错误消息

4.21 错误处理概述

4.22通信错误和处理通信错误

4.23 接口版本的兼容性规则

5 Configuration Parameters

6 协议使用和指南

6.1 选择传输协议

6.2 传输 CAN 和 FlexRay 帧

6.3 为结构体插入填充

6.4 SOME/IP 的安全考虑


1、介绍

        本协议规范规定了AUTOSAR协议“可扩展的面向服务的IP中间件(SOME/IP)”的格式、消息序列和语义。

        SOME/IP是一种自动/嵌入式通信协议,支持远程过程调用、事件通知和底层序列化/有线格式。唯一有效的缩写是SOME/IP。其他缩写(例如Some/IP)是错误的,不应使用。

        SOME/IP应在不同的操作系统(如AUTOSAR、GENIVI和OSEK)上实现,甚至在没有操作系统的嵌入式设备上实现。SOME/IP应用于ECU客户端/服务器之间的串行化。SOME/IP的实现允许AUTOSAR解析RPC PDU并将信号传输到应用程序。

SOME/IP PRS将描述SOME/IP的以下两个方面:

1. SOME/IP线路格式的规范(序列化)

   - 头格式的结构

   - 如何根据SOME/IP序列化不同的数据类型

2. 基于事件和RPC的通信协议的规范

   - 传输协议

   - 管理SOME/IP的RPC的规则

(1、RPC(Remote Procedure Call,远程过程调用)是一种计算机通信协议,它允许程序调用其他地址空间(通常是共享网络的另一台机器上)的过程或子程序,就像是本地调用一样,而不需要程序员显式地编写网络代码。RPC的目标是使远程计算看起来像是本地计算。

2、SOME/IP(Service-Oriented Middleware over IP)序列化是指将数据转换为符合 SOME/IP 协议规范的二进制格式,以便在网络上传输。SOME/IP 是一种轻量级的通信协议,通常用于汽车电子领域中的通信和服务发现。在SOME/IP中,数据通过网络传输之前,需要进行序列化,以确保发送和接收方都能正确地解释和处理数据。序列化的过程涉及将结构化的数据对象转换为字节流,以便能够在网络上传输。反之,接收方需要进行反序列化,将字节流还原为原始的数据对象序列化是网络通信中常见的操作,它确保在不同系统之间的数据交换能够准确、高效地进行。SOME/IP定义了一种特定的序列化格式,包括对整数、浮点数、字符串等不同数据类型的编码规则。这有助于确保不同系统之间的数据交换的一致性和互操作性。总的来说,SOME/IP序列化是指将通信数据按照协议规范进行编码,以便通过网络进行可靠的传输。这有助于实现分布式系统中各个模块之间的通信和协作)

2,首字母缩略词

         Byte Order Mark,字节顺序标记(BOM)是一个Unicode字符,即U+FEFF字节 订单标记(BOM),其外观为 文本流的开始用于指示所使用的编码。

        Method ,方法,方式。

        Parameters ,方法或事件的输入、输出或输入/输出参数。

        Remote Procedure Call (RPC),从一个ECU到另一个ECU的方法调用,使用
信息。
        Request,客户端向调用方法的服务器发送的消息。
        Response ,服务器向客户端发送的消息,用于传输方法调用
        
        Request/Response communication,由请求和响应组成的RPC
        Event,一种单向数据传输,只在发生变化或循环时调用,从数据生产者发送到消费者。
        Field,一个字段代表一种状态,因此在getter、setter和notifier作用的所有时间都有一个有效值。
        Notification Event,字段通知者的事件消息。
        Getter ,允许对字段进行读访问的一种请求/响应调用。
         Setter,允许对字段进行写访问的一种请求/响应调用。
         Notifier,当字段值发生变化时,发送带有新值的事件消息。
        Service,零个或多个方法、零个或多个事件以及零个或多个字段的逻辑组合。
        Service Interface ,服务的正式规范,包括其方法、事件和字段
         Eventgroup ,服务内字段的事件和通知事件的逻辑分组,以便允许订阅
        Service Instance ,服务的实现,该服务可以在车辆中存在多次,也可以在ECU中存在多次
         Server ,提供服务实例的ECU在该服务实例的上下文中应被称为服务器。
         Client ,在该服务实例的上下文中,使用服务器的服务实例的ECU应被称为客户端。
         Fire and Forget,没有响应消息的请求被称为触发并忘记。
        User Datagram Protocol ,使用简单无连接模型的标准网络协议。
         Union,动态采用不同数据类型的数据结构
        non-extensible (standard) struct ,无标记序列化的结构。最多,新成员可以以兼容的方式添加到结构的末尾,而可选成员是不可能的。
         extensible struct ,用标记序列化的结构。可以在任意位置以兼容的方式添加新成员,并且可选成员是可能的。

3、协议规范

SOME/IP提供基于服务的通信,通过网络进行。它基于列出服务提供的功能的服务定义。一个服务可以由零个或多个事件、方法和字段的组合构成。

- **Events(事件)** 提供周期性或从提供者到订阅者的变化发送的数据。

- **Methods(方法)** 为订阅者提供了发出远程过程调用的可能性,这些调用在提供者端执行。

- **Fields(字段)** 是以下三种之一或多个的组合:

          - 通知器(notifier),在提供者到订阅者的变化时发送数据。

          - 获取器(getter),由订阅者显式查询提供者的值时调用。

          - 设置器(setter),当订阅者希望更改提供者端的值时调用。

字段的通知器和事件的主要区别在于,事件仅在变化时发送,字段的通知器在订阅后直接发送数据。

4 SOM/IP消息格式规范

串行化描述了数据在协议数据单元(PDU)中表示为UDP或TCP消息的有效载荷的方式,通过基于IP的车载网络传输。

SOME/IP协议组成包括

•消息ID(服务ID/方法ID)[32位]

•长度[32位]

•请求ID(客户端ID/会话ID)[32位]

•协议版本[8位]

•接口版本[8位]

•消息类型[8位]

•返回代码[8位]

•负载

在应用E2E通信保护的情况下,根据为E2E报头选择的偏移值,将E2E报头放置在返回码之后。默认偏移值为64位,这使E2E标头正好位于返回代码和有效负载之间。

注意:End-to-End (E2E)通信保护是一种安全机制,旨在确保通信的完整性和机密性,从通信的发送端一直到接收端。该机制旨在防止在通信路径中的中间节点或攻击者对通信进行篡改或窃听。

4.1Message ID消息ID[32位]

消息ID应为32位标识符,用于识别

•对应用程序方法的RPC调用

•或识别事件。

注意:消息ID的分配由用户/系统设计者决定。但是,假定消息ID于整个系统(即车辆)是唯一的。

Method ID方法ID16位
消息ID头字段应结构化为16位服务ID头字段(用于区分多达2^16个服务)和16位方法ID头字段(用于区分多达2^16个服务元素,即方法和/或事件)。相当于前面是具体服务类型,后半截为这一类哪一个服务

将方法ID的ID空间在方法和事件/通知之间拆分是常见的做法,建议在0x0000-0x7FFF范围内放置方法(方法ID的第一个位是0),而在0x8000-0x8FFF范围内放置事件/通知(方法ID的第一个位是1)。

4.2Length长度32位

长度字段应包含以字节为单位的长度,从请求ID/客户端ID开始,直到SOME/IP消息结束。

 4.3Request ID客户端ID32位

允许服务器和客户端区分对同一方法、获取器或设置器的多个并行使用。 

  • Request ID应对于请求-响应对是唯一的,以区分同一方法的多次调用。
  • 在生成响应消息时,提供者应将Request ID从请求复制到响应消息。
  • 这允许客户端将响应映射到已发出的请求,即使有多个未完成的请求。
  • 在响应到达或不再期望到达(超时)之前,不得重新使用Request ID。

这意味着ECU的实现者可以根据他的实现要求定义Client-ID,而提供者无需知道此布局或定义,因为他只是将完整的Request-ID复制到响应中。

Client ID是ECU内调用客户端的唯一标识符。Client ID允许ECU区分来自同一方法的多个客户端的调用。

Session ID是一个唯一标识符,允许区分来自同一发送者的连续消息或请求。

Client ID还应支持通过具有可配置前缀或固定值(例如,Client ID的最高有效字节为诊断地址或给定应用程序/SW-C的配置的Client ID)在整个车辆中是唯一的,如图。

如果Session Handling未激活,则Session ID应设置为0x00

如果Session Handling激活,则Session ID应设置为在范围[0x1,0xFFFF]内的值。

如果Session Handling激活,则Session ID应根据相应的用例递增。

请求/响应方法应使用带有Session ID的会话处理。每次调用后,Session ID应递增。

当Session ID达到0xFFFF时,应循环回到0x01。

对于请求/响应方法,如果响应的Session ID与请求的Session ID不匹配,客户端必须忽略响应。

对于通知消息,如果Session Handling未激活,接收者应忽略Session ID。

对于通知消息,如果Session Handling激活,接收者应根据相应的用例处理Session ID(有关专用用例的详细信息包含在单独的规范项中。

4.4ProtocolVersion协议版本8位
协议版本标识使用的SOME/IP报头格式(不包括有效载荷格式)协议版本应为1

SOME/IP头中的所有不兼容更改,Protocol Version应递增。如果基于较旧Protocol Version的接收方不会丢弃消息并且会错误处理它,那么更改是不兼容的。

Protocol Version本身是SOME/IP Header的一部分,因此不应更改协议版本在头中的位置。

Protocol Version不应因仅影响Payload格式的更改而增加。

4.5InterfaceVersion接口版本[8位]

接口版本应为8位字段,包含服务接口的主要版本。

4.6MessageType消息类型[8位]
息类型字段用于区分不同类型的消息

1. 请求和响应处理:

    - 普通请求(消息类型0x00)应当在没有错误发生时由响应消息(消息类型0x80)回答。如果发生错误,应发送错误消息(消息类型0x81)。

    - 也可以发送没有响应消息的请求(消息类型0x01)。

为了通过通知更新值,存在回调接口(消息类型0x02)。(相当于告诉请求者我完成了你的请求,并且通知你哪些东西改了)

2. 分段消息标志:

    - 消息类型字段的第三高位(=0x20)称为TP-Flag,应设置为1,以表示当前的SOME/IP消息是一个分段消息。消息类型的其他位按照本节的规定设置。

3. 返回码:

    - 返回码用于表示请求是否成功处理。为了简化头部布局,每个消息都携带返回码字段。

 4.7ReturnCode返回码8位

返回代码应用于指示请求是否已成功处理。为了简化标题布局,每条消息都传输返回代码字段。

最后两个类型在表中,error不应0x00

4.8Payload负载

SOME/IP负载字段的大小取决于所使用的传输协议。对于UDP,SOME/IP负载应在0到1400字节之间。将限制为1400字节是为了允许将来对协议栈进行更改(例如切换到IPv6或添加安全手段)。由于TCP支持负载的分段,较大的大小将自动得到支持。

负载可能包含用于事件的数据元素或用于方法的参数。

4.9字节序

- 所有SOME/IP头字段必须以网络字节序(大端序)进行编码。

- 负载内部参数的字节顺序由配置定义。

  1. 大端序(Big-Endian): 数据的高位字节存储在低地址,低位字节存储在高地址。人类阅读顺序也是从左到右,所以也称为"大头在前"。例如,0x12345678在大端序中的存储顺序为:0x12 0x34 0x56 0x78。

  2. 小端序(Little-Endian): 数据的低位字节存储在低地址,高位字节存储在高地址。这种方式和人们阅读数字的顺序一致,从右到左,所以也称为"小头在前"。例如,0x12345678在小端序中的存储顺序为:0x78 0x56 0x34 0x12。

4.10数据结构的序列化

序列化基于接口规范定义的参数列表。接口规范定义了PDU中所有数据结构的确切位置,并且必须考虑内存对齐。

对齐用于通过在数据后插入填充元素来对齐数据的开头,以确保对齐的数据从特定的内存地址开始。有一些处理器架构可以在它们从某个数字的倍数(例如32位的倍数)开始的地址上更有效地访问数据。

- 数据的对齐应通过在序列化数据流中的变量大小数据的后面插入填充元素来实现,如果变量大小数据不是序列化数据流中的最后一个元素。

对齐

- 对齐始终应从SOME/IP消息的开始计算。

 数据元素填充

- 不得在固定长度数据元素的后面进行填充,以确保对以下数据的对齐。

变长数据元素后的对齐

- 变长数据元素后数据的对齐应为8、16、32、64、128或256位。

4.11基本数据类型

支持如下基本数据类型:

布尔值解释

- 对于布尔值的评估仅解释uint8的最低位,其余的位将被忽略。

4.12结构化数据类型(structs)

- 结构体的序列化应接近内存布局。这意味着参数应按顺序序列化到缓冲区中。特别是对于结构体,正确的内存对齐非常重要。

自动插入虚拟/填充数据

-         SOME/IP实现不得自动插入虚拟/填充数据。

结构体前的可选长度字段

        - 可选长度字段(8、16或32位)可能根据配置而插入到结构体前面。

 结构体的长度字段

        - 结构体的长度字段应描述此结构体在SOME/IP传输中占用的字节数。

处理结构体长度大于定义长度的情况

        - 如果长度大于数据类型定义中结构体的长度,只有在数据类型中指定的字节将被解释,而其他字节将基于长度字段被跳过。

处理结构体长度小于所有结构体成员长度之和的情况

        - 如果长度小于所有结构体成员长度之和,且接收方无法提供缺失数据的替代方案,则反序列化应中止,并将消息视为格式错误。

结构体的序列化应遵循结构化数据类型的深度优先遍历。

4.13 结构化数据类型和带标识符和可选成员的参数

为了实现更强大的向前和向后兼容性,可以在结构体成员或方法参数前添加额外的数据标识符(Data ID)。接收方可以跳过未知的成员/参数,即Data ID未知的成员/参数。在序列化字节流中传输Data ID时,可以在任意位置添加新成员/参数。

此外,Data ID的使用允许描述带有可选成员/参数的结构体和方法。成员/参数是否可选是在数据定义中定义的。在运行时,必须确定可选成员/参数是否实际存在于结构体/方法中。如何实现这一点取决于所使用的编程语言或软件平台(例如,使用特殊的可用标志,使用特殊的方法,使用可能为null的指针等)。

数据标识符的唯一性

        - 数据标识符在结构体的直接成员或方法的参数中应是唯一的。

        **注意:** 数据标识符不需要在不同的结构体或方法之间是唯一的。

        **注意:** 在当前情况下,AUTOSAR方法论、AUTOSAR CP RTE以及AUTOSAR AP ara::com都不支持可选方法参数的定义或使用。

数据标识符的定义

        - 数据标识符应在结构体的同一层次级别的所有成员或方法的参数中定义,或者在它们之中不定义。

数据标识符的定义

        - 数据标识符应在方法的所有参数或不在它们之中定义。

除了Data ID之外,Wire Type还编码以下成员的数据类型。Data ID和Wire Type被编码在所谓的标记(tag)中。

 标记的长度

        - 标记的长度应为两个字节。

标记的组成

        - 标记应包括

                  - 保留(第一个字节的第7位)

                  - Wire Type(第一个字节的第6-4位)

                  - Data ID(第一个字节的第3-0位和第二个字节的第7-0位)

请参阅图4.6以获取标记的布局。第7位是字节的最高有效位,第0位是字节的最低有效位。

 数据成员的Data ID的编码

        - 成员的Data ID的较低有效部分应编码在标记的第二个字节的第7-0位中。成员的Data ID的较高有效部分应编码在标记的第一个字节的第3-0位中。

**示例:**

如果成员的Data ID为0x04F2,则标记的第一个字节的第3-0位将设置为0x4。第二个字节将设置为0xF2。

 Wire Type的决定

        - Wire Type应确定成员的后续数据的类型。该值应按表4.7所示进行分配。

        **注意:** 有关Wire Type的更多详细信息,请参考相应的表格。

Wire Type 的后续数据:**

          - `Wire Type`为4确保与当前方法的兼容性,其中长度字段的大小是静态配置的。这种方法的缺点是在接口演变过程中更改长度字段的大小总是不兼容的。因此,`Wire Types`为5、6和7允许在传输的字节流中编码所使用的长度字段的大小。如果静态配置的长度字段的大小不足以容纳数据结构的当前大小,序列化器可以使用这个。

          - 如果`Wire Type`设置为5、6或7,则应忽略数据定义中定义的长度字段的大小,并根据`Wire Type`选择长度字段的大小。

- **标签:**

          - 如果为数据结构的成员或方法的参数配置了`Data ID`,则应在序列化的字节流中插入一个标签。

          - 如果序列化的成员/参数的数据类型是基本数据类型(`Wire Types` 0-3),并且配置了`Data ID`,则应将标签直接插入成员/参数之前。不应在序列化流中插入长度字段。

          - 如果序列化的成员/参数的数据类型不是基本数据类型(`Wire Types` 4-7),并且配置了`Data ID`,则应将标签插入长度字段之前。

          - 如果序列化的成员/参数的数据类型不是基本数据类型,并且配置了`Data ID`,则始终应在成员/参数之前插入长度字段。这是因为在反序列化期间,需要长度字段来跳过未知的成员/参数。

          - 长度字段应始终包含结构的下一个标签的长度。

          - 对于结构的成员或方法的参数,不同的数据类型需要插入不同数量的长度字段,这些字段的插入方式取决于其数据类型的要求。

          - 如果使用`TLV`(Type-Length-Value)序列化,数组、结构、联合和字符串的长度字段的大小应大于0。

          - 为数组、结构、联合和字符串的长度字段配置的大小应该相同。

          - 应该为顶级结构或方法的请求/响应配置长度字段的大小。所有在结构内使用的数组、联合、结构和字符串,或者在方法内的所有参数,都应该继承自顶级定义的长度字段的大小。

          - 不应该允许在子级数组、联合、结构或字符串中覆盖长度字段的大小。

          - 序列化器在序列化字节流时,如果标记为不可用的可选成员/参数,则不应包含它们。

          - 如果反序列化器读取一个未知的`Data ID`(即未包含在其数据定义中的`Data ID`),则它应该使用`Wire Type`和长度字段的信息跳过未知的成员/参数。

          - 如果反序列化器在序列化的字节流中找不到其数据定义中定义的所需(即非可选)的成员/参数,则反序列化应该中止,消息应该被视为格式不正确。

          - 如果在未使用标签的情况下

为现有服务接口引入标签序列化,则应增加并使用主要接口版本来指示这一点。接收方仅处理符合消息ID、协议版本、接口版本和消息类型的所有配置值的接收到的消息

这些规则涵盖了SOME/IP通信中数据结构的序列化和反序列化的许多方面。

- **注意:**

  - 在示例的Figure 4.7、Figure 4.8和Figure 4.9中,顶级结构具有长度字段,因为假定长度字段的大小是静态配置的。根据[PRS_SOMEIP_00243],为顶级结构配置了长度字段的大小,并将其传递给子元素。因此,由于[PRS_SOMEIP_00925]和[PRS_SOMEIP_00926]的影响,顶级结构本身具有长度字段。

  - 当长度字段的大小没有静态配置(即使用`Wire Types` 5-7)时,顶级结构将不具有长度字段。

### 注释

- **注意:**

  - 在示例Figure 4.10中,SOME/IP头部结束和第一个标签之间没有额外的长度字段。这将与SOME/IP头部中的消息长度字段重复

- **字符串 (fixed length):**

  - 要求字符串以"\0"字符终止,尽管具有固定长度。

  - 字符串的长度(包括"\0")以字节为单位必须在数据类型定义中指定。

  - 如果具有固定长度的字符串的长度大于预期值(期望应基于数据类型定义),则反序列化将被中止,并将消息视为格式错误。

  - 如果具有固定长度的字符串的长度小于预期值(期望应基于数据类型定义)并且使用"\0"正确终止,则将其接受。

  - 如果具有固定长度的字符串的长度小于预期值(期望应基于数据类型定义)并且未使用"\0"正确终止,则将中止反序列化,并将消息视为格式错误。

  

- **字符串 (dynamic length):**

  - 具有动态长度的字符串应以长度字段开头。长度以字节为单位测量。

  - 长度字段放置在BOM之前,并且BOM包含在长度中。

  - 字符串以"\0"终止。
### 注释:

- **注意:**

  - 字符串的最大字节数(包括以"\0"终止)也应从数据类型定义中导出。

  - 对于具有动态长度的字符串,[PRS_SOMEIP_00084]、[PRS_SOMEIP_00085]和[PRS_SOMEIP_00086]也应有效。

  - 具有动态长度的字符串应具有8、16或32位的长度字段。这将通过配置确定。

  - 如果未配置,则在字符串前面添加的长度字段的长度为32位(长度字段的默认长度)。

  - 字符串长度字段的长度不包含在长度字段值中;即长度字段不包括自身。

  - 如果具有可变长度的字符串的长度大于预期值(期望应基于数据类型定义),则反序列化将被中止,并将消息视为格式错误。

- **数组 (fixed length):**

  - 具有固定长度的数组在非常小的设备中更容易使用。使用它们的ECU上可能需要更多资源的动态长度数组。

  - 具有固定长度的数组可能以可选的长度字段开头。

  - 如果具有固定长度的数组的长度大于预期值(期望应基于数据类型定义),则只解释数据类型中指定的元素,并根据长度字段跳过其他字节。

  - 如果具有固定长度的数组的长度小于预期值(期望应基于数据类型定义),并且接收方在本地无法提供缺失数据的替代,则将中止反序列化,并将消息视为格式错误。

  - 注意:固定大小数组的溢出只能通过长度字段检测。

- **一维数组:**

  - 具有固定长度 "n" 的一维数组应携带完全相同类型的 "n" 个元素。可选的长度字段可以位于第一个元素之前(参见[PRS_SOMEIP_00944])。

  - 注意:如果为特定的固定长度数组定义了长度字段,则该数组将在总线上表示为长度字段和相同数据类型的 n 个元素的集合。

  - [PRS_SOMEIP_00099]的布局如图4.11所示。

- **多维数组:**

  - 多维数组的序列化遵循C/C++编程语言中多维数组的内存布局(按行主序)。

  - 注意:如果为特定的多维固定长度数组定义了长度字段,则该数组将在总线上表示为长度字段和 n 个集合的复合体,每个集合包含一个长度字段和 m 个相同数据类型的元素。

  - [PRS_SOMEIP_00101]的布局如图4.12所示。

- **动态长度数组:**

  - 具有动态长度的数组的布局应基于具有固定长度的数组的布局。

  - 在数组开头使用的可选长度字段应用于指定数组的字节长度。

  - 长度字段的长度应为0、8、16或32位,这应由配置确定。

  - 长度不包括长度字段的大小。

  - 注意:如果将长度字段的长度设置为0位,则数组中的元素数量必须是固定的,因此它是一个具有固定长度的数组。

  - 动态数组的布局如图4.13和图4.14所示。

- **图4.13: 一维数组(动态长度)**

  - 在一维数组中,使用一个长度字段,它携带用于数组的字节数。

  - 可以通过除以元素的大小轻松计算静态长度元素的数量。

  - 对于动态长度元素,无法计算元素的数量,但必须按顺序解析元素。

- **图4.14: 动态长度的多维数组的结构**

### Comments:

- **[PRS_SOMEIP_00114]**: 多维数组的每个不同维度的子数组都应该有自己的长度字段。如果需要静态缓冲区大小分配,则数据类型定义应定义每个维度的最大长度。这样在反序列化时,可以跳过复杂的多维数组。

- **[PRS_SOMEIP_00919]**: 如果可变长度数组的长度大于预期值,则只解释数据类型定义中指定的元素,而其他字节将根据长度字段跳过。

- **[PRS_SOMEIP_00945]**: 如果未配置添加到动态长度数组前面的长度字段的长度,则默认长度字段长度为32位。

- **[PRS_SOMEIP_00705]**: SOME/IP不考虑枚举。枚举应作为无符号整数数据类型进行传输。

- **[PRS_SOMEIP_00300]**: 位字段应作为无符号整数数据类型uint8/uint16/uint32/uint64进行传输。数据类型定义将能够定义每个位的名称和值。

- **[PRS_SOMEIP_00118]**: 联合(也称为变体)是网络上定义数据的用例,其中有效负载可以是不同数据类型。联合应该用于在网络上传输具有替代数据类型的数据。

- **[PRS_SOMEIP_00119]**: 联合应包括一个长度字段、类型选择器和有效负载。长度字段 [32位],类型字段 [32位],数据包括填充 [sizeof(padding) = length - sizeof(data)]。

- **[PRS_SOMEIP_00126]**: 长度字段应定义数据和填充的字节数,不包括长度字段和类型字段的大小。

- **[PRS_SOMEIP_00121]**: 长度字段的长度应由配置定义,可以是32、16、8或0位。

- **[PRS_SOMEIP_00122]**: 长度字段为0位表示不会将长度字段写入PDU。

- **[PRS_SOMEIP_00123]**: 如果长度字段为0位,则联合中的所有类型都应具有相同的长度。

- **[PRS_SOMEIP_00129]**: 类型字段应指定数据的数据类型。

- **[PRS_SOMEIP_00127]**: 类型字段的长度应由配置定义,可以是32、16或8位。

- **[PRS_SOMEIP_00906]**: 类型字段的可能值应由配置为每个联合单独定义。

- **[PRS_SOMEIP_00907]**: 类型字段的值0应保留用于NULL类型。这表示一个空联合。

- **[PRS_SOMEIP_00908]**: 是否允许联合为NULL应由配置定义。

- **[PRS_SOMEIP_00130]**: 根据类型字段中的类型,有效负载将被序列化。

  以下是关于联合的示例,其中长度字段的长度为32位。该联合应支持uint8和uint16作为数据。两者都填充到32位边界(长度=4字节)。

### Comments:

- **[PRS_SOMEIP_00915]**: 如果联合的长度大于预期值,则只解释数据类型定义中指定的字节,其他字节将根据长度字段跳过。

- **[PRS_SOMEIP_00916]**: 如果联合的长度小于预期值,则根据内部数据类型的情况决定是否可以反序列化有效数据,或者反序列化将中止并将消息视为畸形。

- **[PRS_SOMEIP_00138]**: 如果服务器运行同一服务的不同实例,属于不同服务实例的消息应通过服务器端的传输协议端口映射到服务实例。

- **[PRS_SOMEIP_00535]**: 所有传输协议绑定都应支持在传输层PDU(即UDP数据包或TCP段)中传输多个SOME/IP消息。

- **[PRS_SOMEIP_00142]**: 接收的SOME/IP实现应能够接收由UDP或TCP传输的非对齐的SOME/IP消息。

- **[PRS_SOMEIP_00140]**: 根据SOME/IP长度字段,SOME/IP实现应该确定包中是否有附加的SOME/IP消息。这适用于UDP和TCP传输。

- **[PRS_SOMEIP_00141]**: 每个SOME/IP有效负载应有自己的SOME/IP头。

- **[PRS_SOMEIP_00940]**: 一个服务实例可以使用以下通信设置进行其所有方法、事件和通知的通信:

  - 高达一个TCP连接

  - 高达一个UDP单播连接

  - 高达一个UDP多播连接

- **[PRS_SOMEIP_00139]**: 通过在UDP数据包中传输SOME/IP消息来实现SOME/IP的UDP绑定。

- **[PRS_SOMEIP_00137]**: SOME/IP协议不应限制UDP分段的使用。

- **[PRS_SOMEIP_00943]**: 为了通过UDP单播通信的所有方法、事件和通知使用单个UDP单播连接,客户端和服务器应使用单个UDP单播连接。

- **[PRS_SOMEIP_00942]**: 为了通过UDP多播通信的每个服务实例,客户端和服务器可以使用单个UDP多播地址。

- **[PRS_SOMEIP_00706]**: 当TCP连接丢失时,未完成的请求应该被处理为超时。由于TCP处理可靠性,不需要额外的可靠性手段。

- **[PRS_SOMEIP_00707]**: 为了通过TCP通信的所有方法、事件和通知,客户端和服务器应使用单个TCP连接。

- **[PRS_SOMEIP_00708]**: 当要传输第一个方法调用或客户端尝试接收第一个通知时,客户端应打开TCP连接。客户端负责在失败时重新建立TCP连接。

- **[PRS_SOMEIP_00709]**: 当不再需要TCP连接时,客户端应关闭TCP连接。

- **[PRS_SOMEIP_00710]**: 当不再可用(停止或超时)的所有使用TCP连接的服务时,客户端应关闭TCP连接。

- **[PRS_SOMEIP_00711]**: 当停止所有服务时,服务器不应停止TCP连接,而应给客户端足够的时间来处理关闭TCP连接的控制数据。

- **[PRS_SOMEIP_00154]**: 为了允许测试工具识别通过TCP传输的SOME/IP消息的边界,可以在SOME/IP消息流中定期插入SOME/IP Magic Cookie消息。

- **[PRS_SOMEIP_00160]**: Magic Cookie消息的布局应包括以下字段:

  - 用于从客户端到服务器的通信:

    - 消息ID(服务ID/方法ID):0xFFFF 0000

    - 长度:0x0000 0008

    - 请求ID(客户端ID/会话ID):0xDEAD BEEF

    - 协议版本:0x01

    - 接口版本:0x01

    - 消息类型:0x01

    - 返回码:0x00

  - 用于从服务器到客户端的通信:

    - 消息ID(服务ID/方法ID):0xFFFF 8000

    - 长度:0x0000 0008

    - 请求ID(客户端ID/会话ID):0xDEAD BEEF

    - 协议版本:0x01

    - 接口版本:0x01

    - 消息类型:0x02

    - 返回码:0x00

- **[PRS_SOMEIP_00162]**: 相同服务的服务实例通过不同的实例ID标识。应支持多个服务实例位于不同的ECU上,以及一个或多个服务的多个服务实例位于一个单独的ECU上。

- **[PRS_SOMEIP_00163]**: 虽然不同服务的多个服务实例应该能够共享所使用的传输层协议的相同端口号,但同一台ECU上同一服务的多个服务实例应该为每个服务实例使用不同的端口。

- **[PRS_SOMEIP_00720]**: 使用SOME/IP-TP的SOME/IP消息应激活会话处理(会话ID必须对于原始消息是唯一的)。

- **[PRS_SOMEIP_00721]**: 所有SOME/IP-TP段应携带原始消息的会话ID;因此,它们都具有相同的会话ID。

- **[PRS_SOMEIP_00722]**: SOME/IP-TP段应将消息类型的TP标志设置为1。

- **[PRS_SOMEIP_00723]**: SOME/IP-TP段应在SOME/IP头之后(即在SOME/IP有效负载之前)具有TP头,其结构如下(从最高到最低的位):

  - 偏移 [28位]

  - 保留标志 [1位]

  - 保留标志 [1位]

  - 保留标志 [1位]

  - 更多段标志 [1位]

 **[PRS_SOMEIP_00931]**: SOME/IP-TP头应以网络字节顺序(大端序)编码。

- **[PRS_SOMEIP_00724]**: 偏移字段应传输uint32的高28位。低4位始终被解释为0。这意味着偏移字段只能传输16字节的倍数的偏移值。

- **[PRS_SOMEIP_00725]**: TP头的偏移字段应设置为原始消息中传输段的字节偏移量。

- **[PRS_SOMEIP_00726]**: 发送方应将保留标志设置为0,接收方应忽略(而不检查)这些标志。

- **[PRS_SOMEIP_00727]**: More Segments标志应对所有段设置为1,但对于最后一段,它应设置为0。

- **[PRS_SOMEIP_00728]**: SOME/IP长度字段应按照先前的规定使用。这意味着它涵盖SOME/IP头的前8个字节以及之后的所有字节。

- **[PRS_SOMEIP_00729]**: 段的长度必须反映基于偏移字段的下一个段的对齐。因此,除了最后一段之外,所有段的长度都应是16字节的倍数。

- **[PRS_SOMEIP_00730]**: 最大段长度:基于UDP的SOME/IP消息的有效载荷限制为1400字节。因此,正确对齐的段的最大长度为1392字节。

- **[PRS_SOMEIP_00731]**: SOME/IP-TP消息应使用与原始消息相同的Message ID(即Service ID和Method ID)、Request ID(即Client ID和Session ID)、Protocol Version、Interface Version和Return Code。正如上面描述的,Length、Message Type和Payload由SOME/IP-TP进行调整。

这个原始的SOME/IP消息现在将被分割成5个连续的SOME/IP段。这些段的每个有效负载在这个示例中最多携带1392字节。

对于这些段,SOME/IP TP模块添加了额外的TP字段(标记为红色)。

SOME/IP的Length字段携带了SOME/IP段的总长度,包括8字节的Request ID、Protocol Version、Interface Version、Message Type和Return Code。由于添加了TP字段(4字节),这个长度信息扩展了4个额外的SOME/IP TP字节。

以下图提供了每个SOME/IP段的相关SOME/IP头设置的概览:

请注意:

请注意,Offset字段中提供的值以16字节为单位给出,即:87对应于1392字节的有效载荷。

SOME/IP分段消息的完整SOME/IP头将详细如下:

- 前4个段每个包含1392字节的有效载荷,其中“More Segments Flag”设置为'1':

 最后一个段(即第5段)包含原始的5880字节有效负载中剩余的312字节。这个最后一个段的标志为“More Segments flag”设置为'0'。

Sender specific behavior

[PRS_SOMEIP_00732] - 发送方应该仅对配置为分段的消息进行分段。 (RS_SOMEIP_00010, RS_SOMEIP_00051)

[PRS_SOMEIP_00733] - 发送方应按升序发送段。 (RS_SOMEIP_00010, RS_SOMEIP_00051)

[PRS_SOMEIP_00734] - 发送方应该以所有"More Segment Flag"设置为1的段具有相同大小的方式进行分段。 (RS_SOMEIP_00010, RS_SOMEIP_00051)

[PRS_SOMEIP_00735] - 发送方应尽量使段的大小最大化,但受到本规范的限制。 (RS_SOMEIP_00010, RS_SOMEIP_00051)

[PRS_SOMEIP_00736] - 发送方不应发送重叠或重复的段。 (RS_SOMEIP_00010, RS_SOMEIP_00051)

Receiver specific behavior

[PRS_SOMEIP_00738] - 接收方应基于配置的Message-ID、Protocol-Version、Interface-Version和Message-Type(不包含TP Flag)的值匹配段以进行重组。 (RS_SOMEIP_00010, RS_SOMEIP_00051)

[PRS_SOMEIP_00740] - 应支持并行重新组装具有相同Message ID但来自不同客户端的多个消息(发送者IP、发送者端口或客户端ID不同)。 这应由配置控制,并确定"重新组装缓冲区"的数量。 (RS_SOMEIP_00010, RS_SOMEIP_00051)

[PRS_SOMEIP_00741] - 应使用Session ID来检测下一个要重新组装的原始消息。 (RS_SOMEIP_00010, RS_SOMEIP_00051)

[PRS_SOMEIP_00742] - 如果接收到具有不同Session-ID的新段,则接收方应开始新的重组(并可能丢弃未成功重组的旧段)。 (RS_SOMEIP_00010, RS_SOMEIP_00051)

[PRS_SOMEIP_00743] - 接收方应该仅重新组装到其配置的缓冲区大小,并跳过消息的其余部分。 (RS_SOMEIP_00010, RS_SOMEIP_00051)

[PRS_SOMEIP_00744] - 仅将完全重组且长度不超过配置大小的消息传递给应用程序。 (RS_SOMEIP_00010, RS_SOMEIP_00051)

[PRS_SOMEIP_00745] - 在重新组装SOME/IP TP段成大型未分段消息时,应使用最后一个段的Return Code作为重新组装的消息的Return Code。 (RS_SOMEIP_00010, RS_SOMEIP_00051)

[PRS_SOMEIP_00746] - 在重新组装SOME/IP TP段成大型未分段消息时,应调整Message Type,将TP Flag重置为0。 (RS_SOMEIP_00010, RS_SOMEIP_00051)

[PRS_SOMEIP_00747] - 接收方应支持按升序和降序接收的段进行重新组装。 (RS_SOMEIP_00010, RS_SOMEIP_00051)

[PRS_SOMEIP_00749] - 在组装SOME/IP消息时检测到缺失段时,应取消当前的组装过程。 (RS_SOMEIP_00010, RS_SOMEIP_00051)

[PRS_SOMEIP_00750] - 不支持使用相同缓冲区(例如,仅Session-ID和有效负载不同)交错不同分段消息。 (RS_SOMEIP_00010, RS_SOMEIP_00051)

[PRS_SOMEIP_00751] - 不关注完全不同的原始消息(例如,消息ID不同)的段的重新排序,因为这些段进入不同的缓冲区。 (RS_SOMEIP_00010, RS_SOMEIP_00051)

[PRS_SOMEIP_00752] - 接收方应正确重新组装重叠和重复的段,基于最后接收的段进行覆盖。 (RS_SOMEIP_00010, RS_SOMEIP_00051)

[PRS_SOMEIP_00753] - 如果重叠或重复的段更改了缓冲区中已写入的字节,则接收方可能会取消重新组装。 如果可以通过配置关闭此功能,则应该关闭。 (RS_SOMEIP_00010, RS_SOMEIP_00051)

[PRS_SOMEIP_00754] - 接收方应能够优雅地检测和处理明显的错误。 例如,如果带有MS=1的段的段长度不是16的倍数,则应取消重新组装。 (RS_SOMEIP_00010, RS_SOMEIP_00051)

4.14 请求/响应通信

最常见的通信模式之一是请求/响应模式。一个通信伙伴(客户端)发送一个请求消息,另一个通信伙伴(服务器)回答。

 - 对于SOME/IP请求消息,客户端必须对有效负载和标头执行以下操作:

          - 构造有效负载

          - 根据客户端要调用的方法设置Message ID

          - 将Length字段设置为8字节(用于SOME/IP标头中长度字段之后的部分)+序列化有效负载的长度

          - 可选择将Request ID设置为唯一编号(仅对客户端唯一)

          - 设置Protocol Version

          - 根据接口定义设置Interface Version

          - 将Message Type设置为REQUEST(即0x00)

          - 将Return Code设置为0x00

- 要构造请求消息的有效负载,必须根据方法签名中参数的顺序对所有输入或inout参数进行序列化。 

 - 服务器根据客户端请求的标头构建响应的标头,并进行以下操作:

          - 构造有效负载

          - 从相应请求中获取Message ID

          - 将长度设置为8字节+新有效负载大小

          - 从相应请求中获取Request ID

          - 将Message Type设置为RESPONSE(即0x80)或ERROR(即0x81)

          -设置Return Code

对于有效的返回代码,请参见表4.11。

 - 要构造响应消息的有效负载,必须根据方法签名中参数的顺序对所有输出或inout参数进行序列化。 

- 服务器不应为尚未接收到相应请求消息的特定Request ID的请求发送响应消息。

- 当相应请求消息尚未完全发送时,客户端应忽略特定Request ID的响应消息的接收。 

4.15火炬通信

没有响应消息的请求称为火炬通信。

- 对于SOME/IP请求-无返回消息,客户端必须对有效负载和标头执行以下操作:

          - 构造有效负载

          - 根据客户端要调用的方法设置Message ID

          - 将Length字段设置为8字节(用于SOME/IP标头中长度字段之后的部分)+序列化有效负载的长度

          - 可选择将Request ID设置为唯一编号(仅对客户端唯一)

          - 根据[PRS_SOMEIP_00052]设置Protocol Version

          - 根据接口定义设置Interface Version

          - 将Message Type设置为REQUEST_NO_RETURN(即0x01)

          - 将Return Code设置为0x00

- 火炬消息不应返回错误。 当需要时,应用程序应实现错误处理和返回代码。 

4.16 通知事件

通知描述了一般的发布/订阅概念。通常,服务器发布一个客户端订阅的服务。在某些情况下,服务器将向客户端发送一个事件,例如更新的值或发生的事件。SOME/IP 仅用于传输更新的值,而不用于发布和订阅机制。这些机制由 SOME/IP-SD 实现。

- 对于 SOME/IP 通知消息,服务器必须对有效负载和标头执行以下操作:

          - 构造有效负载

          - 根据服务器想要发送的事件设置 Message ID

          - 将 Length 字段设置为 8 字节(用于 SOME/IP 标头中长度字段之后的部分)+序列化有效负载的长度

          - 将 Client ID 设置为 0x00。设置 Session ID。在进行主动会话处理的情况下,每次传输时应递增 Session ID。

          - 设置 Protocol Version

          - 根据接口定义设置 Interface Version

          - 将 Message Type 设置为 NOTIFICATION(即0x02)

          - 将 Return Code 设置为 0x00

- 通知消息的有效负载应包含事件的序列化数据。 

- 当同一 ECU 上存在多个订阅的客户端时,系统应处理通知的复制,以节省在通信介质上的传输。 这在使用多播消息传输通知时尤其重要。

4.17 发送通知的策略

对于不同的用例,可以使用不同的发送通知的策略。以下示例是常见的:

        - 周期性更新:在固定间隔内发送更新的值(例如,每 100 毫秒用于具有存活标志的安全相关消息)

        - 改变时更新:在“值”更改时立即发送更新(例如,门打开)

        - Epsilon 改变:仅在与上一个值的差异大于某个特定的 epsilon 时才发送更新。这个概念可能是自适应的,即预测基于历史;因此,仅当预测值与当前值之间的差异大于 epsilon 时才传输更新。

4.18 字段

字段表示一种状态并具有有效值。在订阅字段后立即获得字段值作为初始事件的使用者。

- 字段应该是 getter、setter 和通知事件的组合。

- 不带有 setter、getter 和 notifier 的字段不应存在。字段应至少包含 getter、setter 或 notifier 中的一个。

- 字段的 getter 应该是一个请求/响应调用,其请求消息中的有效负载为空,并且响应消息的有效负载中包含字段的值。 

- 字段的 setter 应该是一个请求/响应调用,其请求消息的有效负载中包含字段的所需值,并且响应消息的有效负载中包含字段的设置值。 如果请求有效负载的值已调整(例如,因为超出了限制),则调整后的值将在响应有效负载中传输。 

- 当客户端订阅字段时,通知器应该发送一个传输字段值的事件消息。

- 通知器应该发送一个在更改时传输字段值的事件消息,并遵循事件的规则。 

4.19 错误处理

错误处理可以在应用程序或下面的通信层中完成。因此,SOME/IP 支持两种不同的机制:

        - 方法的响应消息中的返回代码

        - 明确的错误消息

使用其中之一取决于配置。

        - 应使用方法的响应消息中的返回代码来传输应用程序错误以及方法提供程序对方法的调用方的响应数据。

         - 应使用明确的错误消息来传输应用程序错误以及方法提供程序对方法的调用方的响应数据或一般的 SOME/IP 错误

 4.20 错误消息

为了实现更灵活的错误处理,SOME/IP 允许使用特定于错误消息的不同消息布局,而不是使用 Response Messages 的消息布局。异常消息的推荐布局如下:

        - 特定异常的 Union。至少需要存在一个没有字段的通用异常。

        - 用于异常描述的动态长度字符串。

原理:联合提供了以类型安全的方式将新异常添加到将来的灵活性。字符串用于传输人类可读的异常描述,以便于测试和调试。

4.21 错误处理概述

- 返回代码的生成和处理应该是可配置的。

- 错误处理应该基于接收到的消息类型(例如,只有方法可以用返回代码回答),并且应该按照 [PRS_SOMEIP_00195] 中定义的顺序进行检查。

- 对于通过 UDP 接收的 SOME/IP 消息,应进行以下检查:

- UDP 数据报的大小应至少为 16 字节(SOME/IP 消息的最小大小)。

- 长度字段的值应小于或等于 UDP 数据报负载中的剩余字节。

如果其中一个检查失败,应发出格式错误。

- SOME/IP 消息应通过错误处理进行检查。这不包括基于应用程序的错误处理,仅涉及消息传递和 RPC 的错误处理。

Figure 4.21 展示了错误处理的概述。

4.22通信错误和处理通信错误

在考虑传输 RPC 消息时,存在不同的可靠性语义:

        - Maybe(可能性):消息可能会到达通信伙伴

        - At least once(至少一次):消息至少一次到达通信伙伴

        - Exactly once(仅一次):消息仅一次到达通信伙伴

在使用上述术语时,就请求/响应而言,该术语适用于两种消息(即请求和响应或错误)。

虽然不同的实现可能采用不同的方法,但目前 SOME/IP 在使用 UDP 绑定时实现了"可能性"可靠性,在使用 TCP 绑定时实现了"仅一次"可靠性。更进一步的错误处理留给了应用程序。

对于"可能性"可靠性,仅在使用 UDP 作为传输协议的请求/响应通信结合使用时需要一个超时。图 4.22 显示了"可能性"可靠性的状态机。客户端的 SOME/IP 实现必须等待指定超时时间的响应。如果超时发生,SOME/IP 将向客户端应用程序发出 E_TIMEOUT 信号。

4.23 接口版本的兼容性规则

接口版本标识了有效载荷格式。有效载荷格式受以下因素的影响:

        - 服务接口规范

        - 序列化配置(例如,使用可变大小数组、长度字段的大小、填充、TLV、SOME/IP-TP)

接口版本应出于以下任何原因而增加:

        - 有效载荷格式的不兼容更改

        - 服务行为的不兼容更改

        - 应用程序设计的要求

接口版本不应因有效载荷格式的兼容更改而增加。

表 4.12 中的规则应定义有效载荷格式更改的兼容性。对于复杂数据类型,应递归应用规则。X 表示兼容更改,空单元格表示不兼容更改。

注意:此表基于 SOME/IP 协议的规范。作为经验法则,如果数据的接收方在预期的位置上找到所有预期的信息,则接口是兼容的。

5 Configuration Parameters

Configuration Parameters are not handled and described in this document.

6 协议使用和指南

6.1 选择传输协议

SOME/IP 支持用户数据报协议(UDP)和传输控制协议(TCP)。虽然 UDP 是一种非常精简的传输协议,仅支持最重要的功能(多路复用和使用校验和进行错误检测),但 TCP 添加了额外的功能以实现可靠的通信。TCP 不仅处理比特错误,还处理分段、丢失、复制、重新排序和网络拥塞。

在车辆内部,许多应用需要非常短的超时时间以快速做出反应。使用 UDP 可以更好地满足这些要求,因为应用程序本身可以处理错误的不太可能事件。例如,在周期数据的用例中,通常最好的方法是等待下一次数据传输,而不是尝试修复最后一次传输。UDP 的主要缺点是它不处理分段,因此只能传输较小的数据块。

指南:

- 仅在需要传输非常大的数据块(> 1400 字节)且在错误情况下没有硬实时性要求时使用 TCP

- 在需要在错误情况下具有非常严格的实时性要求(<100ms)时使用 UDP

- 在需要传输非常大的数据块(> 1400 字节)且在错误情况下存在硬实时性要求时,与 SOME/IP-TP 一起使用 UDP

- 在更适合用例时,尝试使用外部传输或传输机制(网络文件系统、APIX 链接、1722 等)。在这种情况下,SOME/IP 可以传输文件句柄或类似的标识符。这为设计人员提供了额外的自由度(例如,关于缓存)。

所使用的传输协议由每条消息的接口规范指定。方法、事件和字段通常只应使用单个传输协议。

6.2 传输 CAN 和 FlexRay 帧

SOME/IP 应允许封装 CAN 和 FlexRay 帧。但是,消息 ID 空间需要在两种用例之间协调。完整的 SOME/IP 头应用于传输/封装 CAN/FlexRay。

6.3 为结构体插入填充

如果需要填充,接口的设计者应根据需要插入保留/填充元素在数据类型的定义中,因为 SOME/IP 实现不应自动添加此类填充。

6.4 SOME/IP 的安全考虑

- **限制服务器从客户端的连接**:服务器可以实施通信策略以保护服务器免受恶意或未经授权的客户端。例如,服务器可以拒绝对事件组的订阅,或来自未经授权的客户端的方法调用。这些策略超出了本规范的范围。此类策略可以基于客户端的 IP 地址或任何其他用于标识客户端的手段。

- **限制客户端连接到服务器**:客户端可以实施通信策略以保护客户端免受恶意服务器的侵害。例如,客户端可以拒绝与未经授权的服务器进行通信。这些策略超出了本规范的范围。此类策略可以基于服务器的 IP 地址或任何其他用于标识服务器的手段。

  • 1
    点赞
  • 34
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值