1import binascii
2import io
3import itertools
4from pathlib import Path
5from typing import TYPE_CHECKING, cast
6
7from structlog import get_logger
8
9from unblob.extractor import carve_chunk_to_file
10from unblob.file_utils import Endian, File, InvalidInputFormat, StructParser
11from unblob.models import (
12 Chunk,
13 Extractor,
14 HandlerDoc,
15 HandlerType,
16 HexString,
17 StructHandler,
18 ValidChunk,
19)
20
21if TYPE_CHECKING:
22 from collections.abc import Iterable
23
24logger = get_logger()
25CRC_CONTENT_OFFSET = 12
26
27TRX_V1_C_DEFINITION = r"""
28 typedef struct trx_header {
29 uint32 magic; /* "HDR0" */
30 uint32 len; /* header size + data */
31 uint32 crc32; /* 32-bit CRC from flag_version to end of file */
32 uint16 flags;
33 uint16 version;
34 uint32 offsets[3]; /* Offsets of partitions from start of header */
35 } trx_header_t;
36"""
37
38TRX_V2_C_DEFINITION = r"""
39 typedef struct trx_header {
40 uint32 magic; /* "HDR0" */
41 uint32 len; /* header size + data */
42 uint32 crc32; /* 32-bit CRC from flag_version to end of file */
43 uint16 flags;
44 uint16 version;
45 uint32 offsets[4]; /* Offsets of partitions from start of header */
46 } trx_header_t;
47"""
48
49
50class TRXExtractor(Extractor):
51 def __init__(self, c_definitions: str):
52 self._struct_parser = StructParser(c_definitions)
53
54 def extract(self, inpath: Path, outdir: Path):
55 with File.from_path(inpath) as file:
56 header = self._struct_parser.parse("trx_header_t", file, Endian.LITTLE)
57 file.seek(0, io.SEEK_END)
58 eof = file.tell()
59 offsets = sorted(
60 [
61 offset
62 for offset in [*cast("Iterable", header.offsets), eof]
63 if offset > 0
64 ]
65 )
66 for i, (start_offset, end_offset) in enumerate(itertools.pairwise(offsets)):
67 chunk = Chunk(start_offset=start_offset, end_offset=end_offset)
68 carve_chunk_to_file(outdir.joinpath(Path(f"part{i}")), file, chunk)
69
70
71class NetgearTRXBase(StructHandler):
72 HEADER_STRUCT = "trx_header_t"
73
74 def calculate_chunk(self, file: File, start_offset: int) -> ValidChunk | None:
75 header = self.parse_header(file, endian=Endian.LITTLE)
76
77 if not self.is_valid_header(header):
78 raise InvalidInputFormat("Invalid TRX header.")
79
80 if not self._is_crc_valid(file, start_offset, header):
81 raise InvalidInputFormat("Invalid CRC32.")
82
83 return ValidChunk(
84 start_offset=start_offset, end_offset=start_offset + header.len
85 )
86
87 def is_valid_header(self, header) -> bool:
88 return header.len >= len(header)
89
90 def _is_crc_valid(self, file: File, start_offset: int, header) -> bool:
91 file.seek(start_offset + CRC_CONTENT_OFFSET)
92 content = bytearray(file.read(header.len - CRC_CONTENT_OFFSET))
93 computed_crc = (binascii.crc32(content) ^ -1) & 0xFFFFFFFF
94 return header.crc32 == computed_crc
95
96
97class NetgearTRXv1Handler(NetgearTRXBase):
98 NAME = "trx_v1"
99 PATTERNS = [
100 HexString(
101 """
102 // 00000000: 4844 5230 0010 0000 33b9 d625 0000 0100 HDR0....3..%....
103 // 00000010: 1c00 0000 3000 0000 4400 0000 7361 6d70 ....0...D...
104 48 44 52 30 [10] 01 00
105 """
106 ),
107 ]
108 C_DEFINITIONS = TRX_V1_C_DEFINITION
109 EXTRACTOR = TRXExtractor(TRX_V1_C_DEFINITION)
110
111 DOC = HandlerDoc(
112 name="Netgear TRX v1",
113 description="Netgear TRX v1 firmware format includes a custom header with partition offsets and a CRC32 checksum for integrity verification. It supports up to three partitions defined in the header.",
114 handler_type=HandlerType.ARCHIVE,
115 vendor="Netgear",
116 references=[],
117 limitations=[],
118 )
119
120
121class NetgearTRXv2Handler(NetgearTRXBase):
122 NAME = "trx_v2"
123 PATTERNS = [
124 HexString(
125 """
126 // 00000000: 4844 5230 0010 0000 0a3d 4b05 0000 0200 HDR0.....=K.....
127 // 00000010: 2000 0000 3400 ffff ffff ffff ffff 0000 ...4...........
128 48 44 52 30 [10] 02 00
129 """
130 ),
131 ]
132 C_DEFINITIONS = TRX_V2_C_DEFINITION
133 EXTRACTOR = TRXExtractor(TRX_V2_C_DEFINITION)
134
135 DOC = HandlerDoc(
136 name="Netgear TRX v2",
137 description="Netgear TRX v2 firmware format includes a custom header with partition offsets and a CRC32 checksum for integrity verification. It supports up to four partitions defined in the header.",
138 handler_type=HandlerType.ARCHIVE,
139 vendor="Netgear",
140 references=[],
141 limitations=[],
142 )