Skip to content

Commit 7499f32

Browse files
committed
Add Binary Logging Format pattern
1 parent ad1e300 commit 7499f32

File tree

1 file changed

+274
-0
lines changed

1 file changed

+274
-0
lines changed

patterns/blf.hexpat

Lines changed: 274 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,274 @@
1+
// Pattern for binary logging files (Vector BLF)
2+
// References used for writing this:
3+
// https://python-can.readthedocs.io/en/stable/_modules/can/io/blf.html
4+
// https://bitbucket.org/tobylorenz/vector_blf
5+
6+
#pragma magic [ 4C 4F 47 47 ] @ 0x00
7+
#pragma array_limit 4194304
8+
#pragma pattern_limit 4294967296
9+
#pragma endian little
10+
11+
import hex.dec;
12+
import std.io;
13+
import type.magic;
14+
15+
enum object_type : u32 {
16+
unknown = 0,
17+
can_message = 1,
18+
can_error = 2,
19+
can_overload = 3,
20+
can_statistic = 4,
21+
log_container = 10,
22+
app_text = 0x41, // 65
23+
can_message2 = 0x56, // 86
24+
can_fd_message = 100,
25+
can_fd_message_64 = 101,
26+
can_fd_error_64 = 104,
27+
rpc_unknown = 115, // ? "RestorePointContainer"
28+
};
29+
30+
struct can_msg {
31+
u16 channel;
32+
u8 flags;
33+
u8 dlc;
34+
u32 id;
35+
u8 data[8];
36+
};
37+
struct can_msg2 {
38+
u16 channel;
39+
u8 flags;
40+
u8 dlc;
41+
u32 id;
42+
43+
auto struct_len = 2 + 1 + 1 + 4 + 4 + 1 + 1 + 2; // TODO: Alternative way of doing this?
44+
u8 data[parent.header.object_size - parent.header.header_size - struct_len];
45+
46+
u32 frame_length;
47+
u8 bit_count;
48+
padding[1];
49+
padding[2];
50+
};
51+
struct can_fd_msg {
52+
u16 channel;
53+
u8 flags;
54+
u8 dlc;
55+
u32 id;
56+
u32 frame_len;
57+
u8 arbitration_bit_count;
58+
u8 fd_flags;
59+
u8 valid_data_bytes;
60+
padding[1];
61+
padding[4];
62+
63+
u8 data[64];
64+
65+
padding[4];
66+
};
67+
68+
struct app_text {
69+
u32 source;
70+
padding[4];
71+
u32 text_length;
72+
padding[4];
73+
char text[text_length];
74+
};
75+
76+
/// No idea what this is, apparently called "RestorePointContainer"
77+
struct rpc_container {
78+
u8 rpc[14];
79+
u16 data_len;
80+
u8 data[data_len];
81+
};
82+
83+
enum compression_method : u16 {
84+
no_compression = 0,
85+
zlib = 2,
86+
};
87+
88+
// The following section contains all of the decompressed data at once
89+
std::mem::Section decompressed_data = std::mem::create_section("Decompressed data");
90+
// This section is used only for decompressing data
91+
std::mem::Section zlib_decompress_result = std::mem::create_section("zlib decompress result");
92+
93+
struct log_container {
94+
u64 container_begin = $;
95+
96+
compression_method compression_method;
97+
padding[2];
98+
padding[4];
99+
u32 uncompressed_size;
100+
padding[4];
101+
102+
if (compression_method == compression_method::zlib) {
103+
std::mem::set_section_size(zlib_decompress_result, uncompressed_size);
104+
105+
// Create a pattern that defines the compressed array data
106+
auto compressed_byte_len = parent.header.object_size - parent.header.header_size - ($ - container_begin);
107+
u8 compressed[compressed_byte_len];
108+
if (uncompressed_size != 0)
109+
padding[parent.header.object_size % 4]; // Idk, the format wants this... for some reason
110+
111+
std::assert(hex::dec::zlib_decompress(compressed, zlib_decompress_result) == compressed_byte_len,
112+
"zlib decompress needs to succeed");
113+
114+
// Copy the decompressed data to the end of the section
115+
std::mem::copy_section_to_section(zlib_decompress_result, 0,
116+
decompressed_data, std::mem::get_section_size(decompressed_data),
117+
std::mem::get_section_size(zlib_decompress_result));
118+
} else if (compression_method == compression_method::no_compression) {
119+
u8 data[uncompressed_size];
120+
std::mem::copy_value_to_section(data, decompressed_data, std::mem::get_section_size(decompressed_data));
121+
} else {
122+
std::assert(false, "Invalid/unknown compression method");
123+
}
124+
};
125+
126+
enum object_flags : u32 {
127+
time_10_ms = 1,
128+
time_1_ns = 2,
129+
};
130+
131+
enum timestamp_status : u8 {
132+
orig = 0x01,
133+
swhw = 0x02,
134+
user = 0x10,
135+
};
136+
137+
struct obj_v1_header {
138+
object_flags flags;
139+
u16 client_index;
140+
u16 object_version;
141+
u64 timestamp;
142+
};
143+
struct obj_v2_header {
144+
object_flags flags;
145+
timestamp_status timestamp_status;
146+
u8 padding0;
147+
u16 object_version;
148+
u64 timestamp;
149+
u8 padding1[8];
150+
};
151+
152+
struct obj_header {
153+
type::Magic<"LOBJ"> magic; // 4C 4F 42 4A
154+
u16 header_size;
155+
u16 header_version;
156+
u32 object_size;
157+
object_type object_type;
158+
159+
std::assert(header_version == 1 || header_version == 2, "Invalid/unknown header version");
160+
};
161+
162+
struct obj_struct {
163+
obj_header header [[inline]];
164+
165+
if (header.object_type == object_type::log_container) {
166+
log_container log [[inline]];
167+
} else {
168+
if (header.header_version == 1)
169+
obj_v1_header v1_header [[inline]];
170+
else if (header.header_version == 2)
171+
obj_v2_header v2_header [[inline]];
172+
173+
match(header.object_type) {
174+
(object_type::can_message): {
175+
can_msg message [[inline]];
176+
}
177+
(object_type::can_message2): {
178+
can_msg2 message [[inline]];
179+
}
180+
(object_type::can_fd_message): {
181+
can_fd_msg message [[inline]];
182+
}
183+
(object_type::app_text): {
184+
app_text text [[inline]];
185+
padding[header.object_size % 4];
186+
}
187+
(object_type::rpc_unknown): {
188+
rpc_container rpc [[inline]];
189+
}
190+
(_): u8 bytes[header.object_size - header.header_size];
191+
}
192+
}
193+
};
194+
195+
enum application_id : u8 {
196+
unknown = 0,
197+
canalyzer = 1,
198+
canoe = 2,
199+
canstress = 3,
200+
canlog = 4,
201+
canape = 5,
202+
cancasexl = 6,
203+
vlconfig = 7,
204+
porsche_logger = 200,
205+
caetec_logger = 201,
206+
vector_net_sim = 202,
207+
ipetronik_logger = 203,
208+
rtpk = 204,
209+
piketec = 205,
210+
sparks = 206,
211+
};
212+
213+
fn format_api_version(u32 api_version) {
214+
return std::format("{}.{}.{}",
215+
api_version / 1000000,
216+
(api_version % 1000000) / 1000,
217+
(api_version % 1000) / 100);
218+
};
219+
220+
// Mostly just the zlib compression levels, but with some extras
221+
enum compression_level : u8 {
222+
no_compression = 0,
223+
best_speed = 1,
224+
default_compression = 6,
225+
best_compression = 9,
226+
// This means that the file contains only log containers, usually compressed at level 6
227+
default_container_compression = 10,
228+
};
229+
230+
fn extract_u16(u64 data, u64 idx) {
231+
return u16((data >> idx * 16) & 0xFFFF);
232+
};
233+
fn format_timestamp(u128 start_timestamp) {
234+
u64 date = u64(start_timestamp);
235+
u64 time = u64(start_timestamp >> 64);
236+
return std::format("{}-{}-{}_{}-{}-{}",
237+
extract_u16(date, 0), extract_u16(date, 1), extract_u16(date, 3),
238+
extract_u16(time, 0), extract_u16(time, 1), extract_u16(time, 2));
239+
};
240+
241+
struct file_header {
242+
type::Magic<"LOGG"> magic; // 4C 4F 47 47
243+
u32 header_length;
244+
245+
u32 api_version [[format("format_api_version")]];
246+
application_id app_id;
247+
compression_level compression_level;
248+
u8 app_major;
249+
u8 app_minor;
250+
251+
u64 file_length;
252+
u64 uncompressed_length;
253+
u32 object_count;
254+
u32 application_build;
255+
256+
u128 start_timestamp [[format("format_timestamp")]];
257+
u128 stop_timestamp [[format("format_timestamp")]];
258+
259+
u64 restore_point_offset; // ?
260+
} [[inline]];
261+
262+
struct file_layout {
263+
file_header header;
264+
padding[header.header_length - sizeof(header)];
265+
obj_struct objects[while($ < std::mem::size())];
266+
267+
// Decode all objects from the zlib compressed data
268+
if (std::mem::get_section_size(decompressed_data) != 0)
269+
obj_struct decompressed_objects[header.object_count] @ 0x00 in decompressed_data;
270+
} [[inline]];
271+
272+
file_layout file @ 0x00;
273+
274+
std::assert_warn(std::mem::size() == file.header.file_length, "file size mismatch");

0 commit comments

Comments
 (0)