处理UDS报文-按字节分割和替换内容的多种实现

一、要解决的问题

测试汽车诊断系统的时候,很难通过实车覆盖所有的诊断场景,即使可以,时间代价也很高。

所以很多时候需要通过模拟的手段进行测试,那就要改造报文,长度较短的报文可以直接手动修改,对于一个字节数上千的报文,就需要借助工具了。

比如一个字节数为772的十六进制报文,我们要实现以下2个需求:

  1. 替换指定字节位的内容为相应的值(从第0个字节开始计数)
  2. 替换指定连续字节位的内容为相应的值(从第0个字节开始计数)

原始报文格式 和 期待输出的报文格式为:

FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF

二、代码实现

整体思路

首先,我们可以用一个字典存储我们要做的操作

如 loc_text = {550:‘00’,‘601 to 605’:‘0102030405’},标识我们要把第550个字节替换为00,第601个字节到第605个字节替换为0102030405

# 原始报文
text = ''
# 要操作的报文字节位置和替换进去的内容
loc_text = {550: '00', '601 to 605': '0102030405'}

for index, value in loc_text.items():
    if isinstance(index, str) and 'to' in index:
        try:
            start = int(index.split(' ')[0])
            end = int(index.split(' ')[2]) + 1
            # TODO:连续字节替换处理
        except ValueError:
            print(f'在 {test} 中to前后不是数字')
    elif isinstance(index, int):
        # 单个字节替换处理
        pass
    else:
        print(f"无法处理的索引值:{index}")

# 输出结果
print(text)

接下来,需要对原始报文进行替换,有2种思路:

  • 第一种思路,将需要操作的字节位置 * 2,然后通过对原始报文字符串进行切片和拼接实现;
  • 第二种思路,把原始报文按照字节分开,转为列表或字典或数组等,然后替换指定位置的内容。

第一种思路实现

  • 使用字符串的切片,注意其前闭后开的特性,以及一个字节占了2个字符
for index, value in loc_text.items():
    if isinstance(index,str) and 'to' in index:
        try:
            start = int(index.split(' ')[0])
            end = int(index.split(' ')[2])
            # 连续字节替换处理
            # 字节数 * 2 为在字符串中的起始位置
            text = text[: start * 2] + value + text[(end * 2) + 2 :]
        except ValueError:
            print(f'在 {test} 中to前后不是数字')
    else:
        # 单个字节替换处理
        text = text[:index * 2] + value + text[(index * 2) + 2 :]

第二种思路实现(3种方法)

1. 将原始报文转为字节数组

  • bytearray 是 Python 的一种可变字节数组类型。它是 bytes 类型的可变版本,可以进行修改操作
  • bytearray 对象可以通过索引和切片来访问和修改其中的值
  • bytearray.fromhex() 是 bytearray 类的一个方法,用于将十六进制字符串转换为对应的字节数组
# 将原始报文转为字节数组
text_to_array = bytearray.fromhex(text)

for index, value in loc_text.items():
    if isinstance(index,str) and 'to' in index:
        try:
            start = int(index.split(' ')[0])
            end = int(index.split(' ')[2])
            # 连续字节替换处理,需要替换进去的内容也要转为字节数组
            text_to_array[start:(end + 1)] = bytearray.fromhex(value)
        except ValueError:
            print(f'在 {test} 中to前后不是数字')
    elif isinstance(index, int):
        # 单个字节替换处理,需要将字符串转为十六进制的数字
        text_to_array[index] = int(value, 16)
    else:
        print(f"无法处理的索引值:{index}")
        
text = text_to_array.hex().upper()

2. 将原始报文转为列表

使用正则表达式的sub方法,将报文按照字节分开。

(1)正则表达式为 (.{2})(?=.{2})

表示匹配任意两个字符,并且这两个字符后面还要跟着两个字符

这里使用了正向预查(lookahead)。

