struct-frame 0.0.31__tar.gz → 0.0.42__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of struct-frame might be problematic. Click here for more details.
- {struct_frame-0.0.31 → struct_frame-0.0.42}/PKG-INFO +1 -1
- {struct_frame-0.0.31 → struct_frame-0.0.42}/pyproject.toml +6 -5
- struct_frame-0.0.42/src/struct_frame/boilerplate/c/struct_frame.h +72 -0
- struct_frame-0.0.42/src/struct_frame/boilerplate/c/struct_frame_cpp.h +41 -0
- struct_frame-0.0.42/src/struct_frame/boilerplate/c/struct_frame_default_frame.h +59 -0
- struct_frame-0.0.42/src/struct_frame/boilerplate/c/struct_frame_gen.h +17 -0
- struct_frame-0.0.42/src/struct_frame/boilerplate/c/struct_frame_parser.h +107 -0
- struct_frame-0.0.42/src/struct_frame/boilerplate/c/struct_frame_types.h +76 -0
- struct_frame-0.0.42/src/struct_frame/boilerplate/py/__init__.py +0 -0
- struct_frame-0.0.42/src/struct_frame/boilerplate/py/struct_frame_parser.py +119 -0
- struct_frame-0.0.42/src/struct_frame/boilerplate/ts/struct_frame.ts +65 -0
- struct_frame-0.0.42/src/struct_frame/boilerplate/ts/struct_frame_gen.ts +7 -0
- struct_frame-0.0.42/src/struct_frame/boilerplate/ts/struct_frame_parser.ts +98 -0
- struct_frame-0.0.42/src/struct_frame/boilerplate/ts/struct_frame_types.ts +80 -0
- {struct_frame-0.0.31 → struct_frame-0.0.42}/.clang-format +0 -0
- {struct_frame-0.0.31 → struct_frame-0.0.42}/.github/copilot-instructions.md +0 -0
- {struct_frame-0.0.31 → struct_frame-0.0.42}/.gitignore +0 -0
- {struct_frame-0.0.31 → struct_frame-0.0.42}/AI_TESTING_GUIDE.md +0 -0
- {struct_frame-0.0.31 → struct_frame-0.0.42}/DEVGUIDE.md +0 -0
- {struct_frame-0.0.31 → struct_frame-0.0.42}/LICENSE +0 -0
- {struct_frame-0.0.31 → struct_frame-0.0.42}/README.md +0 -0
- {struct_frame-0.0.31 → struct_frame-0.0.42}/TODO +0 -0
- {struct_frame-0.0.31 → struct_frame-0.0.42}/examples/array_test.proto +0 -0
- {struct_frame-0.0.31 → struct_frame-0.0.42}/examples/generic_robot.proto +0 -0
- {struct_frame-0.0.31 → struct_frame-0.0.42}/examples/index.ts +0 -0
- {struct_frame-0.0.31 → struct_frame-0.0.42}/examples/main.c +0 -0
- {struct_frame-0.0.31 → struct_frame-0.0.42}/package-lock.json +0 -0
- {struct_frame-0.0.31 → struct_frame-0.0.42}/package.json +0 -0
- {struct_frame-0.0.31 → struct_frame-0.0.42}/src/main.py +0 -0
- {struct_frame-0.0.31 → struct_frame-0.0.42}/src/struct_frame/__init__.py +0 -0
- {struct_frame-0.0.31 → struct_frame-0.0.42}/src/struct_frame/__main__.py +0 -0
- {struct_frame-0.0.31 → struct_frame-0.0.42}/src/struct_frame/base.py +0 -0
- {struct_frame-0.0.31 → struct_frame-0.0.42}/src/struct_frame/c_gen.py +0 -0
- {struct_frame-0.0.31 → struct_frame-0.0.42}/src/struct_frame/generate.py +0 -0
- {struct_frame-0.0.31 → struct_frame-0.0.42}/src/struct_frame/gql_gen.py +0 -0
- {struct_frame-0.0.31 → struct_frame-0.0.42}/src/struct_frame/py_gen.py +0 -0
- {struct_frame-0.0.31 → struct_frame-0.0.42}/src/struct_frame/ts_gen.py +0 -0
- {struct_frame-0.0.31 → struct_frame-0.0.42}/test_all.py +0 -0
- {struct_frame-0.0.31 → struct_frame-0.0.42}/tests/README.md +0 -0
- {struct_frame-0.0.31 → struct_frame-0.0.42}/tests/proto/basic_types.proto +0 -0
- {struct_frame-0.0.31 → struct_frame-0.0.42}/tests/proto/comprehensive_arrays.proto +0 -0
- {struct_frame-0.0.31 → struct_frame-0.0.42}/tests/proto/nested_messages.proto +0 -0
- {struct_frame-0.0.31 → struct_frame-0.0.42}/tests/proto/serialization_test.proto +0 -0
- {struct_frame-0.0.31 → struct_frame-0.0.42}/tests/run_tests.py +0 -0
- {struct_frame-0.0.31 → struct_frame-0.0.42}/tsconfig.json +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: struct-frame
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.42
|
|
4
4
|
Summary: A framework for serializing data with headers
|
|
5
5
|
Project-URL: Homepage, https://github.com/mylonics/struct-frame
|
|
6
6
|
Project-URL: Issues, https://github.com/mylonics/struct-frame/issues
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "struct-frame"
|
|
7
|
-
version = "0.0.
|
|
7
|
+
version = "0.0.42"
|
|
8
8
|
authors = [
|
|
9
9
|
{ name="Rijesh Augustine", email="rijesh@mylonics.com" },
|
|
10
10
|
]
|
|
@@ -27,8 +27,9 @@ Homepage = "https://github.com/mylonics/struct-frame"
|
|
|
27
27
|
Issues = "https://github.com/mylonics/struct-frame/issues"
|
|
28
28
|
|
|
29
29
|
|
|
30
|
-
[tool.hatch.build.targets.wheel]
|
|
31
|
-
only-include = ["src/struct_frame"]
|
|
32
30
|
|
|
33
|
-
[tool.hatch.build.targets.
|
|
34
|
-
"src" = ""
|
|
31
|
+
[tool.hatch.build.targets.sdist.force-include]
|
|
32
|
+
"src/struct_frame/boilerplate" = "src/struct_frame/boilerplate"
|
|
33
|
+
|
|
34
|
+
[tool.hatch.build.targets.wheel.force-include]
|
|
35
|
+
"src/struct_frame/boilerplate" = "struct_frame/boilerplate"
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
#include "stdbool.h"
|
|
3
|
+
#include "stdint.h"
|
|
4
|
+
#include "string.h"
|
|
5
|
+
#include "struct_frame_types.h"
|
|
6
|
+
|
|
7
|
+
static inline struct checksum_t fletcher_checksum_calculation(uint8_t* buffer, uint8_t data_length) {
|
|
8
|
+
checksum_t checksum = {0};
|
|
9
|
+
|
|
10
|
+
for (int i = 0; i < data_length; i++) {
|
|
11
|
+
checksum.byte1 += buffer[i];
|
|
12
|
+
checksum.byte2 += checksum.byte1;
|
|
13
|
+
}
|
|
14
|
+
return checksum;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
static inline bool msg_encode(msg_encode_buffer* buffer, packet_format_t* format, void* msg, uint8_t msg_id,
|
|
18
|
+
uint8_t msg_size) {
|
|
19
|
+
if (buffer->in_progress) {
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
buffer->in_progress = true;
|
|
23
|
+
buffer->size += format->encode(buffer->data, msg_id, (uint8_t*)msg, msg_size);
|
|
24
|
+
buffer->in_progress = false;
|
|
25
|
+
return true;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
static inline uint8_t* msg_reserve(msg_encode_buffer* buffer, packet_format_t* format, uint8_t msg_id,
|
|
29
|
+
uint8_t msg_size) {
|
|
30
|
+
if (buffer->in_progress) {
|
|
31
|
+
return NULL;
|
|
32
|
+
}
|
|
33
|
+
buffer->in_progress = true;
|
|
34
|
+
uint8_t* out = format->encode_reserve(buffer->data, msg_id, msg_size);
|
|
35
|
+
return out;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
static inline bool msg_finish(msg_encode_buffer* buffer, packet_format_t* format, uint8_t msg_size) {
|
|
39
|
+
if (buffer->in_progress == false) {
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
buffer->size += format->encode_finsish(buffer->data, msg_size);
|
|
44
|
+
buffer->in_progress = false;
|
|
45
|
+
return true;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
#define MESSAGE_HELPER(funcname, name, msg_size, msg_id) \
|
|
49
|
+
static inline bool funcname##_encode(msg_encode_buffer* buffer, packet_format_t* format, name* name##_obj) { \
|
|
50
|
+
return msg_encode(buffer, format, name##_obj, msg_id, msg_size); \
|
|
51
|
+
} \
|
|
52
|
+
static inline bool funcname##_reserve(msg_encode_buffer* buffer, packet_format_t* format, name** msg) { \
|
|
53
|
+
void* ptr = msg_reserve(buffer, format, msg_id, msg_size); \
|
|
54
|
+
if (ptr) { \
|
|
55
|
+
*msg = (name*)ptr; \
|
|
56
|
+
return true; \
|
|
57
|
+
} \
|
|
58
|
+
return false; \
|
|
59
|
+
} \
|
|
60
|
+
static inline bool funcname##_finish(msg_encode_buffer* buffer, packet_format_t* format) { \
|
|
61
|
+
return msg_finish(buffer, format, msg_size); \
|
|
62
|
+
} \
|
|
63
|
+
static inline name funcname##_get_from_buffer(uint8_t* buffer) { \
|
|
64
|
+
name msg = *(name*)(buffer); \
|
|
65
|
+
return msg; \
|
|
66
|
+
} \
|
|
67
|
+
static inline name funcname##_get(msg_info_t result) { \
|
|
68
|
+
name msg = *(name*)(result.msg_loc); \
|
|
69
|
+
return msg; \
|
|
70
|
+
} \
|
|
71
|
+
static inline name* funcname##_get_ref_from_buffer(uint8_t* buffer) { return (name*)(buffer); } \
|
|
72
|
+
static inline name* funcname##_get_ref(msg_info_t result) { return (name*)(result.msg_loc); }
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
#include "struct_frame_parser.h"
|
|
4
|
+
#include "struct_frame_types.h"
|
|
5
|
+
|
|
6
|
+
class StructFrameDevice : public msg_encode_buffer {
|
|
7
|
+
public:
|
|
8
|
+
StructFrameDevice(packet_format_t formats[], packet_definitions_t defines[])
|
|
9
|
+
: msg_encode_buffer{0, 0, 0},
|
|
10
|
+
packet_state_{LOOKING_FOR_START_BYTE, 0, formats, defines, nullptr, 0, 0, 0, false, 0} {}
|
|
11
|
+
|
|
12
|
+
void RunRx() {
|
|
13
|
+
GetArray(packet_state_.buffer, packet_state_.buffer_size);
|
|
14
|
+
if (packet_state_.buffer && packet_state_.buffer_size) {
|
|
15
|
+
while (!packet_state_.finished) {
|
|
16
|
+
msg_info_t info = parse_buffer(&packet_state_);
|
|
17
|
+
if (info.valid) {
|
|
18
|
+
HandleResult(info);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
void RunTx() { PutArray(msg_encode_buffer::data, msg_encode_buffer::max_size, msg_encode_buffer::size); }
|
|
25
|
+
|
|
26
|
+
protected:
|
|
27
|
+
void Init() {
|
|
28
|
+
size_t dummy = 0;
|
|
29
|
+
PutArray(msg_encode_buffer::data, msg_encode_buffer::max_size, dummy);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Put Array must accept the full buffer of data and returns a pointer to either a new buffer or the same buffer
|
|
33
|
+
// that is free
|
|
34
|
+
virtual void PutArray(uint8_t *&buffer, size_t &max_length, size_t &length) = 0;
|
|
35
|
+
|
|
36
|
+
// Get array, a pointer to an array and refernce to the array length is pased and mutated by this function
|
|
37
|
+
virtual void GetArray(uint8_t *&buffer, size_t &length) = 0;
|
|
38
|
+
|
|
39
|
+
virtual void HandleResult(msg_info_t info) = 0;
|
|
40
|
+
packet_state_t packet_state_;
|
|
41
|
+
};
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
#include "stdint.h"
|
|
3
|
+
#include "struct_frame.h"
|
|
4
|
+
#include "struct_frame_parser.h"
|
|
5
|
+
#include "struct_frame_types.h"
|
|
6
|
+
|
|
7
|
+
#define BASIC_FRAME_HEADER_LENGTH 2
|
|
8
|
+
#define BASIC_FRAME_FOOTER_LENGTH 2
|
|
9
|
+
|
|
10
|
+
bool basic_frame_check_start_bytes(uint8_t c) { return c == 0x90; }
|
|
11
|
+
|
|
12
|
+
bool basic_frame_process_header_byte(uint8_t c, size_t length) { return length >= BASIC_FRAME_HEADER_LENGTH; }
|
|
13
|
+
|
|
14
|
+
size_t basic_frame_get_msg_id(uint8_t* data) { return data[1]; }
|
|
15
|
+
|
|
16
|
+
size_t basic_frame_get_full_packet_length(size_t msg_length) {
|
|
17
|
+
return msg_length + BASIC_FRAME_HEADER_LENGTH + BASIC_FRAME_FOOTER_LENGTH;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
msg_info_t basic_frame_validate_packet(uint8_t* data, size_t packet_length) {
|
|
21
|
+
size_t msg_size = packet_length - BASIC_FRAME_HEADER_LENGTH - BASIC_FRAME_FOOTER_LENGTH;
|
|
22
|
+
checksum_t ck = fletcher_checksum_calculation(data + 1, msg_size + 1);
|
|
23
|
+
|
|
24
|
+
msg_info_t info = {false, (uint8_t)msg_size, data[1], data + 2};
|
|
25
|
+
if (ck.byte1 == data[packet_length - 2] && ck.byte2 == data[packet_length - 1]) {
|
|
26
|
+
info.valid = true;
|
|
27
|
+
}
|
|
28
|
+
return info;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
uint8_t basic_frame_finish(uint8_t* buffer, uint8_t msg_size) {
|
|
32
|
+
checksum_t ck = fletcher_checksum_calculation(buffer + 1, msg_size + 1);
|
|
33
|
+
|
|
34
|
+
buffer[msg_size + BASIC_FRAME_HEADER_LENGTH] = ck.byte1;
|
|
35
|
+
buffer[msg_size + BASIC_FRAME_HEADER_LENGTH + 1] = ck.byte2;
|
|
36
|
+
return msg_size + BASIC_FRAME_HEADER_LENGTH + BASIC_FRAME_FOOTER_LENGTH;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
size_t basic_frame_encode(uint8_t* buffer, uint8_t msg_id, uint8_t* msg, uint8_t msg_size) {
|
|
40
|
+
buffer[0] = 0x90;
|
|
41
|
+
buffer[1] = msg_id;
|
|
42
|
+
memcpy(buffer + 2, msg, msg_size);
|
|
43
|
+
return basic_frame_finish(buffer, msg_size);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
uint8_t* basic_frame_reserve(uint8_t* buffer, uint8_t msg_id, uint8_t msg_size) {
|
|
47
|
+
buffer[0] = 0x90;
|
|
48
|
+
buffer[1] = msg_id;
|
|
49
|
+
return buffer + 2;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
static packet_format_t default_frame_format = {.check_start_bytes = basic_frame_check_start_bytes,
|
|
53
|
+
.process_header_byte = basic_frame_process_header_byte,
|
|
54
|
+
.get_msg_id = basic_frame_get_msg_id,
|
|
55
|
+
.get_full_packet_length = basic_frame_get_full_packet_length,
|
|
56
|
+
.validate_packet = basic_frame_validate_packet,
|
|
57
|
+
.encode = basic_frame_encode,
|
|
58
|
+
.encode_reserve = basic_frame_reserve,
|
|
59
|
+
.encode_finsish = basic_frame_finish};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
// bool getMessageLength(size_t id, size_t *length) {
|
|
4
|
+
// *length = 10;
|
|
5
|
+
// return false;
|
|
6
|
+
// }
|
|
7
|
+
//
|
|
8
|
+
// packet_format_t packetFormats[10];
|
|
9
|
+
//
|
|
10
|
+
// packet_format_t *getPacketFormat(packet_format_t *formats, size_t formats_length, uint8_t c) {
|
|
11
|
+
// for (int i = 0; i++; i < formats_length) {
|
|
12
|
+
// if (formats[i].check_start_bytes(c)) {
|
|
13
|
+
// return &formats[i];
|
|
14
|
+
// }
|
|
15
|
+
// }
|
|
16
|
+
// return NULL;
|
|
17
|
+
// }
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
#include "stdint.h"
|
|
3
|
+
#include "struct_frame_types.h"
|
|
4
|
+
|
|
5
|
+
// https://github.com/serge-sans-paille/frozen
|
|
6
|
+
|
|
7
|
+
static inline msg_info_t parse_buffer(packet_state_t *packet_state) {
|
|
8
|
+
packet_state->state = LOOKING_FOR_START_BYTE;
|
|
9
|
+
packet_state->finished = false;
|
|
10
|
+
for (size_t i = packet_state->r_loc; i < packet_state->buffer_size; i++) {
|
|
11
|
+
uint8_t c = packet_state->buffer[i];
|
|
12
|
+
switch (packet_state->state) {
|
|
13
|
+
case LOOKING_FOR_START_BYTE:
|
|
14
|
+
packet_state->format = packet_state->defines->get_packet_formats(c);
|
|
15
|
+
if (packet_state->format) {
|
|
16
|
+
packet_state->packet_start_index = i;
|
|
17
|
+
if (packet_state->format->process_header_byte(c, true)) {
|
|
18
|
+
packet_state->state = GETTING_PAYLOAD;
|
|
19
|
+
} else {
|
|
20
|
+
packet_state->state = GETTING_HEADER;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
break;
|
|
24
|
+
|
|
25
|
+
case GETTING_HEADER:
|
|
26
|
+
if (packet_state->format->process_header_byte(c, false)) {
|
|
27
|
+
size_t msg_id = packet_state->format->get_msg_id(packet_state->buffer + packet_state->packet_start_index);
|
|
28
|
+
size_t length = 0;
|
|
29
|
+
if (packet_state->defines->get_message_length(msg_id, &length)) {
|
|
30
|
+
packet_state->packet_size = packet_state->format->get_full_packet_length(length);
|
|
31
|
+
packet_state->state = GETTING_PAYLOAD;
|
|
32
|
+
} else {
|
|
33
|
+
packet_state->state = LOOKING_FOR_START_BYTE;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
break;
|
|
37
|
+
|
|
38
|
+
case GETTING_PAYLOAD:
|
|
39
|
+
if ((packet_state->packet_start_index + packet_state->packet_size) > packet_state->buffer_size) {
|
|
40
|
+
packet_state->state = LOOKING_FOR_START_BYTE;
|
|
41
|
+
msg_info_t info = packet_state->format->validate_packet(
|
|
42
|
+
packet_state->buffer + packet_state->packet_start_index, packet_state->packet_size);
|
|
43
|
+
packet_state->r_loc += i;
|
|
44
|
+
return info;
|
|
45
|
+
} else {
|
|
46
|
+
packet_state->state = LOOKING_FOR_START_BYTE;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
break;
|
|
50
|
+
|
|
51
|
+
default:
|
|
52
|
+
break;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
packet_state->finished = true;
|
|
56
|
+
packet_state->r_loc = 0;
|
|
57
|
+
|
|
58
|
+
msg_info_t info = {0, 0, 0, 0};
|
|
59
|
+
return info;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
msg_info_t parse_char(packet_state_t *state, const uint8_t c) {
|
|
63
|
+
state->buffer[state->buffer_size] = c;
|
|
64
|
+
state->buffer_size = (state->buffer_size + 1) % state->buffer_max_size;
|
|
65
|
+
|
|
66
|
+
switch (state->state) {
|
|
67
|
+
case LOOKING_FOR_START_BYTE:;
|
|
68
|
+
state->format = state->defines->get_packet_formats(c);
|
|
69
|
+
if (state->format) {
|
|
70
|
+
state->buffer[0] = state->buffer[state->buffer_size];
|
|
71
|
+
state->buffer_size = 0;
|
|
72
|
+
if (state->format->process_header_byte(c, true)) {
|
|
73
|
+
state->state = GETTING_PAYLOAD;
|
|
74
|
+
} else {
|
|
75
|
+
state->state = GETTING_HEADER;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
break;
|
|
79
|
+
|
|
80
|
+
case GETTING_HEADER:
|
|
81
|
+
if (state->format->process_header_byte(c, false)) {
|
|
82
|
+
size_t msg_id = state->format->get_msg_id(state->buffer);
|
|
83
|
+
size_t length = 0;
|
|
84
|
+
if (state->defines->get_message_length(msg_id, &length)) {
|
|
85
|
+
state->packet_size = state->format->get_full_packet_length(length);
|
|
86
|
+
state->state = GETTING_PAYLOAD;
|
|
87
|
+
} else {
|
|
88
|
+
state->state = LOOKING_FOR_START_BYTE;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
break;
|
|
92
|
+
|
|
93
|
+
case GETTING_PAYLOAD:
|
|
94
|
+
if (state->buffer_size >= state->packet_size) {
|
|
95
|
+
state->state = LOOKING_FOR_START_BYTE;
|
|
96
|
+
msg_info_t info = state->format->validate_packet(state->buffer, state->buffer_size);
|
|
97
|
+
return info;
|
|
98
|
+
}
|
|
99
|
+
break;
|
|
100
|
+
|
|
101
|
+
default:
|
|
102
|
+
break;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
msg_info_t info = {0, 0, 0, 0};
|
|
106
|
+
return info;
|
|
107
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
#include "stdbool.h"
|
|
4
|
+
#include "stdint.h"
|
|
5
|
+
|
|
6
|
+
// https://github.com/serge-sans-paille/frozen
|
|
7
|
+
// https://www.npmjs.com/package/typed-struct
|
|
8
|
+
|
|
9
|
+
// #define default_parser {1, 0, 0x90}
|
|
10
|
+
//
|
|
11
|
+
// #define zero_initialized_parser_result {default_parser, false, 0, 0, false, {0, 0}}
|
|
12
|
+
//
|
|
13
|
+
// #define CREATE_DEFAULT_STRUCT_BUFFER(name, size)
|
|
14
|
+
// uint8_t name##_buffer[size];
|
|
15
|
+
// struct_buffer name = {default_parser, name##_buffer, size, 0, false, 0, LOOKING_FOR_START_BYTE, 0, {false, 0, 0}};
|
|
16
|
+
|
|
17
|
+
typedef struct checksum_t {
|
|
18
|
+
uint8_t byte1;
|
|
19
|
+
uint8_t byte2;
|
|
20
|
+
} checksum_t;
|
|
21
|
+
|
|
22
|
+
typedef struct msg_info_t {
|
|
23
|
+
bool valid;
|
|
24
|
+
uint8_t len;
|
|
25
|
+
uint8_t msg_id;
|
|
26
|
+
uint8_t *msg_loc;
|
|
27
|
+
} msg_info_t;
|
|
28
|
+
|
|
29
|
+
typedef struct _packet_format {
|
|
30
|
+
bool (*check_start_bytes)(uint8_t);
|
|
31
|
+
bool (*process_header_byte)(uint8_t, size_t);
|
|
32
|
+
size_t (*get_msg_id)(uint8_t *data);
|
|
33
|
+
size_t (*get_full_packet_length)(size_t);
|
|
34
|
+
struct msg_info_t (*validate_packet)(uint8_t *, size_t);
|
|
35
|
+
size_t (*encode)(uint8_t *buffer, uint8_t msg_id, uint8_t *msg, uint8_t msg_size);
|
|
36
|
+
uint8_t *(*encode_reserve)(uint8_t *buffer, uint8_t msg_id, uint8_t msg_size);
|
|
37
|
+
uint8_t (*encode_finsish)(uint8_t *buffer, uint8_t msg_size);
|
|
38
|
+
|
|
39
|
+
} packet_format_t;
|
|
40
|
+
|
|
41
|
+
enum parser_state_enum { LOOKING_FOR_START_BYTE = 0, GETTING_HEADER = 1, GETTING_PAYLOAD = 2 };
|
|
42
|
+
|
|
43
|
+
typedef struct _definitions {
|
|
44
|
+
bool (*get_message_length)(size_t, size_t *);
|
|
45
|
+
packet_format_t *(*get_packet_formats)(uint8_t);
|
|
46
|
+
} packet_definitions_t;
|
|
47
|
+
|
|
48
|
+
typedef struct packet_state_t {
|
|
49
|
+
enum parser_state_enum state;
|
|
50
|
+
size_t packet_size;
|
|
51
|
+
packet_format_t *format;
|
|
52
|
+
packet_definitions_t *defines;
|
|
53
|
+
|
|
54
|
+
uint8_t *buffer;
|
|
55
|
+
size_t buffer_size;
|
|
56
|
+
|
|
57
|
+
// for parse buffer
|
|
58
|
+
size_t buffer_max_size;
|
|
59
|
+
size_t packet_start_index;
|
|
60
|
+
bool finished;
|
|
61
|
+
size_t r_loc;
|
|
62
|
+
} packet_state_t;
|
|
63
|
+
|
|
64
|
+
typedef struct _msg_encode_buffer {
|
|
65
|
+
uint8_t *data;
|
|
66
|
+
size_t max_size;
|
|
67
|
+
size_t size;
|
|
68
|
+
bool in_progress;
|
|
69
|
+
} msg_encode_buffer;
|
|
70
|
+
|
|
71
|
+
typedef struct _struct_frame_parse_char_impl {
|
|
72
|
+
// Used for framing and parsing
|
|
73
|
+
packet_format_t packet_format;
|
|
74
|
+
packet_state_t parser_state;
|
|
75
|
+
|
|
76
|
+
} struct_frame_parse_char_impl;
|
|
File without changes
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
|
|
2
|
+
from enum import Enum
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def fletcher_checksum_calculation(buffer, start=0, end=None):
|
|
6
|
+
if end == None:
|
|
7
|
+
end = len(buffer)
|
|
8
|
+
|
|
9
|
+
byte1 = 0
|
|
10
|
+
byte2 = 0
|
|
11
|
+
|
|
12
|
+
for x in range(start, end):
|
|
13
|
+
byte1 = (byte1 + buffer[x]) % 256
|
|
14
|
+
byte2 = (byte2 + byte1) % 256
|
|
15
|
+
|
|
16
|
+
return [byte1, byte2]
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class BasicPacket:
|
|
20
|
+
start_byte = 0x90
|
|
21
|
+
header_length = 0
|
|
22
|
+
footer_length = 0
|
|
23
|
+
|
|
24
|
+
desired_packet_length = 0
|
|
25
|
+
packet = []
|
|
26
|
+
|
|
27
|
+
def __init__(self, start_byte=0x90, header_length=2, footer_length=2):
|
|
28
|
+
self.start_byte = start_byte
|
|
29
|
+
self.header_length = header_length
|
|
30
|
+
self.footer_length = footer_length
|
|
31
|
+
|
|
32
|
+
def add_header_byte(self, byte, clear=False):
|
|
33
|
+
if clear:
|
|
34
|
+
self.packet.clear()
|
|
35
|
+
self.packet.append(byte)
|
|
36
|
+
return len(self.packet) >= self.header_length
|
|
37
|
+
|
|
38
|
+
def add_packet_byte(self, byte):
|
|
39
|
+
self.packet.append(byte)
|
|
40
|
+
return len(self.packet) == self.desired_packet_length
|
|
41
|
+
|
|
42
|
+
def get_msg_id(self):
|
|
43
|
+
return self.packet[1]
|
|
44
|
+
|
|
45
|
+
def get_full_packet_length(self, msg_length):
|
|
46
|
+
self.desired_packet_length = self.header_length + self.footer_length + msg_length
|
|
47
|
+
return self.desired_packet_length
|
|
48
|
+
|
|
49
|
+
def validate_packet(self):
|
|
50
|
+
checksum = fletcher_checksum_calculation(
|
|
51
|
+
self.packet, self.header_length - 1, self.desired_packet_length - self.footer_length)
|
|
52
|
+
return checksum[0] == self.packet[-2] and checksum[1] == self.packet[-1]
|
|
53
|
+
|
|
54
|
+
def get_msg_buffer(self):
|
|
55
|
+
return self.packet[self.header_length:self.desired_packet_length - self.footer_length]
|
|
56
|
+
|
|
57
|
+
def encode_msg(self, msg):
|
|
58
|
+
return self.encode(msg.pack(), msg.msg_id)
|
|
59
|
+
|
|
60
|
+
def encode(self, data, msg_id):
|
|
61
|
+
output = []
|
|
62
|
+
output.append(self.start_byte)
|
|
63
|
+
output.append(msg_id)
|
|
64
|
+
if (len(data)):
|
|
65
|
+
for b in data:
|
|
66
|
+
output.append(b)
|
|
67
|
+
checksum = fletcher_checksum_calculation(data)
|
|
68
|
+
|
|
69
|
+
output.append(checksum[0])
|
|
70
|
+
output.append(checksum[1])
|
|
71
|
+
return output
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
class ParserState(Enum):
|
|
75
|
+
LOOKING_FOR_START_BYTE = 0
|
|
76
|
+
GETTING_HEADER = 1
|
|
77
|
+
GETTING_PACKET = 2
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
class FrameParser:
|
|
81
|
+
state = ParserState.LOOKING_FOR_START_BYTE
|
|
82
|
+
buffer = []
|
|
83
|
+
packetFormat = None
|
|
84
|
+
msg_definitions = None
|
|
85
|
+
msg_id_loc = None
|
|
86
|
+
msg_type = None
|
|
87
|
+
|
|
88
|
+
def __init__(self, packetFormats, msg_definitions):
|
|
89
|
+
self.packetFormats = packetFormats
|
|
90
|
+
self.msg_definitions = msg_definitions
|
|
91
|
+
|
|
92
|
+
def parse_char(self, c):
|
|
93
|
+
if self.state == ParserState.LOOKING_FOR_START_BYTE:
|
|
94
|
+
if c in self.packetFormats:
|
|
95
|
+
self.packetFormat = self.packetFormats[c]
|
|
96
|
+
if self.packetFormat.add_header_byte(c, True):
|
|
97
|
+
self.state = ParserState.GETTING_PACKET
|
|
98
|
+
else:
|
|
99
|
+
self.state = ParserState.GETTING_HEADER
|
|
100
|
+
|
|
101
|
+
elif self.state == ParserState.GETTING_HEADER:
|
|
102
|
+
if self.packetFormat.add_header_byte(c):
|
|
103
|
+
msg_id = self.packetFormat.get_msg_id()
|
|
104
|
+
if msg_id in self.msg_definitions:
|
|
105
|
+
self.msg_type = self.msg_definitions[msg_id]
|
|
106
|
+
if self.msg_type:
|
|
107
|
+
self.packetFormat.get_full_packet_length(
|
|
108
|
+
self.msg_type.msg_size)
|
|
109
|
+
self.state = ParserState.GETTING_PACKET
|
|
110
|
+
else:
|
|
111
|
+
self.state = ParserState.LOOKING_FOR_START_BYTE
|
|
112
|
+
|
|
113
|
+
elif self.state == ParserState.GETTING_PACKET:
|
|
114
|
+
if self.packetFormat.add_packet_byte(c):
|
|
115
|
+
self.state = ParserState.LOOKING_FOR_START_BYTE
|
|
116
|
+
if self.packetFormat.validate_packet():
|
|
117
|
+
return self.msg_type.create_unpack(bytes(self.packetFormat.get_msg_buffer()))
|
|
118
|
+
|
|
119
|
+
return False
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { Struct } from 'typed-struct';
|
|
2
|
+
import * as sf_types from './struct_frame_types';
|
|
3
|
+
|
|
4
|
+
function fletcher_checksum_calculation(buffer: Uint8Array, data_length: number): sf_types.checksum_t {
|
|
5
|
+
const checksum: sf_types.checksum_t = { byte1: 0, byte2: 0 };
|
|
6
|
+
|
|
7
|
+
for (let i = 0; i < data_length; i++) {
|
|
8
|
+
checksum.byte1 += buffer[i];
|
|
9
|
+
checksum.byte2 += checksum.byte1;
|
|
10
|
+
}
|
|
11
|
+
return checksum;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function msg_encode(buffer: sf_types.struct_frame_buffer, msg: any, msgid: number) {
|
|
15
|
+
buffer.data[buffer.size++] = buffer.config.start_byte;
|
|
16
|
+
buffer.crc_start_loc = buffer.size;
|
|
17
|
+
buffer.data[buffer.size++] = msgid;
|
|
18
|
+
|
|
19
|
+
if (buffer.config.has_len) {
|
|
20
|
+
buffer.data[buffer.size++] = msg.getSize();
|
|
21
|
+
}
|
|
22
|
+
const rawData = Struct.raw(msg);
|
|
23
|
+
for (let i = 0; i < rawData.length; i++) {
|
|
24
|
+
buffer.data[buffer.size++] = rawData[i]
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (buffer.config.has_crc) {
|
|
28
|
+
const crc = fletcher_checksum_calculation(buffer.data.slice(buffer.crc_start_loc), buffer.crc_start_loc + rawData.length);
|
|
29
|
+
buffer.data[buffer.size++] = crc.byte1;
|
|
30
|
+
buffer.data[buffer.size++] = crc.byte2;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function msg_reserve(buffer: sf_types.struct_frame_buffer, msg_id: number, msg_size: number) {
|
|
35
|
+
throw new Error('Function Unimplemented');
|
|
36
|
+
|
|
37
|
+
if (buffer.in_progress) {
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
buffer.in_progress = true;
|
|
41
|
+
buffer.data[buffer.size++] = buffer.config.start_byte;
|
|
42
|
+
|
|
43
|
+
buffer.data[buffer.size++] = msg_id;
|
|
44
|
+
if (buffer.config.has_len) {
|
|
45
|
+
buffer.data[buffer.size++] = msg_size;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const ret = Buffer.from(buffer.data, buffer.size, msg_size);
|
|
49
|
+
buffer.size += msg_size;
|
|
50
|
+
return ret;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
export function msg_finish(buffer: sf_types.struct_frame_buffer) {
|
|
56
|
+
throw new Error('Function Unimplemented');
|
|
57
|
+
|
|
58
|
+
if (buffer.config.has_crc) {
|
|
59
|
+
const crc = fletcher_checksum_calculation(buffer.data.slice(buffer.crc_start_loc), buffer.crc_start_loc - buffer.size);
|
|
60
|
+
buffer.data[buffer.size++] = crc.byte1;
|
|
61
|
+
buffer.data[buffer.size++] = crc.byte2;
|
|
62
|
+
buffer.size += 2
|
|
63
|
+
}
|
|
64
|
+
buffer.in_progress = false;
|
|
65
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { get_message_length } from './struct_frame_gen';
|
|
2
|
+
import * as sf_types from './struct_frame_types';
|
|
3
|
+
|
|
4
|
+
function parse_default_format_validate(buffer: Uint8Array, msg_id_len: sf_types.msg_id_len_t): boolean {
|
|
5
|
+
return true;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
function parse_default_format_char_for_len_id(c: number, msg_id_len: sf_types.msg_id_len_t): boolean {
|
|
9
|
+
msg_id_len.msg_id = c;
|
|
10
|
+
msg_id_len.len = get_message_length(c);
|
|
11
|
+
return true;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const default_parser_functions: sf_types.parser_functions_t = { get_msg_id_len: parse_default_format_char_for_len_id, validate_packet: parse_default_format_validate };
|
|
15
|
+
|
|
16
|
+
function parse_char_for_start_byte(config: sf_types.struct_frame_config, c: number): sf_types.parser_functions_t | undefined {
|
|
17
|
+
if (config.start_byte == c) {
|
|
18
|
+
return default_parser_functions;
|
|
19
|
+
}
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function parse_char(pb: sf_types.struct_frame_buffer, c: number): boolean {
|
|
24
|
+
let parse_func_ptr: sf_types.parser_functions_t | undefined = undefined;
|
|
25
|
+
switch (pb.state) {
|
|
26
|
+
case sf_types.ParserState.LOOKING_FOR_START_BYTE:
|
|
27
|
+
parse_func_ptr = parse_char_for_start_byte(pb.config, c);
|
|
28
|
+
if (parse_func_ptr) {
|
|
29
|
+
pb.config.parser_funcs = parse_func_ptr;
|
|
30
|
+
pb.state = sf_types.ParserState.GETTING_LENGTH_MSG_AND_ID;
|
|
31
|
+
}
|
|
32
|
+
break;
|
|
33
|
+
|
|
34
|
+
case sf_types.ParserState.GETTING_LENGTH_MSG_AND_ID:
|
|
35
|
+
if (pb.config.parser_funcs && pb.config.parser_funcs.get_msg_id_len(c, pb.msg_id_len)) {
|
|
36
|
+
pb.state = sf_types.ParserState.GETTING_PAYLOAD;
|
|
37
|
+
pb.size = 0;
|
|
38
|
+
}
|
|
39
|
+
break;
|
|
40
|
+
|
|
41
|
+
case sf_types.ParserState.GETTING_PAYLOAD:
|
|
42
|
+
pb.data[pb.size] = c;
|
|
43
|
+
pb.size++;
|
|
44
|
+
if (pb.size >= pb.msg_id_len.len) {
|
|
45
|
+
pb.msg_data = Buffer.from(pb.data, 0, pb.size)
|
|
46
|
+
pb.state = sf_types.ParserState.LOOKING_FOR_START_BYTE;
|
|
47
|
+
if (pb.config.parser_funcs) {
|
|
48
|
+
return pb.config.parser_funcs.validate_packet(pb.data, pb.msg_id_len);
|
|
49
|
+
}
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
break;
|
|
53
|
+
|
|
54
|
+
default:
|
|
55
|
+
break;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function parse_buffer(buffer: Uint8Array, size: number, parser_result: sf_types.buffer_parser_result_t): boolean {
|
|
62
|
+
let state: sf_types.ParserState = sf_types.ParserState.LOOKING_FOR_START_BYTE;
|
|
63
|
+
let parse_func_ptr: sf_types.parser_functions_t | undefined;
|
|
64
|
+
for (let i = parser_result.r_loc; i < size; i++) {
|
|
65
|
+
switch (state) {
|
|
66
|
+
case sf_types.ParserState.LOOKING_FOR_START_BYTE:
|
|
67
|
+
parse_func_ptr = parse_char_for_start_byte(parser_result.config, buffer[i]);
|
|
68
|
+
if (parse_func_ptr) {
|
|
69
|
+
state = sf_types.ParserState.GETTING_LENGTH_MSG_AND_ID;
|
|
70
|
+
}
|
|
71
|
+
break;
|
|
72
|
+
|
|
73
|
+
case sf_types.ParserState.GETTING_LENGTH_MSG_AND_ID:
|
|
74
|
+
if (parse_func_ptr && parse_func_ptr.get_msg_id_len(buffer[i], parser_result.msg_id_len)) {
|
|
75
|
+
state = sf_types.ParserState.GETTING_PAYLOAD;
|
|
76
|
+
}
|
|
77
|
+
break;
|
|
78
|
+
|
|
79
|
+
case sf_types.ParserState.GETTING_PAYLOAD:
|
|
80
|
+
parser_result.msg_data = Buffer.from(buffer, i, (i + parser_result.msg_id_len.len));
|
|
81
|
+
parser_result.r_loc = i + parser_result.msg_id_len.len;
|
|
82
|
+
parser_result.found = true;
|
|
83
|
+
if (parse_func_ptr && parse_func_ptr.validate_packet(parser_result.msg_data, parser_result.msg_id_len)) {
|
|
84
|
+
parser_result.valid = true;
|
|
85
|
+
return true;
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
parser_result.valid = false;
|
|
89
|
+
return true;
|
|
90
|
+
}
|
|
91
|
+
break;
|
|
92
|
+
|
|
93
|
+
default:
|
|
94
|
+
break;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return false;
|
|
98
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
|
|
2
|
+
export class msg_id_len_t {
|
|
3
|
+
valid = false;
|
|
4
|
+
len = 0;
|
|
5
|
+
msg_id = 0;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export type GetMsgIdLenType = (c: number, msg_id_len: msg_id_len_t) => boolean;
|
|
9
|
+
export type ValidatePacketType = (buffer: Uint8Array, msg_id_len: msg_id_len_t) => boolean;
|
|
10
|
+
|
|
11
|
+
export interface parser_functions_t {
|
|
12
|
+
get_msg_id_len: GetMsgIdLenType;
|
|
13
|
+
validate_packet: ValidatePacketType;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface struct_frame_config {
|
|
17
|
+
has_crc: number;
|
|
18
|
+
has_len: number;
|
|
19
|
+
start_byte: number;
|
|
20
|
+
parser_funcs?: parser_functions_t;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export enum ParserState {
|
|
24
|
+
LOOKING_FOR_START_BYTE = 0,
|
|
25
|
+
GETTING_LENGTH_MSG_AND_ID = 1,
|
|
26
|
+
GETTING_PAYLOAD = 2
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export const basic_frame_config: struct_frame_config = { has_crc: 0, has_len: 0, start_byte: 0x90 };
|
|
30
|
+
|
|
31
|
+
export class struct_frame_buffer {
|
|
32
|
+
// Used for framing and parsing
|
|
33
|
+
config: struct_frame_config = basic_frame_config;
|
|
34
|
+
data: Uint8Array;
|
|
35
|
+
size = 0;
|
|
36
|
+
in_progress = false;
|
|
37
|
+
|
|
38
|
+
// Used for framing
|
|
39
|
+
crc_start_loc = 0;
|
|
40
|
+
|
|
41
|
+
// Used for parsing
|
|
42
|
+
state: ParserState = ParserState.LOOKING_FOR_START_BYTE;
|
|
43
|
+
payload_len = 0;
|
|
44
|
+
msg_id_len: msg_id_len_t = new msg_id_len_t();
|
|
45
|
+
msg_data: Buffer = Buffer.allocUnsafe(0);
|
|
46
|
+
|
|
47
|
+
constructor(public max_size: number, buffer?: Uint8Array) {
|
|
48
|
+
if (buffer) {
|
|
49
|
+
this.data = buffer;
|
|
50
|
+
} else {
|
|
51
|
+
this.data = new Uint8Array(max_size);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export class buffer_parser_result_t {
|
|
57
|
+
config: struct_frame_config = basic_frame_config;
|
|
58
|
+
found = false;
|
|
59
|
+
valid = false;
|
|
60
|
+
msg_data: Buffer = Buffer.allocUnsafe(0);
|
|
61
|
+
r_loc = 0;
|
|
62
|
+
finished = false;
|
|
63
|
+
msg_id_len: msg_id_len_t = new msg_id_len_t();
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// https://github.com/serge-sans-paille/frozen
|
|
67
|
+
// https://www.npmjs.com/package/typed-struct
|
|
68
|
+
|
|
69
|
+
//#define default_parser { 0, 0, 0x90 }
|
|
70
|
+
//
|
|
71
|
+
//#define zero_initialized_parser_result { default_parser, false, false, 0, 0, false, { 0, 0} };
|
|
72
|
+
//
|
|
73
|
+
//#define CREATE_DEFAULT_STRUCT_BUFFER(name, size) \
|
|
74
|
+
// uint8_t name##_buffer[size]; \
|
|
75
|
+
// struct_buffer name = { default_parser, name##_buffer, size, 0, 0, false, 0, 0, 0, 0, NULL }
|
|
76
|
+
|
|
77
|
+
export interface checksum_t {
|
|
78
|
+
byte1: number;
|
|
79
|
+
byte2: number;
|
|
80
|
+
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|