Skip to content

Commit 3dff18a

Browse files
committed
Add compression and decompression logic to file transfer
Signed-off-by: Simone Orru <simone.orru@secomind.com>
1 parent d60409f commit 3dff18a

34 files changed

Lines changed: 961 additions & 123 deletions

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,4 +35,5 @@ e2e/http_server_certs/192.0.2.2.pem
3535
e2e/http_server_certs/192.0.2.2.key
3636

3737
# All data in the http_server_data folder
38-
e2e/http_server_data/*_test_data.txt
38+
e2e/http_server_data/*.txt
39+
e2e/http_server_data/*.lz4

e2e/prj.conf

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
# in the application. See the README for more details.
77

88
# Increase size for heap allocation space
9-
CONFIG_COMMON_LIBC_MALLOC_ARENA_SIZE=32768
9+
CONFIG_COMMON_LIBC_MALLOC_ARENA_SIZE=262144
1010

1111
# Increase the UART RX ring buffer to handle long automated commands
1212
CONFIG_SHELL_BACKEND_SERIAL_RX_RING_BUFFER_SIZE=1024
@@ -193,3 +193,7 @@ CONFIG_UUID=y
193193
CONFIG_UUID_V4=y
194194
CONFIG_UUID_V5=y
195195
CONFIG_UUID_BASE64=y
196+
197+
# Enable LZ4 compression
198+
CONFIG_LZ4=y
199+
CONFIG_LZ4_FRAME_SUPPORT=y

e2e/pytest/file_transfer.py

Lines changed: 54 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@
55
import logging
66
import hashlib
77
import time
8-
98
import uuid
9+
import lz4.frame
1010
from pathlib import Path
1111
from datetime import datetime, timezone
1212

@@ -78,41 +78,53 @@ def _execute_and_wait_for_transfer(e2e_cfg: Configuration, interface: str, trans
7878
assert progress_received, f"No progress update received for transfer {target_id}"
7979

8080

81-
def _run_full_transfer_cycle(e2e_cfg: Configuration, transfer_type: str, device_path: str):
81+
def _run_full_transfer_cycle(e2e_cfg: Configuration, transfer_type: str, device_path: str, encoding: str = None):
8282
"""
83-
Executes a complete Server -> Device -> Server test loop.
83+
Executes a complete Server -> Device -> Server test loop with optional encoding.
8484
Generates unique content, downloads to the device, uploads back to the server,
8585
and verifies the data integrity.
8686
"""
87-
logger.info(f"Testing full file transfer loop for type: {transfer_type}")
87+
logger.info(f"Testing full file transfer loop for type: '{transfer_type}', encoding: '{encoding}'")
88+
89+
# Generate test data unique to the transfer type and encoding
90+
test_payload = f"Zephyr {transfer_type.capitalize()} Transfer Test Payload! (Encoding: {encoding}) " * 200
8891

89-
# Generate test data unique to the transfer type
90-
test_payload = f"Zephyr {transfer_type.capitalize()} Transfer Test Payload! " * 200
91-
test_bytes = test_payload.encode('utf-8')
92-
file_digest = f"sha256:{hashlib.sha256(test_bytes).hexdigest()}"
92+
# We hash the UNCOMPRESSED bytes, as device code calculates digest of decompressed chunks
93+
raw_bytes = test_payload.encode('utf-8')
94+
file_digest = f"sha256:{hashlib.sha256(raw_bytes).hexdigest()}"
9395

94-
download_filename = f"{transfer_type}_download.txt"
95-
upload_filename = f"{transfer_type}_upload.txt"
96+
enc_suffix = f"{encoding}" if encoding else "txt"
97+
download_filename = f"{transfer_type}_download.{enc_suffix}"
98+
upload_filename = f"{transfer_type}_upload.{enc_suffix}"
9699

97100
dl_id = str(uuid.uuid4())
98101
ul_id = str(uuid.uuid4())
99102

103+
# If encoding is lz4, create an lz4 compressed file for the server to serve
104+
server_download_bytes = raw_bytes
105+
if encoding == "lz4":
106+
server_download_bytes = lz4.frame.compress(raw_bytes)
107+
100108
# Write the initial file to the HTTP server's data directory
101109
download_file_path = Path(e2e_cfg.http_server_data_dir) / download_filename
102-
download_file_path.write_bytes(test_bytes)
110+
download_file_path.write_bytes(server_download_bytes)
103111

104112
# --- PHASE 1: DOWNLOAD (Server to Device) ---
105113
dl_data = {
106114
"url": f"https://192.0.2.2:8443/{download_filename}",
107115
"id": dl_id,
108116
"progress": True,
109117
"digest": file_digest,
110-
"fileSizeBytes": len(test_bytes),
118+
"fileSizeBytes": len(raw_bytes),
111119
"httpHeaderKeys": ["Content-Type"],
112-
"httpHeaderValues": ["text/plain"],
120+
"httpHeaderValues": ["application/octet-stream" if encoding else "text/plain"],
113121
"destinationType": transfer_type,
114122
"destination": device_path,
115123
}
124+
125+
if encoding:
126+
dl_data["encoding"] = encoding
127+
116128
logger.info(f"Initiating {transfer_type} download...")
117129
_execute_and_wait_for_transfer(e2e_cfg, interface_ft_server_to_device, dl_data)
118130

@@ -122,21 +134,35 @@ def _run_full_transfer_cycle(e2e_cfg: Configuration, transfer_type: str, device_
122134
"id": ul_id,
123135
"progress": True,
124136
"httpHeaderKeys": ["Content-Type"],
125-
"httpHeaderValues": ["text/plain"],
137+
"httpHeaderValues": ["application/octet-stream" if encoding else "text/plain"],
126138
"sourceType": transfer_type,
127139
"source": device_path,
128140
}
141+
142+
if encoding:
143+
ul_data["encoding"] = encoding
144+
129145
logger.info(f"Initiating {transfer_type} upload...")
130146
_execute_and_wait_for_transfer(e2e_cfg, interface_ft_device_to_server, ul_data)
131147

132148
# --- PHASE 3: VERIFICATION ---
133149
uploaded_file_path = Path(e2e_cfg.http_server_data_dir) / upload_filename
134150
assert uploaded_file_path.exists(), "The device failed to upload the file back to the server"
135151

136-
actual_payload = uploaded_file_path.read_bytes()
137-
assert actual_payload == test_bytes, f"Data corruption! Uploaded file does not match downloaded file for {transfer_type}."
152+
actual_uploaded_bytes = uploaded_file_path.read_bytes()
138153

139-
logger.info(f"Full transfer loop for '{transfer_type}' completed.")
154+
# If encoding is lz4, the device compressed the file. We need to decompress to verify.
155+
if encoding == "lz4":
156+
try:
157+
actual_payload = lz4.frame.decompress(actual_uploaded_bytes)
158+
except Exception as e:
159+
assert False, f"Failed to decompress the uploaded LZ4 file: {e}"
160+
else:
161+
actual_payload = actual_uploaded_bytes
162+
163+
assert actual_payload == raw_bytes, f"Data corruption! Uploaded file does not match downloaded file for {transfer_type} with encoding {encoding}."
164+
165+
logger.info(f"Full transfer loop for '{transfer_type}' (encoding: '{encoding}') completed.")
140166

141167

142168
def validate_file_transfer_capabilities(e2e_cfg: Configuration):
@@ -145,16 +171,22 @@ def validate_file_transfer_capabilities(e2e_cfg: Configuration):
145171
ft_res = http_get_server_data(e2e_cfg, interface_ft_capabilities)
146172

147173
assert (i in ft_res for i in ["encodings", "targets", "unixPermissions"]), "Wrong capabilities"
148-
assert ft_res.get("encodings") is None, "Wrong encodings"
174+
assert ft_res.get("encodings") == ["lz4"], "Incorrect capability encodings"
149175
assert ft_res.get("targets") == ["streaming", "filesystem"], "Wrong targets"
150176
assert not ft_res.get("unixPermissions"), "Wrong unix permissions"
151177

152178
logger.info("File transfer test (capabilities) completed successfully")
153179

154-
180+
# Raw Transfer Tests
155181
def validate_file_transfer_stream(e2e_cfg: Configuration):
156-
_run_full_transfer_cycle(e2e_cfg, transfer_type="stream", device_path="loopback")
157-
182+
_run_full_transfer_cycle(e2e_cfg, transfer_type="stream", device_path="loopback", encoding=None)
158183

159184
def validate_file_transfer_filesystem(e2e_cfg: Configuration):
160-
_run_full_transfer_cycle(e2e_cfg, transfer_type="filesystem", device_path="/lfs1/test_fs_transfer.txt")
185+
_run_full_transfer_cycle(e2e_cfg, transfer_type="filesystem", device_path="/lfs1/test_fs_transfer.txt", encoding=None)
186+
187+
# LZ4 Transfer Tests
188+
def validate_file_transfer_stream_lz4(e2e_cfg: Configuration):
189+
_run_full_transfer_cycle(e2e_cfg, transfer_type="stream", device_path="loopback", encoding="lz4")
190+
191+
def validate_file_transfer_filesystem_lz4(e2e_cfg: Configuration):
192+
_run_full_transfer_cycle(e2e_cfg, transfer_type="filesystem", device_path="/lfs1/test_fs_transfer_lz4.txt", encoding="lz4")

e2e/pytest/test_e2e.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,10 @@
1010
from configuration import Configuration
1111
from http_server import start_server, stop_server
1212
from telemetry import validate_initial_telemetry, validate_telemetry_frequency
13-
from file_transfer import is_file_transfer_enabled, validate_file_transfer_stream, validate_file_transfer_capabilities, validate_file_transfer_filesystem
13+
from file_transfer import (
14+
is_file_transfer_enabled, validate_file_transfer_stream, validate_file_transfer_capabilities,
15+
validate_file_transfer_filesystem, validate_file_transfer_stream_lz4, validate_file_transfer_filesystem_lz4
16+
)
1417

1518
logger = logging.getLogger(__name__)
1619
logging.getLogger("urllib3").setLevel(logging.INFO)
@@ -69,5 +72,7 @@ def test_file_transfer(e2e_device_env):
6972
validate_file_transfer_capabilities(cfg)
7073
validate_file_transfer_stream(cfg)
7174
validate_file_transfer_filesystem(cfg)
75+
validate_file_transfer_stream_lz4(cfg)
76+
validate_file_transfer_filesystem_lz4(cfg)
7277

7378
time.sleep(1)

e2e/testcase.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,3 +33,4 @@ tests:
3333
- ft
3434
extra_configs:
3535
- CONFIG_EDGEHOG_DEVICE_FILE_TRANSFER=y
36+
- CONFIG_EDGEHOG_DEVICE_FILE_TRANSFER_COMPRESSION=y

lib/edgehog_device/CMakeLists.txt

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
zephyr_library()
66

7-
# TODO: remove this as it's a workaroung to make available the mbedTLS crypto headers in zephyr 4.3
7+
# TODO: remove this as it's a workaround to make available the mbedTLS crypto headers in zephyr 4.3
88
zephyr_library_link_libraries(mbedTLS)
99

1010
zephyr_library_include_directories(include)
@@ -18,5 +18,12 @@ zephyr_library_sources(${lib_sources})
1818

1919
if(CONFIG_EDGEHOG_DEVICE_FILE_TRANSFER)
2020
FILE(GLOB ft_sources file_transfer/*.c)
21+
22+
# Remove the compression source file if the config is not enabled
23+
if(NOT CONFIG_EDGEHOG_DEVICE_FILE_TRANSFER_COMPRESSION)
24+
list(REMOVE_ITEM ft_sources "${CMAKE_CURRENT_SOURCE_DIR}/file_transfer/compression.c")
25+
list(REMOVE_ITEM ft_sources "${CMAKE_CURRENT_SOURCE_DIR}/file_transfer/decompression.c")
26+
endif()
27+
2128
zephyr_library_sources(${ft_sources})
2229
endif()

lib/edgehog_device/Kconfig

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ config EDGEHOG_DEVICE_FILE_TRANSFER_COMPRESSION
9797
bool "Enable file transfer compression functionality"
9898
depends on EDGEHOG_DEVICE
9999
depends on EDGEHOG_DEVICE_FILE_TRANSFER
100+
depends on LZ4
100101
default false
101102
help
102103
Enable the possibility to compress and decompress files
@@ -227,4 +228,14 @@ module-str = Log level for Edgehog device file transfer storage utilities.
227228
module-help = Sets log level for Edgehog device file transfer storage utilities.
228229
source "subsys/logging/Kconfig.template.log_config"
229230

231+
module = EDGEHOG_DEVICE_FILE_TRANSFER_DECOMPRESSION
232+
module-str = Log level for the file transfer decompression
233+
module-help = Sets log level for zephyr file transfer decompression
234+
source "subsys/logging/Kconfig.template.log_config"
235+
236+
module = EDGEHOG_DEVICE_FILE_TRANSFER_COMPRESSION
237+
module-str = Log level for the file transfer compression
238+
module-help = Sets log level for zephyr file transfer compression
239+
source "subsys/logging/Kconfig.template.log_config"
240+
230241
endmenu

lib/edgehog_device/base_image.c

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
EDGEHOG_LOG_MODULE_REGISTER(base_image, CONFIG_EDGEHOG_DEVICE_BASE_IMAGE_LOG_LEVEL);
2121

2222
/************************************************
23-
* Static functions declaration
23+
* Static functions declarations *
2424
***********************************************/
2525

