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