vsomeip的e2e研究
E2E 介绍
AUTOSAR(AUTomotive Open System ARchitecture)是一个开放和标准化的软件架构,它用于汽车行业中的电子控制单元(ECU)开发。为了确保数据通信的可靠性和安全性,AUTOSAR引入了End-to-End(E2E)通信保护机制。E2E保护的目的是在数据传输过程中检测和应对可能出现的错误,如数据损坏、丢失或重放攻击。
E2E保护的基本概念
E2E保护通过在数据报文中添加校验信息(如CRC校验、序列号等)来检测传输中的错误。它的核心理念是即使数据在传输链的某个环节出现了错误,接收端也能通过这些校验信息检测到,并采取相应的措施。
VSOMEIP 中的E2E
在VSOMEIP的v3.1.20.3版本中,只支持三种E2E:p01、p04和custom。在VSOMEIP中如果要启用E2E则需要在VSOMEIP的配置文件中进行配置。E2E的配置分为protector
和checker
以及both
,protector
是对数据进行保护,checker
是对数据进行检测,both
是同时支持两种。下面给出P04的E2E配置:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// protect
"e2e" :
{
"e2e_enabled" : "true",
"protected" :
[
{
"service_id" : "0xd025",
"event_id" : "0x0001",
"profile" : "P04",
"variant" : "protector",
"crc_offset" : "64",
"data_id" : "0x2d"
}
]
}
// check
"e2e" :
{
"e2e_enabled" : "true",
"protected" :
[
{
"service_id" : "0xd025",
"event_id" : "0x0001",
"profile" : "P04",
"variant" : "checker",
"crc_offset" : "64",
"data_id" : "0x2d"
}
]
}
VSOMEIP在e2e_enabled为true的情况下,会加载e2e模块。代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 在routing_manager_impl::init函数中
if( configuration_->is_e2e_enabled()) { // 判断e2e是否开启,即配置文件中的e2e_enabled
VSOMEIP_INFO << "E2E protection enabled.";
const char *its_e2e_module = getenv(VSOMEIP_ENV_E2E_PROTECTION_MODULE);
std::string plugin_name = its_e2e_module != nullptr ? its_e2e_module : VSOMEIP_E2E_LIBRARY;
// 加载e2e模块
auto its_plugin = plugin_manager::get()->get_plugin(plugin_type_e::APPLICATION_PLUGIN, plugin_name);
if (its_plugin) {
VSOMEIP_INFO << "E2E module loaded.";
e2e_provider_ = std::dynamic_pointer_cast<e2e::e2e_provider>(its_plugin);
}
}
发送数据的数据,对数据进行E2E保护的代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// bool routing_manager_impl::send(client_t _client, const byte_t *_data,
// length_t _size, instance_t _instance, bool _reliable,
// client_t _bound_client,
// credentials_t _credentials,
// uint8_t _status_check, bool _sent_from_remote)
// ......
if (e2e_provider_) {
if ( !is_service_discovery) {
service_t its_service = VSOMEIP_BYTES_TO_WORD(
_data[VSOMEIP_SERVICE_POS_MIN], _data[VSOMEIP_SERVICE_POS_MAX]);
method_t its_method = VSOMEIP_BYTES_TO_WORD(
_data[VSOMEIP_METHOD_POS_MIN], _data[VSOMEIP_METHOD_POS_MAX]);
#ifndef ANDROID
// is_protected会判断该消息是否需要保护,判断的依据就是配置文件中有没有进行配置
if (e2e_provider_->is_protected({its_service, its_method})) {
// Find out where the protected area starts
// get_protection_base会得到someip的基础头,e2e会跳过此部分,e2e会对接下来的数据进行protect。
size_t its_base = e2e_provider_->get_protection_base({its_service, its_method});
// Build a corresponding buffer
// 跳过基础头
its_buffer.assign(_data + its_base, _data + _size);
// 对数据进行保护
e2e_provider_->protect({ its_service, its_method }, its_buffer, _instance);
// Prepend header
its_buffer.insert(its_buffer.begin(), _data, _data + its_base);
_data = its_buffer.data();
}
#endif
}
}
具体的protect代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
// 具体的是P04的protect
void
protector::protect(e2e_buffer &_buffer, instance_t _instance) {
std::lock_guard<std::mutex> lock(protect_mutex_);
if (_instance > VSOMEIP_E2E_PROFILE04_MAX_INSTANCE) {
VSOMEIP_ERROR << "E2E Profile 4 can only be used for instances [1-255]";
return;
}
/** @req: [SWS_E2E_00363] */
if (verify_inputs(_buffer)) {
/** @req [SWS_E2E_00364] */
// 写入16位的length
write_16(_buffer, static_cast<uint16_t>(_buffer.size()), 0);
/** @req [SWS_E2E_00365] */
// 写入16位的counter
write_16(_buffer, counter_, 2);
/** @req [SWS_E2E_00366] */
// 写入data_id
uint32_t its_data_id(uint32_t(_instance) << 24 | config_.data_id_);
write_32(_buffer, its_data_id, 4);
/** @req [SWS_E2E_00367] */
// 计算crc
uint32_t its_crc = profile_04::compute_crc(config_, _buffer);
/** @req [SWS_E2E_0368] */
// 写入crc
write_32(_buffer, its_crc, 8);
/** @req [SWS_E2E_00369] */
increment_counter();
}
}
可以看到会对12个字节进行覆盖,所以实际发送的时候需要预留12字节的空间。配置文件中的crc_offset的作用是写入P04E2E的偏移量,具体的代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// config_.offset_就是配置文件中crc_offest的值
void
protector::write_16(e2e_buffer &_buffer, uint16_t _data, size_t _index) {
_buffer[config_.offset_ + _index] = VSOMEIP_WORD_BYTE1(_data);
_buffer[config_.offset_ + _index + 1] = VSOMEIP_WORD_BYTE0(_data);
}
void
protector::write_32(e2e_buffer &_buffer, uint32_t _data, size_t _index) {
_buffer[config_.offset_ + _index] = VSOMEIP_LONG_BYTE3(_data);
_buffer[config_.offset_ + _index + 1] = VSOMEIP_LONG_BYTE2(_data);
_buffer[config_.offset_ + _index + 2] = VSOMEIP_LONG_BYTE1(_data);
_buffer[config_.offset_ + _index + 3] = VSOMEIP_LONG_BYTE0(_data);
}
收到数据会进行E2E的检测,代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// void routing_manager_impl::on_message(const byte_t *_data, length_t _size,
// endpoint *_receiver, const boost::asio::ip::address &_destination,
// client_t _bound_client, credentials_t _credentials,
// const boost::asio::ip::address &_remote_address,
// std::uint16_t _remote_port)
// .......
// is_checked会判断该消息是否需要保护,判断的依据就是配置文件中有没有进行配置
if (e2e_provider_->is_checked({its_service, its_method})) {
auto its_base = e2e_provider_->get_protection_base({its_service, its_method});
e2e_buffer its_buffer(_data + its_base, _data + _size);
e2e_provider_->check({its_service, its_method},
its_buffer, its_instance, its_check_status);
if (its_check_status != e2e::profile_interface::generic_check_status::E2E_OK) {
VSOMEIP_INFO << "E2E protection: CRC check failed for service: "
<< std::hex << its_service << " method: " << its_method;
}
}
然后是具体的check代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// 具体的是P04的check
if (verify_input(_buffer)) {
/** @req [SWS_E2E_357] */
uint16_t its_received_length;
if (read_16(_buffer, its_received_length, 0)) {
/** @req [SWS_E2E_358] */
uint16_t its_received_counter;
if (read_16(_buffer, its_received_counter, 2)) {
/** @req [SWS_E2E_359] */
uint32_t its_received_data_id;
if (read_32(_buffer, its_received_data_id, 4)) {
/** @req [SWS_E2E_360] */
uint32_t its_received_crc;
if (read_32(_buffer, its_received_crc, 8)) {
uint32_t its_crc = profile_04::compute_crc(config_, _buffer);
/** @req [SWS_E2E_361] */
if (its_received_crc != its_crc) {
_generic_check_status = e2e::profile_interface::generic_check_status::E2E_WRONG_CRC;
VSOMEIP_ERROR << std::hex << "E2E P04 protection: CRC32 does not match: calculated CRC: "
<< its_crc << " received CRC: " << its_received_crc;
} else {
uint32_t its_data_id(uint32_t(_instance) << 24 | config_.data_id_);
if (its_received_data_id == its_data_id
&& static_cast<size_t>(its_received_length) == _buffer.size()
&& verify_counter(its_received_counter)) {
_generic_check_status = e2e::profile_interface::generic_check_status::E2E_OK;
}
counter_ = its_received_counter;
}
}
}
}
}
}
依次获得length、counter、data_id以及crc,然后用提取的crc和这边计算的crc进行对比,如果一样,还需要验证data_id和couter_id。用户是通过调用message::is_valid_crc
来判断E2E是否检测成功的,vsomeip在上面的代码中进行了check后,需要将check结果返回给用户,具体代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
bool routing_manager_impl::deliver_message(const byte_t *_data, length_t _size,
instance_t _instance, bool _reliable, client_t _bound_client, credentials_t _credentials,
uint8_t _status_check, bool _is_from_remote) {
bool is_delivered(false);
std::uint32_t its_sender_uid = std::get<0>(_credentials);
std::uint32_t its_sender_gid = std::get<1>(_credentials);
auto its_deserializer = get_deserializer();
its_deserializer->set_data(_data, _size);
std::shared_ptr<message_impl> its_message(its_deserializer->deserialize_message());
its_deserializer->reset();
put_deserializer(its_deserializer);
if (its_message) {
its_message->set_instance(_instance);
its_message->set_reliable(_reliable);
its_message->set_check_result(_status_check);
its_message->set_uid(std::get<0>(_credentials));
its_message->set_gid(std::get<1>(_credentials));
// ......
在deliver_message函数中会设置检查的结果,然后用户就可以调用 message::is_valid_crc()
判断是否e2e检测成功。
e2e除了p04,还支持p01,p01的配置文件如下所示:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
"e2e_enabled" : "true",
"protected" :
[
{
"service_id" : "0x1234",
"event_id" : "0x8421",
"profile" : "CRC8",
"variant" : "checker",
"crc_offset" : "0",
"data_id_mode" : "3",
"data_length" : "56",
"data_id" : "0xA73"
}
]
可以看到p01多,data_id_mode,data_id_mode就是将data_id纳入crc计算的方式有以下几种: 这段描述定义了四种将两字节数据 ID 纳入一字节 CRC 计算的模式。具体如下:
- E2E_P01_DATAID_BOTH:
- 在这种模式下,两个字节(双字节 ID 配置)都被包含在 CRC 计算中,先低字节,再高字节(参见变体 1A - PRS_E2EProtocol_00227)。
- E2E_P01_DATAID_ALT:
- 根据计数器的奇偶性(交替 ID 配置),高字节或低字节会被包含在 CRC 计算中(参见变体 1B - PRS_E2EProtocol_00228)。对于偶数计数器值,低字节被包含;对于奇数计数器值,高字节被包含。
- E2E_P01_DATAID_LOW:
- 只有低字节被包含在 CRC 计算中,高字节从未使用。这相当于数据 ID(在给定应用中)只有 8 位的情况。
- E2E_P01_DATAID_NIBBLE:
- 数据 ID 的高字节的高 4 位(高半字节)不使用(它是 0x0),因为数据 ID 限制为 12 位。
- 数据 ID 的高字节的低 4 位(低半字节)会显式传输,并且在计算数据的 CRC 时被覆盖。
- 低字节不会被传输,但它作为初始值被包含在 CRC 计算中(隐式传输,就像 _BOTH、_ALT 和 _LOW 模式一样)。
crc_offest在p04中为开始插入e2e的位置,在p01中较为复杂,仅仅是crc的offset,p01中还存在 counter offset data_id nibble offset。这里没有在配置文件中给出,则默认值分别为8和12。
而custom的e2e的配置则是如下:
1
2
3
4
5
6
7
{
"service_id" : "0x1234",
"event_id" : "0x6543",
"profile" : "CRC32",
"variant" : "checker",
"crc_offset" : "0"
}
profile为CRC32,此时为custum profile,custum profile和没有data_id,counter_id等,只需要对数据进行crc32即可,存放的位置和crc_offest有关。