具体解析如下:

  • . 表示匹配任意字符。 {2} 表示前面的模式匹配两次。
  • (.) 使用括号将模式包裹起来,表示将匹配结果捕获为一个分组。
  • (?=.{2}) 是一个正向预查,它表示在当前位置往后查找时,后面必须跟着两个字符。

(2)转换具体代码为 re.sub(r’(.{2})(?=.{2})‘, r’\1 ', text)

其中, r’\1 ’ 是替换字符串

它表示将匹配到的内容 替换为 分组捕获的结果 \1(即两个字符),并在后面添加一个空格

import re

.....

# 首先把报文按照字节用空格分开,这里使用正则表达式
re_split = re.sub(r'(.{2})(?=.{2})', r'\1 ', text)
# 然后将报文按照空格分割为列表
split_to_list = re_split .split(' ')

for index, value in loc_text.items():
    if isinstance(index,str) and 'to' in index:
        try:
            start = int(index.split(' ')[0])
            end = int(index.split(' ')[2])
            # 连续替换,连续替换的值也需要转为列表
            value_split = re.sub(r'(.{2})(?=.{2})',r'\1 ',value).split(' ')
            split_to_list[start:end+1]=value_split
        except ValueError:
            print(f'在 {test} 中to前后不是数字')
    elif isinstance(index,int):
        split_to_list[index] = value
    else:
        print(f"无法处理的索引值:{index}")
        
text = ''.join(split_to_list)

3. 将原始报文转为字典

  • 在列表实现的基础上,将报文进一步改为字典
  • enumerate 是 Python 中的一个内置函数,用于将一个可迭代对象(例如列表、元组、字符串等)组合为一个索引序列,返回一个迭代器对象
  • zip 是 Python 中的一个内置函数,用于将多个可迭代对象(例如列表、元组等)的对应元素打包成元组,返回一个迭代器对象
import re

.....

# 首先把报文按照字节用空格分开,这里使用正则表达式
re_split = re.sub(r'(.{2})(?=.{2})', r'\1 ', text)
# 然后将报文按照空格分割为列表
split_to_list = re_split .split(' ')
# 使用lamda表达式,和enumerate方法,将索引和值组成字典
split_to_dic = {index:result for index,result in enumerate(split_to_list)}

for index, value in loc_text.items():
    if isinstance(index,str) and 'to' in index:
        try:
            start = int(index.split(' ')[0])
            end = int(index.split(' ')[2])
            # 连续替换的值转为列表
            value_split = re.sub(r'(.{2})(?=.{2})',r'\1 ',value).split(' ')
            # 使用zip函数,将索引和值打包成元组,返回一个迭代器对象
            value_iter = zip(range(start, end+1),value_split)
            # 更新字典相应key的value
            split_to_dic.update(value_iter)
        except ValueError:
            print(f'在 {test} 中to前后不是数字')
    elif isinstance(index,int):
        split_to_dic[index] = value
    else:
        print(f"无法处理的索引值:{index}")

text = ''.join(split_to_dic.values())

三、代码效率

我们使用 timeit 模块,统计一下每种实现方法的代码效率

首先将需要替换的内容稍微添加一些 loc_text = {0:‘AA’,‘1 to 2’:‘0000’,100:‘BB’,‘205 to 207’:‘AABBDD’,550:‘00’,‘601 to 609’:‘010203040506000000’}

然后使用下面的结构统计执行时间

import timeit
time_start = timeit.default_timer()

#这里放要执行的代码
.......

time_end = timeit.default_timer()
time_spend = time_end - time_start
# 记得将XX替换为具体的名称
print(f'XX耗时 {time_spend}')

统计结果为

切片耗时 6.520003080368042e-05

数组耗时 2.389994915574789e-05

列表耗时 0.0008663999615237117

字典耗时 0.004955200012773275

可以看到,数组效率最高,其次是切片,然后是列表,最后是字典

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值