2626
static void publish_fingerprint(edgehog_device_handle_t edgehog_device);
@@ -32,7 +32,7 @@ static void publish_version(edgehog_device_handle_t edgehog_device);
3232
static void publish_build_id(edgehog_device_handle_t edgehog_device);
3333

3434
/************************************************
35-
* Constants and defines
35+
* Constants and defines *
3636
***********************************************/
3737

3838
#define FINGERPRINT_PROP "/fingerprint"
@@ -41,7 +41,7 @@ static void publish_build_id(edgehog_device_handle_t edgehog_device);
4141
#define BUILD_ID_PROP "/buildId"
4242

4343
/************************************************
44-
* Global functions definition
44+
* Global functions definition *
4545
***********************************************/
4646

4747
void publish_base_image(edgehog_device_handle_t edgehog_device)
@@ -54,7 +54,7 @@ void publish_base_image(edgehog_device_handle_t edgehog_device)
5454
}
5555

5656
/************************************************
57-
* Static functions definition
57+
* Static functions definitions *
5858
***********************************************/
5959

6060
static void publish_fingerprint(edgehog_device_handle_t edgehog_device)

lib/edgehog_device/device.c

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ typedef edgehog_result_t (*datastream_obj_event_handler_cb_t)(
4444
edgehog_device_handle_t, astarte_device_datastream_object_event_t *);
4545

4646
/************************************************
47-
* Static functions declaration
47+
* Static functions declarations *
4848
***********************************************/
4949

5050
static edgehog_result_t add_interfaces(astarte_device_handle_t astarte_device);
@@ -223,7 +223,7 @@ static void astarte_property_unset_cbk(astarte_device_data_event_t event)
223223
}
224224

225225
/************************************************
226-
* Global functions definition
226+
* Global functions definition *
227227
***********************************************/
228228

229229
edgehog_result_t edgehog_device_new(
@@ -468,7 +468,7 @@ astarte_result_t edgehog_device_get_astarte_error(edgehog_device_handle_t edgeho
468468
}
469469

470470
/************************************************
471-
* Static functions definition
471+
* Static functions definitions *
472472
***********************************************/
473473

474474
static edgehog_result_t add_interfaces(astarte_device_handle_t astarte_device)

0 commit comments

Comments
 (0)