* librz/type: rewrite pf format parser
Replace the legacy print-format engine in librz/type/format.c with a
clean three-stage pipeline (parse -> read -> render) and a properly
typed DSL.
The new pipeline:
rz_pf_parse(str) source string -> RzPfFormat
rz_pf_read(fmt, buf, len, ctx) RzPfFormat -> RzPfValue[]
rz_pf_render(vals, mode, opts) RzPfValue[] -> printable string
rz_pf_format() one-shot for all of the above
Public API lives in <rz_pf.h> (pulled in transitively via <rz_type.h>).
DSL improvements over the legacy scheme:
* Sized integers with explicit endianness via case:
x4 d4 u4 o4 b4 (LE) vs X4 D4 U4 O4 B4 (BE).
* Sized floats: f2 / f4 / f8 (and BE F2 / F4 / F8).
* Encoding-aware strings: z(utf8) / z(utf16le) / z(utf16be) /
z(utf32le) / z(utf32be) / z(latin1) / z(ebcdic).
* Length-prefixed strings: z[N] reads a N-byte LE prefix then body.
* Parameterized timestamps: t(unix32) / t(unix64) / t(unixms) /
t(unixus) / t(unixns) / t(filetime) / t(dos) / t(hfs) /
t(oletime) / t(webkit) / t(cocoa). ntfs alias maps to filetime.
* GUID: G(ms) (Microsoft mixed-endian, default), G(be), G(le).
* Inline bitfields: B4(FLAG_A=1,FLAG_B=2,FLAG_C=4).
* Alignment: @N advances to next multiple of N.
* Bit fields: :N with optional < (LSB-first) or > (MSB-first).
* Length-by-reference arrays: [@earlier_field]T.
* TLV records: V(t=u1,l=u2,e=le,h=v|l|a,d=table). Dispatch table
uses tlv.<name>.<hex-tag> in the typedb formats hash.
Architecture highlights:
* Per-instance ReadState (bit_cursor + sibling lookup window).
The bit cursor snap-flushes to the next byte when a non-bit
field follows mid-byte.
* Recursion bound via RzPfCtx::max_depth (default 32).
* Five render modes: text (default), json, cstruct, quiet, dot.
The dot renderer emits a column-aligned record with offset/type/
name/value rows, suitable for direct rendering through Graphviz.
* Positioned diagnostics: RzPfError carries severity, category,
column position, and a human-readable message. Each is also
emitted via RZ_LOG_WARN for backward compatibility.
* Optional RzPfPalette and RzPfRenderOpts threaded through render
for inline ANSI colorisation. NULL palette = canonical text.
* Pointer dereference via RzPfCtx::read_at callback.
The integration test expectations in test/db/cmd/cmd_pf* are
updated in the same commit to match the new text-output format and
the new DOT renderer's column-aligned record layout. Bundling these
keeps the test suite green at this commit.
A transitional rz_type_format_data() shim at the bottom of
pf_parser.c forwards legacy callers (cprint.c, disasm.c) to the new
API; it is removed in the librz/core migration commit.
* librz/arch/types: migrate type DB to new pf format codes
Map every legacy single-character format code in the bundled type
SDB files to its new-DSL equivalent:
b / C -> x1 (1-byte hex)
c -> c (signed char, unchanged)
w -> x2 (2-byte hex LE)
i -> d4 (signed 32-bit decimal LE)
d -> x4 (4-byte hex LE)
q -> x8 (8-byte hex LE)
f -> f4 (32-bit float LE)
F -> F8 (64-bit float BE)
Z -> z(utf16le)
x -> x4
o -> o4
X -> r (hexdump)
n / N -> d4 / u4
Also add new typedef-only types for explicit display formats:
bin{8,16,32,64}_t -> b{1,2,4,8} (binary)
hex{8,16,32,64}_t -> x{1,2,4,8} (hex)
oct{8,16,32,64}_t -> o{1,2,4,8} (octal)
These let users opt into a display representation per field without
changing the underlying scalar size.
Update the corresponding integration test expectations in
test/db/cmd/cmd_avg and test/db/cmd/types: the 'tu', 'tuc', 'tp',
and 'avgp' commands report formats via the SDB, so the migrated
codes appear in their text output (e.g. 'pf 0f4x4 a b' instead of
the legacy 'pf 0fd a b' for a union of float + int).
* librz/core: rewrite pf integration on the new parser API
Remove the legacy rz_type_format_data() bridge and rewrite every
caller to use the new <rz_pf.h> API directly.
librz/core/cprint.c
core_print_format() constructs an RzPfCtx populated from the
RzCore (typedb, big_endian, bits, max_depth) and calls
rz_pf_format() in one shot. The bridge between RzCore I/O and the
pf reader is cprint_pf_read_at(), which forwards to rz_io_nread_at.
The bitmask-to-RzPfMode mapping is cprint_pf_mode().
For DOT mode the format name is passed via RzPfRenderOpts::graph_label
so the dot renderer uses it as the top-level record label.
When scr.color > 0 in TEXT mode, an RzPfPalette is populated from
the active color theme via RzConsPrintablePalette so `pf` output
blends with the rest of Rizin's UI and respects user theme
choices (eco / ec). The palette slots map to theme fields as:
pf field offset <- pal->offset (address column)
pf field name <- pal->fname (symbolic name)
pf endian marker <- pal->meta (metadata tag)
pf hex/number literal <- pal->num (numeric literal)
pf typedb label <- pal->flag (resolved symbol)
reset <- pal->reset
Each slot falls back to its previous hardcoded ANSI escape if the
theme field is unset, preserving prior behaviour for stripped-down
contexts. Other modes (json/cstruct/quiet/dot) ignore the palette.
Closes rizinorg/rizin#782.
librz/core/disasm.c
RZ_META_TYPE_FORMAT case rewritten to resolve via rz_pf_resolve_name
and decode via rz_pf_format() directly. Palette is supplied from
the same theme palette when ds->show_color is true so that
pd @ <struct> blends with the surrounding listing.
librz/include/rz_type.h
Drop the rz_type_format_data() forward declaration. Callers must
use the public <rz_pf.h> surface instead.
librz/core/cmd_descs/cmd_print.yaml
Rewrite the pf command help for the new DSL: sized integers
(case-discriminated endian), special scalars (G GUID, V TLV,
: bits, @ align), strings with encodings, parameterized timestamps,
typed composites (E enum, B bitfield, ? struct), DSL extensions
(length-prefix strings, length-by-reference arrays, inline
bitfields), skip/repeat/pointers, example invocations.
cmd_descs.c is regenerated from the YAML.
* test: pf parser unit and DSL-extension integration tests
Add fresh test coverage for the new pf parser. These are purely
additive: existing integration tests were updated in the parser
and SDB-migration commits so the suite stayed green throughout.
test/unit/test_pf.c (new, 131 tests)
Field-size tables (every fixed-size type) and ctype mapping, every
parse path (sized integers / floats / strings / timestamps /
pointers / structs / enums / bitfields / GUIDs / TLV / alignment /
bits / arrays), every read path (with both fixed and length-by-
reference array counts), every render mode (text / json / cstruct /
quiet / dot), every DSL extension (align, bits MSB/LSB, GUID layout,
length-ref array, length-prefix string, inline bitfield, TLV with
dispatch table), pointer dereference via an in-memory I/O callback,
recursion safety on self-referential structs, lifecycle / NULL
safety, diagnostics (positioned errors, all categories, caret-line
formatter, source capture, verbose parse), ambiguity (each
superficially similar DSL form parses unambiguously), the palette /
colorisation API (issue #782) including a regression test surfaced
by the property-based test harness for input bytes containing ESC,
and DOT-mode coverage (column-aligned layout, single field,
empty/filtered, timestamp values, TLV records).
test/db/cmd/cmd_pf_dsl (new)
8 integration tests targeting the new DSL extensions specifically:
@N alignment, :N bits in MSB and LSB ordering, default-layout GUID,
z[N] length-prefixed string, B4(K=V) inline bitfield, bare V TLV
with defaults, V(t=u2,l=u2,e=be) configured TLV.
test/db/cmd/types_format (new)
Verifies that the new bin{N}_t / hex{N}_t / oct{N}_t typedefs apply
the expected display representation when used inside struct fields
via the 'tp' command, demonstrating the per-base rendering
(binary / decimal / octal / hex).
* doc: pf DSL reference and librz/type README
doc/pf.md (new)
User-facing reference for the pf format DSL covering quick
examples, spec grammar (sized integers with case-discriminated
endian, floats, strings with encodings and length prefixes,
parameterized timestamps, composites -- struct ?, enum E, bitfield B
inline and typed, GUID G, TLV V, raw hexdump r), repetition and
arrays ([N], [@name], {N}, leading 0 for union), padding and
alignment (. skip, @N align, :N bits MSB / LSB), pointer
dereference (*<type>), names grammar (plain vs (typename)name),
output modes (text, json, cstruct, quiet, dot), and diagnostics
(severity / category / position / caret-line formatter; backward-
compatible RZ_LOG_WARN channel).
librz/type/README.md (new)
Subsystem architecture doc for librz/type. Covers public headers,
file map, the pf three-stage pipeline (parse -> read -> render) with
a diagram and entry-point table, per-stage explanations (parser
shape, ReadState scoping rules including bit cursor and sibling
lookup window and snap-flush rule, render mode dispatch including
the DOT column-aligned layout and the graph_label parameter),
typedb integration and recursion bound, error-reporting model,
test coverage summary, and pointer to doc/pf.md as the user-facing
reference.
* librz/type: remove legacy DSL conversion shim
All in-tree callers -- librz/bin/d/, librz/bin/format/, and the
integration tests in test/db/cmd/ -- have been migrated to the new
DSL in the earlier commits of this series. The compatibility shim
that translated bare legacy specifiers (x -> x4, b -> x1, w -> x2,
nN -> uN, NN -> UN, etc.) into the new DSL at the entry of
rz_pf_parse() is no longer load-bearing; this commit deletes it.
- librz/type/pf_parser.c: drop the 267-line
convert_legacy_to_new_dsl() function, the WARN_LEGACY macro,
and the per-parse allocation + free of the converted string.
rz_pf_parse() now walks the caller's spec directly.
- test/db/cmd/cmd_pf,cmd_pf2,cmd_pf_write,cmd_pfd,cmd_pf_new,
metadata: migrate legacy specifiers in CMDS blocks to the new
DSL (b -> x1, w -> x2, q -> x8, i -> d4, bare x -> x4, etc.)
and regenerate EXPECT blocks against the new render where
affected.
The parser now only accepts the new DSL. Any caller still passing
bare legacy specifiers will get "unknown specifier" warnings.
* librz/core,type: add scr.pf.short for delta-offset rendering
When scr.pf.short is enabled, MODE_TEXT rendering shows offsets as
deltas (+<n>) from the format's base address instead of the absolute
hex address. Useful for self-contained struct dumps where the
absolute load address is noise:
$ rizin -e scr.pf.short=true -qc 'wx 00007a452a4b9a02
pf fcb1d4 a b c d' =
0 : a = 4000 [LE]
+4 : b = '*'
+5 : c = 0b0100_1011 [LE]
+6 : d = 666 [LE]
Nested structs reset their delta from the same base, so a child of
`head` at offset +8 still prints as +0 in its own struct block.
The base is derived from the offset of the first top-level value;
callers can override it via RzPfRenderOpts::base_offset.
Includes 5 integration tests in test/db/cmd/cmd_pf_short covering
basic struct, off-mode parity (control), nested struct, concatenated
TLV records, and @-offset usage.
* librz/type,test,doc: add v(N) bitvector type for forensics use
Adds a new specifier `v(N)` to the pf DSL that reads N individual
bits (1..4096) from `ceil(N/8)` bytes and exposes them as N
separate 0/1 scalars. Forensics targets: page-frame bitmaps, NTFS
$Bitmap clusters, ext4 block/inode bitmaps, ELF DT_FLAGS_1, PE
characteristics, ACL bitmasks -- anywhere you want to *see* a
bitmap rather than collapse it to a hex number.
Grammar:
v(N) N-bit bitvector, default MSB-first per byte
v(N,lsb) LSB-first per byte (Intel order)
v(N,msb) explicit MSB-first (DWARF / network order)
Rendering:
text: [ 1 0 1 0 1 0 1 1 | 1 1 0 0 ] (12-bit)
json: {"bit_width":12,"value":"101010111100"}
quiet: 1 0 1 0 1 0 1 1 1 1 0 0
Bitvec fields do not participate in the packed-bit cursor used by
:N; each v(N) reads whole bytes and stands alone. A v(N) field
adjacent to a :N field flushes the partial bit cursor first.
Width is clamped to [1, 4096] with a RANGE diagnostic. The reader
consumes exactly ceil(N/8) bytes, leaving the cursor positioned for
the next field. Verified by the existing tail-field test pattern.
Tests:
test/unit/test_pf.c +10 unit tests (146 total OK)
test/db/cmd/cmd_pf_new +9 cmd tests (440 OK / 9 BR / 0 XX / 4 FX
across all 19 pf suites)
property harness +7 properties (140 unique props, 1000
trials each, 0 failures)
Docs:
doc/pf.md bitvector section with examples
librz/type/README.md cursor-interaction note for v(N) vs :N
* librz/core,type,test: address PR CI feedback for pf rewrite
Five fixes prompted by CI feedback and reviewer comments on the pf
rewrite series:
* librz/core/cmd_descs/cmd_print.yaml: two help-text lines exceeded the
yamllint 120-char ceiling (n1/n2/n4/n8 comment and the deprecation
notes entry). Both now use the same folded-scalar (comment: >) form
already used in cmd_analysis.yaml and cmd_descs.yaml. cmd_descs.c is
regenerated.
* test/db/cmd/cmd_pf_write: drop the BROKEN 'pf xxd print' test. It
relied on '.pf*' (run pf output as rizin commands) which the new
parser no longer emits -- analogous to the '.pfw' removal done
earlier. The companion 'pf xxd print happy' test exercises the same
path through 'pf.' and stays.
* librz/type/pf_parser.c: thread the RzTypeDB through render_cstruct
and render_dot (and their inner helpers) so RZ_PF_ENUM resolution
works in pfc and pfd mode the same way it already does in text mode.
Without this, 'pfc elf_header' emitted '/* 0x00000002 */' and 'pfd'
emitted '|value|0x0000ffff|'; with it the comments and cells carry
the symbolic name (ELFCLASS64, ET_HIPROC, etc).
scalar_text() RZ_PF_BITFIELD: when no inline B4(K=V,...) flags are
present but val->type_name names a typedb enum, walk that enum and
treat each case as a settable bit name. This makes the typedb form
'B (pe_characteristics) flags' decode to
'0x00008140 : IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE | ...' instead
of just the raw hex -- previously the bitname decoding only worked
on inline B4(...) forms.
* librz/core/cprint.c: cprint_pf_mode() had no mapping for
RZ_PRINT_VALUE, so 'pfv' fell through to TEXT mode and emitted
'0x00000004 = 0x11111111 [LE]' instead of the bare value '0x11111111'
expected by db/esil/arm_16 and existing pfv users. Added an explicit
RZ_PRINT_VALUE -> RZ_PF_MODE_QUIET mapping; QUIET already emits one
bare scalar per field, which is exactly the pfv contract.
* test EXPECTs: cmd_pfd unbreaks its bitfield case (also fixes the
data buffer -- the original used 'wx 0x00008140' which is parsed
byte-by-byte and yields LE u32 0x40810000, not 0x00008140, so no
flags matched). cmd_pf 'PE test' picks up the now-decoded
characteristics and dllCharacteristics bitfields. cmd_pf 'Print
value only' updates to the bare-value pfv contract. test/db/cmd/print
'elf 64bit ls pfc.elf_header' picks up the decoded enum symbols.
Also picks up clang-format-20 reflows in cprint.c, cmd_print.c,
pf_parser.c, and rz_pf.h that the CI's clang-format check flagged.
Also bumps the offset-delta buffer in render_val_text from 16 to 24
bytes so a worst-case ut64 decimal (20 digits + sign + NUL = 22) fits
without tripping -Werror=format-truncation on gcc with -O0.
Also fixes three swapped-argument mu_assert calls in test_pf.c (the
v(N) bitvector tests added in commit 8): the macro signature is
mu_assert(message, test) but those three calls passed (test, message),
which tripped -Werror=format= under -O3 because the int 'test' was
fed to the format string's %s slot.
Adds a second round of regression fixes prompted by a manual audit
against pristine upstream (review feedback after the first pass):
* render_quiet: handle raw byte-sequential types (RZ_PF_UINT128,
RZ_PF_HEXDUMP). scalar_text() has no per-byte case for these so the
default-clause '?' was emitted, making 'pfq Q' and 'pfv Q' print '?'.
render_quiet now mirrors text mode and emits a space-separated hex
stream for raw types.
* Sized pointers (p{2,4,8}): the legacy parser accepted 'p2', 'p4',
'p8' to force a 16/32/64-bit pointer width regardless of ctx.bits;
the new parser only handled bare 'p'. Restored via a new dispatch
branch ahead of bare 'p', recording the override in
RzPfField::bit_width (overloaded -- documented in rz_pf.h). A new
fld_ptr_size() helper consults the override first; read_ptr, the
pointer-deref read path, the STRPTR read path, and
pf_struct_size_impl all use it. 'pf p2p4p8pp2' now consumes
2+4+8+4+2=20 bytes and renders the right value at each position.
* Numeric pointer dereference: '*d4', '*x2', '*u8', etc. (pointer to
fixed-size scalar) only read the pointer itself and never followed
it. The reader now also calls ctx->read_at to fetch the target word
and populate scalars[0]; the text renderer prints both the pointer
literal and the dereferenced value -- '(*0x20) 42' instead of bare
'(*0x20)'. Affects 'pf *d4 ...' and the 'Pointers' / '32 bit twice
then string' tests.
* Pointer-to-struct dereference (*?): pointer-to-struct fields read
the pointer value but stopped there, so 'pf *?' (or typedb forms like
'(troll)Bah' marked with '*') showed only '(*0x30)'. The reader now
recurses through ctx->read_at into a worst-case 4 KiB buffer and
re-invokes read_nested_struct; the renderer prints the nested struct
body after the '(*ptr)' annotation. Recursion is bounded by
ctx->max_depth so cyclic Bah->Bah pointer chains terminate at the
documented limit instead of unbounded recursion. Affects 'nested
struct', 'complex nested struct', and 'flag for nested struct'.
* String pointer (bare 's'): RZ_PF_STRPTR rendering didn't show the
dereferenced target -- output was bare '"hello"' with no pointer
context. Now emits '(*ptr) "string"' (mirroring '*z') when the
deref produced a non-empty body; falls back to bare '""' on
unmapped or empty targets to avoid advertising a phantom pointer.
* render_val_text is_pointer branch: extended to render the nested
struct body for *? pointers and the dereferenced value for numeric
*d4/*x2/*u8 forms.
* Top-level read loop: rz_pf_read passed both 'cur_off + off' (as
read_field's off parameter) AND 'base_addr + cur_off' (as base_addr),
but read_field computes 'val->offset = base_addr + off'. This
double-added cur_off on every iteration past the first, so 'pf 2ic'
reading 5 bytes per iter reported iter 1's nb at offset 0x0a instead
of 0x05, and 'pf 2F' reported iter 1's double at offset 0x10 instead
of 0x08. Data reads were correct; only the displayed/JSON offsets
drifted. The fix: pass base_addr unchanged so val->offset becomes
base_addr + (cur_off + off). Affects 'array obj', 'print n-times a
format', 'pf', 'pf field name', 'JSON output'.
* Bare 'C' (legacy 1-byte unsigned decimal): the legacy parser accepted
'C' as 'print byte as decimal'; the new parser had dropped it. The
'types' test format 'pf fcb1d4C foo bar fool beer plop' was therefore
truncating to 4 fields. Restored as a deprecated alias for 'u1' (with
the standard 'use u1' note).
* pfw dotted-path navigation: the write-mode lookup only matched
top-level field names, so 'pfw gobelin.Buh.first=42' through nested
structs failed with 'field not found'. Replaced with a segment-by-
segment walker that descends through children at each dot. Affects
'write specific element through nested struct'.
* Drop '.pfw' usage from tests. '.pfw' (execute pfw output as rizin
commands) was a legacy convention -- the new pfw writes directly via
rz_io_write_at and emits a human-readable confirmation line, so the
'.' prefix evaluates that confirmation line as a command and fails.
Same direction as the earlier '.pf*' removal. All cmd_pf,
cmd_pf_write, cmd_pf2 tests updated.
* test/db/cmd/cmd_pf 'Register' marked BROKEN with a tracking comment:
the legacy 'r (regname)' looked up CPU registers via
RzPrint::get_register; the new parser repurposes 'r' as raw hex byte
dump and the register-fetch path is gone. Restoring would mean
wiring a new register-lookup hook through RzPfCtx, which is outside
the parser-rewrite scope.
* test/db/cmd/cmd_pf2 'pf F max precision (#13027)' annotated: not a
precision regression. Values differ from the original #13027 expected
output because the legacy 'F' specifier read bytes as LE-then-
reinterpret while the new parser follows the documented 'UPPERCASE
= BE' rule literally; the 17-digit precision -- the actual subject
of #13027 -- is preserved.
EXPECTs updated to match the corrected output across cmd_pf, cmd_pf2,
cmd_pf_write.
* doc/pf.md updated to reflect all DSL and rendering changes:
documents the sized pointers (p/p2/p4/p8), pointer dereference
semantics (numeric, struct, and string), the missing composites
(Q, U, L, n/N), the case-as-endian rule (no standalone endian
directive), the pfw write mode (dotted-path navigation, direct I/O,
no '.pfw'/'.pf*'), and the table of deprecated single-letter
aliases (b, d, o, q, u, i, f, F, w, Z, t, T, X, C).
* librz/type/pf_render.c (new): extracted the render-mode code paths
from pf_parser.c into their own translation unit. Hosts the text /
quiet / JSON / cstruct / DOT renderers, the per-mode helpers
(scalar_text, scalar_json, render_binary, render_guid,
compute_name_width, field_matches, emit_colored*), the RenderCtx
bag, and the public rz_pf_render() + rz_pf_render_json() entry
points. pf_parser.c keeps the parse + read pipeline and shrinks
from 4831 to 3280 lines. Helpers used by both translation units
(is_string_type, is_raw_type, endian_str, pf_vasprintf) move to
pf_parser.h as static inlines.
* librz/type/pf/ (new directory): moved every pf source out of the
librz/type/ top level into a dedicated pf/ subdirectory; only the
legacy format.c stays top-level. meson.build updated with the new
paths and the pf/ include dir.
* Split the monolithic parser into focused sub-grammar TUs, wired
together by the new pf/pf_internal.h (which centralizes the PF_DIAG
diagnostic macro, the shared ReadState, and all cross-TU
declarations):
- pf_parser_string.c string/encoding specs (z / s / Z)
- pf_parser_bitfield.c inline + typed bitfields
- pf_parser_bitvec.c bitvectors v(N)
- pf_parser_array.c array-count resolution
- pf_parser_struct.c nested struct / union reading
pf_parser.c shrinks from 3280 to ~2620 lines and now hosts only the
parse driver, type-spec dispatcher, reader core, context, and the
public utility surface. The TLV TU was migrated off its bespoke
extern/TLV_DIAG block onto the shared header.
* Add an RzStructuredData renderer: pf/pf_render_sd.c with the public
rz_pf_render_sd() (declared in rz_pf.h). (Named _sd, not _sdb: SDB is
Rizin's key/value database, unrelated to RzStructuredData.) It maps the decoded value
vector to the generic key/value document model -- scalars to typed
entries, arrays/bitvectors to arrays, nested structs to sub-maps
with a _type tag, raw/GUID payloads to byte blocks, timestamps to a
formatted string plus a <name>_raw sibling -- so callers get JSON,
YAML, or iterator access for free. Unit and property tests cover it
(see the consistency-pass note below).
* doc/pf.md and librz/type/README.md updated for the new pf/ layout
and the structured-data render mode.
* Consistency pass over librz/type/pf/: deduplicated the structured-data
scalar dispatch (one classifier feeding thin map/array emitters instead
of two parallel ~60-line switches), dropped unused includes
(pf_parser_time.h from the TLV TU, string.h from the struct TU,
rz_endian.h from the SD renderer) and a redundant extern decl now
covered by pf_internal.h, and fixed a dangling-pointer bug in the SD
char path (the classifier returned a pointer into its own by-value
temporary). Expanded coverage: 11 SD unit tests (157 total) and 6 new
theft properties (SD tree non-NULL, JSON structural well-formedness,
top-level object shape, YAML safety, determinism, filter-narrows).
* Split pf_render.c (1548 lines) into per-mode renderer TUs, mirroring
the earlier pf_render_sd.c extraction: pf_render_text.c (text+quiet),
pf_render_json.c (+rz_pf_render_json), pf_render_cstruct.c, and
pf_render_dot.c. A new pf_render.h carries the shared RenderCtx record
plus the cross-TU helper (pf_field_matches, pf_scalar_text,
pf_render_guid) and per-mode entry-point declarations. pf_render.c now
holds only those shared helpers and the rz_pf_render() dispatcher
(~372 lines). meson.build, the file-doc layout listings, and
librz/type/README.md are updated accordingly.
* Second consistency pass: ensured every public RZ_API entry point has a
Doxygen \brief block (added ones for rz_type_format_struct_size and
rz_pf_render_sd) and every file a \file description (added to
pf_parser.h and pf_parser_time.h). Deduplicated the field-size logic:
pf_struct_size_impl's sum and union loops now share one
pf_field_static_size() helper, the enum/bitfield byte-width override is
a single pf_enum_bitfield_width() helper (was inlined 3x), sized-pointer
width routes through the existing fld_ptr_size(), and the inline-bitflag
test shared by the JSON and DOT renderers became the
pf_value_has_inline_bitflags() predicate in pf_render.h. Dropped unused
includes left over from the render split. No behaviour change (157 unit
tests, 146 theft properties, full cmd sweep all green).
* Fix a CodeQL High alert (cpp/integer-multiplication-cast-to-long) in
the scalar-array path selector: the per-element byte delta was computed
as `idx * width` in 32-bit int before being added to the ut64 offset,
so a large array index could overflow int prior to the widening. The
multiplication is now done in ut64 ((ut64)idx * width). No behaviour
change for in-range indices; verified by the path-selection cmd tests.
* Fix a big-endian bug in the structured-data renderer: the bitvector
reader stores each bit in the RzPfScalar union's v_u8 member, but the
SD classifier read it back through v_u64. On little-endian those alias
to the same low byte so it worked by luck; on big-endian (s390x) v_u8
is the high byte of the 64-bit slot, so every set bit read as a large
number and the test_pf_render_sd_bitvec assertion failed. The
classifier now reads v_u8 for RZ_PF_BITVEC, matching the writer; the
other scalar types already read the same width the reader wrote.
* Clean up leftover refactoring-era comments in the renderer TUs: drop
the "Split out of pf_render.c" changelog phrasing from the file-doc
headers (the docs now describe the current layout) and update two
stale references to the pre-rename helper names (field_matches ->
pf_field_matches, render_guid -> pf_render_guid) in comments.
---------
Co-authored-by: Anton Kochkov <anton.kochkov@gmail.com>
3176 lines
115 KiB
C
3176 lines
115 KiB
C
// SPDX-FileCopyrightText: 2026 Anton Kochkov <anton.kochkov@gmail.com>
|
|
// SPDX-License-Identifier: LGPL-3.0-only
|
|
|
|
/**
|
|
* \file test_pf.c
|
|
* \brief Unit tests for the pf format parser, reader, and renderers.
|
|
*
|
|
* Covers the public RZ_API surface of pf_parser.h and pf_parser_time.h:
|
|
* field sizing, ctype mapping, every parse path (sized ints, floats,
|
|
* strings with encodings, timestamps with all wire formats, repeat,
|
|
* union, pointer and array prefixes, struct/enum/bitfield typedb
|
|
* references), the reader pipeline against in-memory buffers, the
|
|
* timestamp decoders and the new DSL extensions.
|
|
*
|
|
* Tests use the minunit harness (test/unit/minunit.h).
|
|
*/
|
|
|
|
#include <rz_util.h>
|
|
#include "../../librz/type/pf/pf_parser.h"
|
|
#include "../../librz/type/pf/pf_parser_time.h"
|
|
#include "minunit.h"
|
|
|
|
/* --------------------------------------------------------------------
|
|
* Helper: simple in-memory read callback for rz_pf_read tests
|
|
* -------------------------------------------------------------------- */
|
|
typedef struct {
|
|
const ut8 *data;
|
|
int len;
|
|
} MemBuf;
|
|
|
|
static int mem_read_at(void *user, ut64 addr, ut8 *buf, int len) {
|
|
MemBuf *m = (MemBuf *)user;
|
|
if (!m || (int)addr >= m->len) {
|
|
return 0;
|
|
}
|
|
int avail = m->len - (int)addr;
|
|
int n = (len < avail) ? len : avail;
|
|
memcpy(buf, m->data + addr, n);
|
|
return n;
|
|
}
|
|
|
|
/* --------------------------------------------------------------------
|
|
* 1. rz_pf_field_size -- byte sizes for every fixed-size type
|
|
* -------------------------------------------------------------------- */
|
|
static bool test_pf_field_size_1byte(void) {
|
|
mu_assert_eq(rz_pf_field_size(RZ_PF_HEX8), 1, "HEX8 size");
|
|
mu_assert_eq(rz_pf_field_size(RZ_PF_DEC_S8), 1, "DEC_S8 size");
|
|
mu_assert_eq(rz_pf_field_size(RZ_PF_DEC_U8), 1, "DEC_U8 size");
|
|
mu_assert_eq(rz_pf_field_size(RZ_PF_OCT8), 1, "OCT8 size");
|
|
mu_assert_eq(rz_pf_field_size(RZ_PF_BIN8), 1, "BIN8 size");
|
|
mu_assert_eq(rz_pf_field_size(RZ_PF_CHAR), 1, "CHAR size");
|
|
mu_assert_eq(rz_pf_field_size(RZ_PF_SKIP), 1, "SKIP size");
|
|
mu_end;
|
|
}
|
|
|
|
static bool test_pf_field_size_2byte(void) {
|
|
mu_assert_eq(rz_pf_field_size(RZ_PF_HEX16), 2, "HEX16 size");
|
|
mu_assert_eq(rz_pf_field_size(RZ_PF_DEC_S16), 2, "DEC_S16 size");
|
|
mu_assert_eq(rz_pf_field_size(RZ_PF_DEC_U16), 2, "DEC_U16 size");
|
|
mu_assert_eq(rz_pf_field_size(RZ_PF_OCT16), 2, "OCT16 size");
|
|
mu_assert_eq(rz_pf_field_size(RZ_PF_BIN16), 2, "BIN16 size");
|
|
mu_assert_eq(rz_pf_field_size(RZ_PF_FLOAT16), 2, "FLOAT16 size");
|
|
mu_end;
|
|
}
|
|
|
|
static bool test_pf_field_size_4byte(void) {
|
|
mu_assert_eq(rz_pf_field_size(RZ_PF_HEX32), 4, "HEX32 size");
|
|
mu_assert_eq(rz_pf_field_size(RZ_PF_DEC_S32), 4, "DEC_S32 size");
|
|
mu_assert_eq(rz_pf_field_size(RZ_PF_DEC_U32), 4, "DEC_U32 size");
|
|
mu_assert_eq(rz_pf_field_size(RZ_PF_OCT32), 4, "OCT32 size");
|
|
mu_assert_eq(rz_pf_field_size(RZ_PF_BIN32), 4, "BIN32 size");
|
|
mu_assert_eq(rz_pf_field_size(RZ_PF_FLOAT32), 4, "FLOAT32 size");
|
|
mu_assert_eq(rz_pf_field_size(RZ_PF_ENUM), 4, "ENUM size");
|
|
mu_assert_eq(rz_pf_field_size(RZ_PF_BITFIELD), 4, "BITFIELD size");
|
|
mu_end;
|
|
}
|
|
|
|
static bool test_pf_field_size_8byte(void) {
|
|
mu_assert_eq(rz_pf_field_size(RZ_PF_HEX64), 8, "HEX64 size");
|
|
mu_assert_eq(rz_pf_field_size(RZ_PF_DEC_S64), 8, "DEC_S64 size");
|
|
mu_assert_eq(rz_pf_field_size(RZ_PF_DEC_U64), 8, "DEC_U64 size");
|
|
mu_assert_eq(rz_pf_field_size(RZ_PF_OCT64), 8, "OCT64 size");
|
|
mu_assert_eq(rz_pf_field_size(RZ_PF_BIN64), 8, "BIN64 size");
|
|
mu_assert_eq(rz_pf_field_size(RZ_PF_FLOAT64), 8, "FLOAT64 size");
|
|
mu_end;
|
|
}
|
|
|
|
static bool test_pf_field_size_special(void) {
|
|
mu_assert_eq(rz_pf_field_size(RZ_PF_UINT128), 16, "UINT128 size");
|
|
mu_assert_eq(rz_pf_field_size(RZ_PF_TIMESTAMP), -1, "TIMESTAMP size is variable");
|
|
mu_assert_eq(rz_pf_field_size(RZ_PF_ZSTRING), -1, "ZSTRING size is variable");
|
|
mu_assert_eq(rz_pf_field_size(RZ_PF_STRPTR), -1, "STRPTR size is variable");
|
|
mu_assert_eq(rz_pf_field_size(RZ_PF_POINTER), -1, "POINTER size is variable");
|
|
mu_assert_eq(rz_pf_field_size(RZ_PF_HEXDUMP), -1, "HEXDUMP size is variable");
|
|
mu_assert_eq(rz_pf_field_size(RZ_PF_ULEB128), -1, "ULEB128 size is variable");
|
|
mu_assert_eq(rz_pf_field_size(RZ_PF_SLEB128), -1, "SLEB128 size is variable");
|
|
mu_end;
|
|
}
|
|
|
|
/* --------------------------------------------------------------------
|
|
* 2. rz_pf_field_ctype -- C type string for each field type
|
|
* -------------------------------------------------------------------- */
|
|
static bool test_pf_field_ctype_unsigned(void) {
|
|
mu_assert_streq(rz_pf_field_ctype(RZ_PF_HEX8), "uint8_t", "HEX8 ctype");
|
|
mu_assert_streq(rz_pf_field_ctype(RZ_PF_DEC_U8), "uint8_t", "DEC_U8 ctype");
|
|
mu_assert_streq(rz_pf_field_ctype(RZ_PF_OCT8), "uint8_t", "OCT8 ctype");
|
|
mu_assert_streq(rz_pf_field_ctype(RZ_PF_BIN8), "uint8_t", "BIN8 ctype");
|
|
mu_assert_streq(rz_pf_field_ctype(RZ_PF_HEX16), "uint16_t", "HEX16 ctype");
|
|
mu_assert_streq(rz_pf_field_ctype(RZ_PF_DEC_U16), "uint16_t", "DEC_U16 ctype");
|
|
mu_assert_streq(rz_pf_field_ctype(RZ_PF_HEX32), "uint32_t", "HEX32 ctype");
|
|
mu_assert_streq(rz_pf_field_ctype(RZ_PF_DEC_U32), "uint32_t", "DEC_U32 ctype");
|
|
mu_assert_streq(rz_pf_field_ctype(RZ_PF_HEX64), "uint64_t", "HEX64 ctype");
|
|
mu_assert_streq(rz_pf_field_ctype(RZ_PF_DEC_U64), "uint64_t", "DEC_U64 ctype");
|
|
mu_assert_streq(rz_pf_field_ctype(RZ_PF_UINT128), "uint128_t", "UINT128 ctype");
|
|
mu_end;
|
|
}
|
|
|
|
static bool test_pf_field_ctype_signed(void) {
|
|
mu_assert_streq(rz_pf_field_ctype(RZ_PF_DEC_S8), "int8_t", "DEC_S8 ctype");
|
|
mu_assert_streq(rz_pf_field_ctype(RZ_PF_CHAR), "int8_t", "CHAR ctype");
|
|
mu_assert_streq(rz_pf_field_ctype(RZ_PF_DEC_S16), "int16_t", "DEC_S16 ctype");
|
|
mu_assert_streq(rz_pf_field_ctype(RZ_PF_DEC_S32), "int32_t", "DEC_S32 ctype");
|
|
mu_assert_streq(rz_pf_field_ctype(RZ_PF_DEC_S64), "int64_t", "DEC_S64 ctype");
|
|
mu_end;
|
|
}
|
|
|
|
static bool test_pf_field_ctype_float(void) {
|
|
mu_assert_streq(rz_pf_field_ctype(RZ_PF_FLOAT16), "_Float16", "FLOAT16 ctype");
|
|
mu_assert_streq(rz_pf_field_ctype(RZ_PF_FLOAT32), "float", "FLOAT32 ctype");
|
|
mu_assert_streq(rz_pf_field_ctype(RZ_PF_FLOAT64), "double", "FLOAT64 ctype");
|
|
mu_end;
|
|
}
|
|
|
|
static bool test_pf_field_ctype_misc(void) {
|
|
mu_assert_streq(rz_pf_field_ctype(RZ_PF_ZSTRING), "char*", "ZSTRING ctype");
|
|
mu_assert_streq(rz_pf_field_ctype(RZ_PF_STRPTR), "char*", "STRPTR ctype");
|
|
mu_assert_streq(rz_pf_field_ctype(RZ_PF_POINTER), "void*", "POINTER ctype");
|
|
mu_assert_streq(rz_pf_field_ctype(RZ_PF_HEXDUMP), "uint8_t[]", "HEXDUMP ctype");
|
|
mu_assert_streq(rz_pf_field_ctype(RZ_PF_ULEB128), "leb128_t", "ULEB128 ctype");
|
|
mu_assert_streq(rz_pf_field_ctype(RZ_PF_SLEB128), "leb128_t", "SLEB128 ctype");
|
|
mu_assert_streq(rz_pf_field_ctype(RZ_PF_ENUM), "enum", "ENUM ctype");
|
|
mu_assert_streq(rz_pf_field_ctype(RZ_PF_BITFIELD), "bitfield", "BITFIELD ctype");
|
|
mu_assert_streq(rz_pf_field_ctype(RZ_PF_SKIP), "unknown", "SKIP ctype");
|
|
mu_end;
|
|
}
|
|
|
|
/* --------------------------------------------------------------------
|
|
* 3. rz_pf_enc_null_unit_size -- null-terminator width per encoding
|
|
* -------------------------------------------------------------------- */
|
|
static bool test_pf_enc_null_unit_size(void) {
|
|
mu_assert_eq(rz_pf_enc_null_unit_size(RZ_STRING_ENC_UTF8), 1, "UTF8 null unit");
|
|
mu_assert_eq(rz_pf_enc_null_unit_size(RZ_STRING_ENC_8BIT), 1, "8BIT null unit");
|
|
mu_assert_eq(rz_pf_enc_null_unit_size(RZ_STRING_ENC_UTF16LE), 2, "UTF16LE null unit");
|
|
mu_assert_eq(rz_pf_enc_null_unit_size(RZ_STRING_ENC_UTF16BE), 2, "UTF16BE null unit");
|
|
mu_assert_eq(rz_pf_enc_null_unit_size(RZ_STRING_ENC_UTF32LE), 4, "UTF32LE null unit");
|
|
mu_assert_eq(rz_pf_enc_null_unit_size(RZ_STRING_ENC_UTF32BE), 4, "UTF32BE null unit");
|
|
mu_end;
|
|
}
|
|
|
|
/* --------------------------------------------------------------------
|
|
* 4. Timestamp format utilities
|
|
* -------------------------------------------------------------------- */
|
|
static bool test_pf_timefmt_from_string(void) {
|
|
mu_assert_eq(rz_pf_timefmt_from_string("unix32"), RZ_PF_TIMEFMT_UNIX32, "unix32");
|
|
mu_assert_eq(rz_pf_timefmt_from_string("unix64"), RZ_PF_TIMEFMT_UNIX64, "unix64");
|
|
mu_assert_eq(rz_pf_timefmt_from_string("unixms"), RZ_PF_TIMEFMT_UNIXMS, "unixms");
|
|
mu_assert_eq(rz_pf_timefmt_from_string("unixus"), RZ_PF_TIMEFMT_UNIXUS, "unixus");
|
|
mu_assert_eq(rz_pf_timefmt_from_string("unixns"), RZ_PF_TIMEFMT_UNIXNS, "unixns");
|
|
mu_assert_eq(rz_pf_timefmt_from_string("filetime"), RZ_PF_TIMEFMT_FILETIME, "filetime");
|
|
mu_assert_eq(rz_pf_timefmt_from_string("ntfs"), RZ_PF_TIMEFMT_FILETIME, "ntfs alias");
|
|
mu_assert_eq(rz_pf_timefmt_from_string("dos"), RZ_PF_TIMEFMT_DOS, "dos");
|
|
mu_assert_eq(rz_pf_timefmt_from_string("hfs"), RZ_PF_TIMEFMT_HFS, "hfs");
|
|
mu_assert_eq(rz_pf_timefmt_from_string("oletime"), RZ_PF_TIMEFMT_OLETIME, "oletime");
|
|
mu_assert_eq(rz_pf_timefmt_from_string("webkit"), RZ_PF_TIMEFMT_WEBKIT, "webkit");
|
|
mu_assert_eq(rz_pf_timefmt_from_string("cocoa"), RZ_PF_TIMEFMT_COCOA, "cocoa");
|
|
mu_end;
|
|
}
|
|
|
|
static bool test_pf_timefmt_from_string_case_insensitive(void) {
|
|
mu_assert_eq(rz_pf_timefmt_from_string("UNIX32"), RZ_PF_TIMEFMT_UNIX32, "UNIX32 upper");
|
|
mu_assert_eq(rz_pf_timefmt_from_string("FileTime"), RZ_PF_TIMEFMT_FILETIME, "FileTime mixed");
|
|
mu_assert_eq(rz_pf_timefmt_from_string("DOS"), RZ_PF_TIMEFMT_DOS, "DOS upper");
|
|
mu_end;
|
|
}
|
|
|
|
static bool test_pf_timefmt_from_string_unknown(void) {
|
|
/* Unknown or empty defaults to UNIX32 */
|
|
mu_assert_eq(rz_pf_timefmt_from_string(""), RZ_PF_TIMEFMT_UNIX32, "empty -> unix32");
|
|
mu_assert_eq(rz_pf_timefmt_from_string(NULL), RZ_PF_TIMEFMT_UNIX32, "NULL -> unix32");
|
|
mu_assert_eq(rz_pf_timefmt_from_string("bogus"), RZ_PF_TIMEFMT_UNIX32, "bogus -> unix32");
|
|
mu_end;
|
|
}
|
|
|
|
static bool test_pf_timefmt_as_string(void) {
|
|
mu_assert_streq(rz_pf_timefmt_as_string(RZ_PF_TIMEFMT_UNIX32), "unix32", "unix32 str");
|
|
mu_assert_streq(rz_pf_timefmt_as_string(RZ_PF_TIMEFMT_UNIX64), "unix64", "unix64 str");
|
|
mu_assert_streq(rz_pf_timefmt_as_string(RZ_PF_TIMEFMT_UNIXMS), "unixms", "unixms str");
|
|
mu_assert_streq(rz_pf_timefmt_as_string(RZ_PF_TIMEFMT_UNIXUS), "unixus", "unixus str");
|
|
mu_assert_streq(rz_pf_timefmt_as_string(RZ_PF_TIMEFMT_UNIXNS), "unixns", "unixns str");
|
|
mu_assert_streq(rz_pf_timefmt_as_string(RZ_PF_TIMEFMT_FILETIME), "filetime", "filetime str");
|
|
mu_assert_streq(rz_pf_timefmt_as_string(RZ_PF_TIMEFMT_DOS), "dos", "dos str");
|
|
mu_assert_streq(rz_pf_timefmt_as_string(RZ_PF_TIMEFMT_HFS), "hfs", "hfs str");
|
|
mu_assert_streq(rz_pf_timefmt_as_string(RZ_PF_TIMEFMT_OLETIME), "oletime", "oletime str");
|
|
mu_assert_streq(rz_pf_timefmt_as_string(RZ_PF_TIMEFMT_WEBKIT), "webkit", "webkit str");
|
|
mu_assert_streq(rz_pf_timefmt_as_string(RZ_PF_TIMEFMT_COCOA), "cocoa", "cocoa str");
|
|
mu_end;
|
|
}
|
|
|
|
static bool test_pf_timefmt_size(void) {
|
|
mu_assert_eq(rz_pf_timefmt_size(RZ_PF_TIMEFMT_UNIX32), 4, "unix32 4B");
|
|
mu_assert_eq(rz_pf_timefmt_size(RZ_PF_TIMEFMT_DOS), 4, "dos 4B");
|
|
mu_assert_eq(rz_pf_timefmt_size(RZ_PF_TIMEFMT_HFS), 4, "hfs 4B");
|
|
mu_assert_eq(rz_pf_timefmt_size(RZ_PF_TIMEFMT_UNIX64), 8, "unix64 8B");
|
|
mu_assert_eq(rz_pf_timefmt_size(RZ_PF_TIMEFMT_UNIXMS), 8, "unixms 8B");
|
|
mu_assert_eq(rz_pf_timefmt_size(RZ_PF_TIMEFMT_UNIXUS), 8, "unixus 8B");
|
|
mu_assert_eq(rz_pf_timefmt_size(RZ_PF_TIMEFMT_UNIXNS), 8, "unixns 8B");
|
|
mu_assert_eq(rz_pf_timefmt_size(RZ_PF_TIMEFMT_FILETIME), 8, "filetime 8B");
|
|
mu_assert_eq(rz_pf_timefmt_size(RZ_PF_TIMEFMT_OLETIME), 8, "oletime 8B");
|
|
mu_assert_eq(rz_pf_timefmt_size(RZ_PF_TIMEFMT_WEBKIT), 8, "webkit 8B");
|
|
mu_assert_eq(rz_pf_timefmt_size(RZ_PF_TIMEFMT_COCOA), 8, "cocoa 8B");
|
|
mu_end;
|
|
}
|
|
|
|
static bool test_pf_timefmt_is_float(void) {
|
|
mu_assert_true(rz_pf_timefmt_is_float(RZ_PF_TIMEFMT_OLETIME), "oletime is float");
|
|
mu_assert_true(rz_pf_timefmt_is_float(RZ_PF_TIMEFMT_COCOA), "cocoa is float");
|
|
mu_assert_false(rz_pf_timefmt_is_float(RZ_PF_TIMEFMT_UNIX32), "unix32 not float");
|
|
mu_assert_false(rz_pf_timefmt_is_float(RZ_PF_TIMEFMT_UNIX64), "unix64 not float");
|
|
mu_assert_false(rz_pf_timefmt_is_float(RZ_PF_TIMEFMT_FILETIME), "filetime not float");
|
|
mu_assert_false(rz_pf_timefmt_is_float(RZ_PF_TIMEFMT_DOS), "dos not float");
|
|
mu_assert_false(rz_pf_timefmt_is_float(RZ_PF_TIMEFMT_HFS), "hfs not float");
|
|
mu_assert_false(rz_pf_timefmt_is_float(RZ_PF_TIMEFMT_WEBKIT), "webkit not float");
|
|
mu_end;
|
|
}
|
|
|
|
/* --------------------------------------------------------------------
|
|
* 5. rz_pf_parse -- NULL / empty input
|
|
* -------------------------------------------------------------------- */
|
|
static bool test_pf_parse_null(void) {
|
|
RzPfFormat *fmt = rz_pf_parse(NULL);
|
|
mu_assert_null(fmt, "NULL input returns NULL");
|
|
/* Empty input returns an empty format (zero fields, zero
|
|
* diagnostics), not NULL, so callers don't have to special-
|
|
* case the empty string. See test_pf_parse_empty_input below. */
|
|
fmt = rz_pf_parse("");
|
|
mu_assert_notnull(fmt, "empty input returns empty format, not NULL");
|
|
mu_assert_eq(fmt->nfields, 0, "empty format has zero fields");
|
|
rz_pf_format_free(fmt);
|
|
mu_end;
|
|
}
|
|
|
|
/* --------------------------------------------------------------------
|
|
* 6. Parse: sized hex integers (LE)
|
|
* -------------------------------------------------------------------- */
|
|
static bool test_pf_parse_hex_le(void) {
|
|
RzPfFormat *fmt = rz_pf_parse("x1x2x4x8");
|
|
mu_assert_notnull(fmt, "parse hex LE");
|
|
mu_assert_eq(fmt->nfields, 4, "4 hex fields");
|
|
mu_assert_eq(fmt->fields[0].type, RZ_PF_HEX8, "x1 -> HEX8");
|
|
mu_assert_eq(fmt->fields[0].endian, RZ_PF_ENDIAN_LE, "x1 LE");
|
|
mu_assert_eq(fmt->fields[1].type, RZ_PF_HEX16, "x2 -> HEX16");
|
|
mu_assert_eq(fmt->fields[1].endian, RZ_PF_ENDIAN_LE, "x2 LE");
|
|
mu_assert_eq(fmt->fields[2].type, RZ_PF_HEX32, "x4 -> HEX32");
|
|
mu_assert_eq(fmt->fields[2].endian, RZ_PF_ENDIAN_LE, "x4 LE");
|
|
mu_assert_eq(fmt->fields[3].type, RZ_PF_HEX64, "x8 -> HEX64");
|
|
mu_assert_eq(fmt->fields[3].endian, RZ_PF_ENDIAN_LE, "x8 LE");
|
|
rz_pf_format_free(fmt);
|
|
mu_end;
|
|
}
|
|
|
|
/* --------------------------------------------------------------------
|
|
* 7. Parse: sized hex integers (BE)
|
|
* -------------------------------------------------------------------- */
|
|
static bool test_pf_parse_hex_be(void) {
|
|
RzPfFormat *fmt = rz_pf_parse("X1X2X4X8");
|
|
mu_assert_notnull(fmt, "parse hex BE");
|
|
mu_assert_eq(fmt->nfields, 4, "4 hex BE fields");
|
|
mu_assert_eq(fmt->fields[0].type, RZ_PF_HEX8, "X1 -> HEX8");
|
|
mu_assert_eq(fmt->fields[0].endian, RZ_PF_ENDIAN_BE, "X1 BE");
|
|
mu_assert_eq(fmt->fields[1].type, RZ_PF_HEX16, "X2 -> HEX16");
|
|
mu_assert_eq(fmt->fields[1].endian, RZ_PF_ENDIAN_BE, "X2 BE");
|
|
mu_assert_eq(fmt->fields[2].type, RZ_PF_HEX32, "X4 -> HEX32");
|
|
mu_assert_eq(fmt->fields[2].endian, RZ_PF_ENDIAN_BE, "X4 BE");
|
|
mu_assert_eq(fmt->fields[3].type, RZ_PF_HEX64, "X8 -> HEX64");
|
|
mu_assert_eq(fmt->fields[3].endian, RZ_PF_ENDIAN_BE, "X8 BE");
|
|
rz_pf_format_free(fmt);
|
|
mu_end;
|
|
}
|
|
|
|
/* --------------------------------------------------------------------
|
|
* 8. Parse: signed decimal (LE + BE)
|
|
* -------------------------------------------------------------------- */
|
|
static bool test_pf_parse_dec_signed(void) {
|
|
RzPfFormat *fmt = rz_pf_parse("d1d2d4d8D1D2D4D8");
|
|
mu_assert_notnull(fmt, "parse signed dec");
|
|
mu_assert_eq(fmt->nfields, 8, "8 signed dec fields");
|
|
|
|
mu_assert_eq(fmt->fields[0].type, RZ_PF_DEC_S8, "d1");
|
|
mu_assert_eq(fmt->fields[0].endian, RZ_PF_ENDIAN_LE, "d1 LE");
|
|
mu_assert_eq(fmt->fields[1].type, RZ_PF_DEC_S16, "d2");
|
|
mu_assert_eq(fmt->fields[2].type, RZ_PF_DEC_S32, "d4");
|
|
mu_assert_eq(fmt->fields[3].type, RZ_PF_DEC_S64, "d8");
|
|
|
|
mu_assert_eq(fmt->fields[4].type, RZ_PF_DEC_S8, "D1");
|
|
mu_assert_eq(fmt->fields[4].endian, RZ_PF_ENDIAN_BE, "D1 BE");
|
|
mu_assert_eq(fmt->fields[5].type, RZ_PF_DEC_S16, "D2");
|
|
mu_assert_eq(fmt->fields[5].endian, RZ_PF_ENDIAN_BE, "D2 BE");
|
|
mu_assert_eq(fmt->fields[6].type, RZ_PF_DEC_S32, "D4");
|
|
mu_assert_eq(fmt->fields[7].type, RZ_PF_DEC_S64, "D8");
|
|
|
|
rz_pf_format_free(fmt);
|
|
mu_end;
|
|
}
|
|
|
|
/* --------------------------------------------------------------------
|
|
* 9. Parse: unsigned decimal (LE + BE)
|
|
* -------------------------------------------------------------------- */
|
|
static bool test_pf_parse_dec_unsigned(void) {
|
|
RzPfFormat *fmt = rz_pf_parse("u1u2u4u8");
|
|
mu_assert_notnull(fmt, "parse unsigned dec");
|
|
mu_assert_eq(fmt->nfields, 4, "4 unsigned dec fields");
|
|
mu_assert_eq(fmt->fields[0].type, RZ_PF_DEC_U8, "u1");
|
|
mu_assert_eq(fmt->fields[0].endian, RZ_PF_ENDIAN_LE, "u1 LE");
|
|
mu_assert_eq(fmt->fields[1].type, RZ_PF_DEC_U16, "u2");
|
|
mu_assert_eq(fmt->fields[2].type, RZ_PF_DEC_U32, "u4");
|
|
mu_assert_eq(fmt->fields[3].type, RZ_PF_DEC_U64, "u8");
|
|
rz_pf_format_free(fmt);
|
|
|
|
/* BE variants */
|
|
fmt = rz_pf_parse("U1U2U4U8");
|
|
mu_assert_notnull(fmt, "parse unsigned dec BE");
|
|
mu_assert_eq(fmt->nfields, 4, "4 unsigned dec BE fields");
|
|
mu_assert_eq(fmt->fields[0].type, RZ_PF_DEC_U8, "U1");
|
|
mu_assert_eq(fmt->fields[0].endian, RZ_PF_ENDIAN_BE, "U1 BE");
|
|
mu_assert_eq(fmt->fields[1].endian, RZ_PF_ENDIAN_BE, "U2 BE");
|
|
mu_assert_eq(fmt->fields[2].endian, RZ_PF_ENDIAN_BE, "U4 BE");
|
|
mu_assert_eq(fmt->fields[3].endian, RZ_PF_ENDIAN_BE, "U8 BE");
|
|
rz_pf_format_free(fmt);
|
|
mu_end;
|
|
}
|
|
|
|
/* --------------------------------------------------------------------
|
|
* 10. Parse: octal (LE + BE)
|
|
* -------------------------------------------------------------------- */
|
|
static bool test_pf_parse_octal(void) {
|
|
RzPfFormat *fmt = rz_pf_parse("o1o2o4o8O1O2O4O8");
|
|
mu_assert_notnull(fmt, "parse octal");
|
|
mu_assert_eq(fmt->nfields, 8, "8 octal fields");
|
|
mu_assert_eq(fmt->fields[0].type, RZ_PF_OCT8, "o1");
|
|
mu_assert_eq(fmt->fields[0].endian, RZ_PF_ENDIAN_LE, "o1 LE");
|
|
mu_assert_eq(fmt->fields[1].type, RZ_PF_OCT16, "o2");
|
|
mu_assert_eq(fmt->fields[2].type, RZ_PF_OCT32, "o4");
|
|
mu_assert_eq(fmt->fields[3].type, RZ_PF_OCT64, "o8");
|
|
mu_assert_eq(fmt->fields[4].type, RZ_PF_OCT8, "O1");
|
|
mu_assert_eq(fmt->fields[4].endian, RZ_PF_ENDIAN_BE, "O1 BE");
|
|
mu_assert_eq(fmt->fields[5].type, RZ_PF_OCT16, "O2");
|
|
mu_assert_eq(fmt->fields[6].type, RZ_PF_OCT32, "O4");
|
|
mu_assert_eq(fmt->fields[7].type, RZ_PF_OCT64, "O8");
|
|
rz_pf_format_free(fmt);
|
|
mu_end;
|
|
}
|
|
|
|
/* --------------------------------------------------------------------
|
|
* 11. Parse: binary representation (LE + BE)
|
|
* -------------------------------------------------------------------- */
|
|
static bool test_pf_parse_binary(void) {
|
|
RzPfFormat *fmt = rz_pf_parse("b1b2b4b8B1B2B4B8");
|
|
mu_assert_notnull(fmt, "parse binary");
|
|
mu_assert_eq(fmt->nfields, 8, "8 binary fields");
|
|
mu_assert_eq(fmt->fields[0].type, RZ_PF_BIN8, "b1");
|
|
mu_assert_eq(fmt->fields[0].endian, RZ_PF_ENDIAN_LE, "b1 LE");
|
|
mu_assert_eq(fmt->fields[1].type, RZ_PF_BIN16, "b2");
|
|
mu_assert_eq(fmt->fields[2].type, RZ_PF_BIN32, "b4");
|
|
mu_assert_eq(fmt->fields[3].type, RZ_PF_BIN64, "b8");
|
|
mu_assert_eq(fmt->fields[4].type, RZ_PF_BIN8, "B1");
|
|
mu_assert_eq(fmt->fields[4].endian, RZ_PF_ENDIAN_BE, "B1 BE");
|
|
mu_assert_eq(fmt->fields[5].type, RZ_PF_BIN16, "B2");
|
|
mu_assert_eq(fmt->fields[6].type, RZ_PF_BIN32, "B4");
|
|
mu_assert_eq(fmt->fields[7].type, RZ_PF_BIN64, "B8");
|
|
rz_pf_format_free(fmt);
|
|
mu_end;
|
|
}
|
|
|
|
/* --------------------------------------------------------------------
|
|
* 12. Parse: sized floats (LE + BE)
|
|
* -------------------------------------------------------------------- */
|
|
static bool test_pf_parse_float_sized(void) {
|
|
RzPfFormat *fmt = rz_pf_parse("f2f4f8F2F4F8");
|
|
mu_assert_notnull(fmt, "parse sized floats");
|
|
mu_assert_eq(fmt->nfields, 6, "6 float fields");
|
|
|
|
mu_assert_eq(fmt->fields[0].type, RZ_PF_FLOAT16, "f2 -> FLOAT16");
|
|
mu_assert_eq(fmt->fields[0].endian, RZ_PF_ENDIAN_LE, "f2 LE");
|
|
mu_assert_eq(fmt->fields[1].type, RZ_PF_FLOAT32, "f4 -> FLOAT32");
|
|
mu_assert_eq(fmt->fields[1].endian, RZ_PF_ENDIAN_LE, "f4 LE");
|
|
mu_assert_eq(fmt->fields[2].type, RZ_PF_FLOAT64, "f8 -> FLOAT64");
|
|
mu_assert_eq(fmt->fields[2].endian, RZ_PF_ENDIAN_LE, "f8 LE");
|
|
|
|
mu_assert_eq(fmt->fields[3].type, RZ_PF_FLOAT16, "F2 -> FLOAT16");
|
|
mu_assert_eq(fmt->fields[3].endian, RZ_PF_ENDIAN_BE, "F2 BE");
|
|
mu_assert_eq(fmt->fields[4].type, RZ_PF_FLOAT32, "F4 -> FLOAT32");
|
|
mu_assert_eq(fmt->fields[4].endian, RZ_PF_ENDIAN_BE, "F4 BE");
|
|
mu_assert_eq(fmt->fields[5].type, RZ_PF_FLOAT64, "F8 -> FLOAT64");
|
|
mu_assert_eq(fmt->fields[5].endian, RZ_PF_ENDIAN_BE, "F8 BE");
|
|
|
|
rz_pf_format_free(fmt);
|
|
mu_end;
|
|
}
|
|
|
|
/* --------------------------------------------------------------------
|
|
* 13. Parse: bare deprecated 'f' and 'F'
|
|
* -------------------------------------------------------------------- */
|
|
static bool test_pf_parse_float_bare_deprecated(void) {
|
|
/* Bare 'f' -> FLOAT32 LE (deprecated) */
|
|
RzPfFormat *fmt = rz_pf_parse("f");
|
|
mu_assert_notnull(fmt, "parse bare f");
|
|
mu_assert_eq(fmt->nfields, 1, "1 field");
|
|
mu_assert_eq(fmt->fields[0].type, RZ_PF_FLOAT32, "bare f -> FLOAT32");
|
|
mu_assert_eq(fmt->fields[0].endian, RZ_PF_ENDIAN_LE, "bare f LE");
|
|
rz_pf_format_free(fmt);
|
|
|
|
/* Bare 'F' -> FLOAT64 BE (deprecated) */
|
|
fmt = rz_pf_parse("F");
|
|
mu_assert_notnull(fmt, "parse bare F");
|
|
mu_assert_eq(fmt->nfields, 1, "1 field");
|
|
mu_assert_eq(fmt->fields[0].type, RZ_PF_FLOAT64, "bare F -> FLOAT64");
|
|
mu_assert_eq(fmt->fields[0].endian, RZ_PF_ENDIAN_BE, "bare F BE");
|
|
rz_pf_format_free(fmt);
|
|
mu_end;
|
|
}
|
|
|
|
/* --------------------------------------------------------------------
|
|
* 14. Parse: single-char specifiers (c, p, Q, r, U, L, E, B, ?, .)
|
|
* -------------------------------------------------------------------- */
|
|
static bool test_pf_parse_single_char_specifiers(void) {
|
|
RzPfFormat *fmt = rz_pf_parse("cpQrULE.?B");
|
|
mu_assert_notnull(fmt, "parse single-char");
|
|
/* '.' is SKIP and consumes no name, but still a field */
|
|
mu_assert_eq(fmt->nfields, 10, "10 fields");
|
|
|
|
mu_assert_eq(fmt->fields[0].type, RZ_PF_CHAR, "c -> CHAR");
|
|
mu_assert_eq(fmt->fields[0].endian, RZ_PF_ENDIAN_CTX, "c CTX");
|
|
|
|
mu_assert_eq(fmt->fields[1].type, RZ_PF_POINTER, "p -> POINTER");
|
|
mu_assert_eq(fmt->fields[1].endian, RZ_PF_ENDIAN_CTX, "p CTX");
|
|
|
|
mu_assert_eq(fmt->fields[2].type, RZ_PF_UINT128, "Q -> UINT128");
|
|
mu_assert_eq(fmt->fields[2].endian, RZ_PF_ENDIAN_CTX, "Q CTX");
|
|
|
|
mu_assert_eq(fmt->fields[3].type, RZ_PF_HEXDUMP, "r -> HEXDUMP");
|
|
mu_assert_eq(fmt->fields[3].endian, RZ_PF_ENDIAN_CTX, "r CTX");
|
|
|
|
mu_assert_eq(fmt->fields[4].type, RZ_PF_ULEB128, "U -> ULEB128");
|
|
mu_assert_eq(fmt->fields[4].endian, RZ_PF_ENDIAN_CTX, "U CTX");
|
|
|
|
mu_assert_eq(fmt->fields[5].type, RZ_PF_SLEB128, "L -> SLEB128");
|
|
mu_assert_eq(fmt->fields[5].endian, RZ_PF_ENDIAN_CTX, "L CTX");
|
|
|
|
mu_assert_eq(fmt->fields[6].type, RZ_PF_ENUM, "E -> ENUM");
|
|
mu_assert_eq(fmt->fields[6].endian, RZ_PF_ENDIAN_CTX, "E CTX");
|
|
|
|
mu_assert_eq(fmt->fields[7].type, RZ_PF_SKIP, ". -> SKIP");
|
|
mu_assert_eq(fmt->fields[7].endian, RZ_PF_ENDIAN_CTX, ". CTX");
|
|
|
|
mu_assert_eq(fmt->fields[8].type, RZ_PF_STRUCT, "? -> STRUCT");
|
|
mu_assert_eq(fmt->fields[8].endian, RZ_PF_ENDIAN_CTX, "? CTX");
|
|
|
|
mu_assert_eq(fmt->fields[9].type, RZ_PF_BITFIELD, "bare B -> BITFIELD");
|
|
mu_assert_eq(fmt->fields[9].endian, RZ_PF_ENDIAN_CTX, "B CTX");
|
|
|
|
rz_pf_format_free(fmt);
|
|
mu_end;
|
|
}
|
|
|
|
/* --------------------------------------------------------------------
|
|
* 15. Parse: strings -- z and s (default UTF-8)
|
|
* -------------------------------------------------------------------- */
|
|
static bool test_pf_parse_string_default(void) {
|
|
RzPfFormat *fmt = rz_pf_parse("zs");
|
|
mu_assert_notnull(fmt, "parse z s");
|
|
mu_assert_eq(fmt->nfields, 2, "2 string fields");
|
|
|
|
mu_assert_eq(fmt->fields[0].type, RZ_PF_ZSTRING, "z -> ZSTRING");
|
|
mu_assert_eq(fmt->fields[0].encoding, RZ_STRING_ENC_UTF8, "z default UTF8");
|
|
mu_assert_eq(fmt->fields[0].endian, RZ_PF_ENDIAN_CTX, "z CTX");
|
|
|
|
mu_assert_eq(fmt->fields[1].type, RZ_PF_STRPTR, "s -> STRPTR");
|
|
mu_assert_eq(fmt->fields[1].encoding, RZ_STRING_ENC_UTF8, "s default UTF8");
|
|
mu_assert_eq(fmt->fields[1].endian, RZ_PF_ENDIAN_CTX, "s CTX");
|
|
|
|
rz_pf_format_free(fmt);
|
|
mu_end;
|
|
}
|
|
|
|
/* --------------------------------------------------------------------
|
|
* 16. Parse: strings with encoding annotation
|
|
* -------------------------------------------------------------------- */
|
|
static bool test_pf_parse_string_encoding(void) {
|
|
RzPfFormat *fmt = rz_pf_parse("z(utf16le)s(utf32be)");
|
|
mu_assert_notnull(fmt, "parse string encodings");
|
|
mu_assert_eq(fmt->nfields, 2, "2 fields");
|
|
|
|
mu_assert_eq(fmt->fields[0].type, RZ_PF_ZSTRING, "z(...) -> ZSTRING");
|
|
mu_assert_eq(fmt->fields[0].encoding, RZ_STRING_ENC_UTF16LE, "z(utf16le)");
|
|
|
|
mu_assert_eq(fmt->fields[1].type, RZ_PF_STRPTR, "s(...) -> STRPTR");
|
|
mu_assert_eq(fmt->fields[1].encoding, RZ_STRING_ENC_UTF32BE, "s(utf32be)");
|
|
|
|
rz_pf_format_free(fmt);
|
|
mu_end;
|
|
}
|
|
|
|
/* --------------------------------------------------------------------
|
|
* 17. Parse: deprecated 'Z' -> ZSTRING with UTF16LE
|
|
* -------------------------------------------------------------------- */
|
|
static bool test_pf_parse_deprecated_Z(void) {
|
|
RzPfFormat *fmt = rz_pf_parse("Z");
|
|
mu_assert_notnull(fmt, "parse deprecated Z");
|
|
mu_assert_eq(fmt->nfields, 1, "1 field");
|
|
mu_assert_eq(fmt->fields[0].type, RZ_PF_ZSTRING, "Z -> ZSTRING");
|
|
mu_assert_eq(fmt->fields[0].encoding, RZ_STRING_ENC_UTF16LE, "Z -> UTF16LE");
|
|
rz_pf_format_free(fmt);
|
|
mu_end;
|
|
}
|
|
|
|
/* --------------------------------------------------------------------
|
|
* 18. Parse: deprecated 'w' -> HEX16 LE
|
|
* -------------------------------------------------------------------- */
|
|
static bool test_pf_parse_deprecated_w(void) {
|
|
RzPfFormat *fmt = rz_pf_parse("w");
|
|
mu_assert_notnull(fmt, "parse deprecated w");
|
|
mu_assert_eq(fmt->nfields, 1, "1 field");
|
|
mu_assert_eq(fmt->fields[0].type, RZ_PF_HEX16, "w -> HEX16");
|
|
mu_assert_eq(fmt->fields[0].endian, RZ_PF_ENDIAN_LE, "w LE");
|
|
rz_pf_format_free(fmt);
|
|
mu_end;
|
|
}
|
|
|
|
/* --------------------------------------------------------------------
|
|
* 19. Parse: timestamps -- t(name) and T(name)
|
|
* -------------------------------------------------------------------- */
|
|
static bool test_pf_parse_timestamp_named(void) {
|
|
RzPfFormat *fmt = rz_pf_parse("t(unix32)T(filetime)t(dos)t(hfs)");
|
|
mu_assert_notnull(fmt, "parse named timestamps");
|
|
mu_assert_eq(fmt->nfields, 4, "4 timestamp fields");
|
|
|
|
mu_assert_eq(fmt->fields[0].type, RZ_PF_TIMESTAMP, "t(unix32) type");
|
|
mu_assert_eq(fmt->fields[0].timefmt, RZ_PF_TIMEFMT_UNIX32, "t(unix32) fmt");
|
|
mu_assert_eq(fmt->fields[0].endian, RZ_PF_ENDIAN_LE, "t LE");
|
|
|
|
mu_assert_eq(fmt->fields[1].type, RZ_PF_TIMESTAMP, "T(filetime) type");
|
|
mu_assert_eq(fmt->fields[1].timefmt, RZ_PF_TIMEFMT_FILETIME, "T(filetime) fmt");
|
|
mu_assert_eq(fmt->fields[1].endian, RZ_PF_ENDIAN_BE, "T BE");
|
|
|
|
mu_assert_eq(fmt->fields[2].timefmt, RZ_PF_TIMEFMT_DOS, "t(dos) fmt");
|
|
mu_assert_eq(fmt->fields[3].timefmt, RZ_PF_TIMEFMT_HFS, "t(hfs) fmt");
|
|
|
|
rz_pf_format_free(fmt);
|
|
mu_end;
|
|
}
|
|
|
|
static bool test_pf_parse_timestamp_all_formats(void) {
|
|
RzPfFormat *fmt = rz_pf_parse(
|
|
"t(unix32)t(unix64)t(unixms)t(unixus)t(unixns)"
|
|
"t(filetime)t(ntfs)t(dos)t(hfs)t(oletime)t(webkit)t(cocoa)");
|
|
mu_assert_notnull(fmt, "parse all timestamp formats");
|
|
mu_assert_eq(fmt->nfields, 12, "12 timestamp fields");
|
|
|
|
mu_assert_eq(fmt->fields[0].timefmt, RZ_PF_TIMEFMT_UNIX32, "unix32");
|
|
mu_assert_eq(fmt->fields[1].timefmt, RZ_PF_TIMEFMT_UNIX64, "unix64");
|
|
mu_assert_eq(fmt->fields[2].timefmt, RZ_PF_TIMEFMT_UNIXMS, "unixms");
|
|
mu_assert_eq(fmt->fields[3].timefmt, RZ_PF_TIMEFMT_UNIXUS, "unixus");
|
|
mu_assert_eq(fmt->fields[4].timefmt, RZ_PF_TIMEFMT_UNIXNS, "unixns");
|
|
mu_assert_eq(fmt->fields[5].timefmt, RZ_PF_TIMEFMT_FILETIME, "filetime");
|
|
mu_assert_eq(fmt->fields[6].timefmt, RZ_PF_TIMEFMT_FILETIME, "ntfs alias");
|
|
mu_assert_eq(fmt->fields[7].timefmt, RZ_PF_TIMEFMT_DOS, "dos");
|
|
mu_assert_eq(fmt->fields[8].timefmt, RZ_PF_TIMEFMT_HFS, "hfs");
|
|
mu_assert_eq(fmt->fields[9].timefmt, RZ_PF_TIMEFMT_OLETIME, "oletime");
|
|
mu_assert_eq(fmt->fields[10].timefmt, RZ_PF_TIMEFMT_WEBKIT, "webkit");
|
|
mu_assert_eq(fmt->fields[11].timefmt, RZ_PF_TIMEFMT_COCOA, "cocoa");
|
|
|
|
rz_pf_format_free(fmt);
|
|
mu_end;
|
|
}
|
|
|
|
/* --------------------------------------------------------------------
|
|
* 20. Parse: deprecated timestamp forms (t4, t8, bare t/T)
|
|
* -------------------------------------------------------------------- */
|
|
static bool test_pf_parse_timestamp_deprecated(void) {
|
|
/* t4 -> unix32 LE */
|
|
RzPfFormat *fmt = rz_pf_parse("t4");
|
|
mu_assert_notnull(fmt, "parse t4");
|
|
mu_assert_eq(fmt->nfields, 1, "1 field");
|
|
mu_assert_eq(fmt->fields[0].type, RZ_PF_TIMESTAMP, "t4 type");
|
|
mu_assert_eq(fmt->fields[0].timefmt, RZ_PF_TIMEFMT_UNIX32, "t4 -> unix32");
|
|
mu_assert_eq(fmt->fields[0].endian, RZ_PF_ENDIAN_LE, "t4 LE");
|
|
rz_pf_format_free(fmt);
|
|
|
|
/* t8 -> unix64 LE */
|
|
fmt = rz_pf_parse("t8");
|
|
mu_assert_notnull(fmt, "parse t8");
|
|
mu_assert_eq(fmt->fields[0].timefmt, RZ_PF_TIMEFMT_UNIX64, "t8 -> unix64");
|
|
rz_pf_format_free(fmt);
|
|
|
|
/* T4 -> unix32 BE */
|
|
fmt = rz_pf_parse("T4");
|
|
mu_assert_notnull(fmt, "parse T4");
|
|
mu_assert_eq(fmt->fields[0].timefmt, RZ_PF_TIMEFMT_UNIX32, "T4 -> unix32");
|
|
mu_assert_eq(fmt->fields[0].endian, RZ_PF_ENDIAN_BE, "T4 BE");
|
|
rz_pf_format_free(fmt);
|
|
|
|
/* T8 -> unix64 BE */
|
|
fmt = rz_pf_parse("T8");
|
|
mu_assert_notnull(fmt, "parse T8");
|
|
mu_assert_eq(fmt->fields[0].timefmt, RZ_PF_TIMEFMT_UNIX64, "T8 -> unix64");
|
|
mu_assert_eq(fmt->fields[0].endian, RZ_PF_ENDIAN_BE, "T8 BE");
|
|
rz_pf_format_free(fmt);
|
|
|
|
/* bare t -> unix32 LE */
|
|
fmt = rz_pf_parse("t");
|
|
mu_assert_notnull(fmt, "parse bare t");
|
|
mu_assert_eq(fmt->fields[0].timefmt, RZ_PF_TIMEFMT_UNIX32, "bare t -> unix32");
|
|
mu_assert_eq(fmt->fields[0].endian, RZ_PF_ENDIAN_LE, "bare t LE");
|
|
rz_pf_format_free(fmt);
|
|
|
|
/* bare T -> unix32 BE */
|
|
fmt = rz_pf_parse("T");
|
|
mu_assert_notnull(fmt, "parse bare T");
|
|
mu_assert_eq(fmt->fields[0].timefmt, RZ_PF_TIMEFMT_UNIX32, "bare T -> unix32");
|
|
mu_assert_eq(fmt->fields[0].endian, RZ_PF_ENDIAN_BE, "bare T BE");
|
|
rz_pf_format_free(fmt);
|
|
mu_end;
|
|
}
|
|
|
|
/* --------------------------------------------------------------------
|
|
* 21. Parse: repeat count (leading digit)
|
|
* -------------------------------------------------------------------- */
|
|
static bool test_pf_parse_repeat_digit(void) {
|
|
RzPfFormat *fmt = rz_pf_parse("5x4");
|
|
mu_assert_notnull(fmt, "parse 5x4");
|
|
mu_assert_eq(fmt->repeat, 5, "repeat = 5");
|
|
mu_assert_eq(fmt->nfields, 1, "1 field");
|
|
mu_assert_eq(fmt->fields[0].type, RZ_PF_HEX32, "x4");
|
|
mu_assert_false(fmt->is_union, "not union");
|
|
rz_pf_format_free(fmt);
|
|
mu_end;
|
|
}
|
|
|
|
/* --------------------------------------------------------------------
|
|
* 22. Parse: repeat count ({N} form)
|
|
* -------------------------------------------------------------------- */
|
|
static bool test_pf_parse_repeat_brace(void) {
|
|
RzPfFormat *fmt = rz_pf_parse("{10}x4");
|
|
mu_assert_notnull(fmt, "parse {10}x4");
|
|
mu_assert_eq(fmt->repeat, 10, "repeat = 10");
|
|
mu_assert_eq(fmt->nfields, 1, "1 field");
|
|
rz_pf_format_free(fmt);
|
|
mu_end;
|
|
}
|
|
|
|
/* --------------------------------------------------------------------
|
|
* 23. Parse: default repeat = 1
|
|
* -------------------------------------------------------------------- */
|
|
static bool test_pf_parse_repeat_default(void) {
|
|
RzPfFormat *fmt = rz_pf_parse("x4");
|
|
mu_assert_notnull(fmt, "parse x4 no repeat");
|
|
mu_assert_eq(fmt->repeat, 1, "default repeat = 1");
|
|
rz_pf_format_free(fmt);
|
|
mu_end;
|
|
}
|
|
|
|
/* --------------------------------------------------------------------
|
|
* 24. Parse: union flag (leading '0')
|
|
* -------------------------------------------------------------------- */
|
|
static bool test_pf_parse_union(void) {
|
|
RzPfFormat *fmt = rz_pf_parse("0x4d4");
|
|
mu_assert_notnull(fmt, "parse union");
|
|
mu_assert_true(fmt->is_union, "is_union set");
|
|
mu_assert_eq(fmt->nfields, 2, "2 fields");
|
|
mu_assert_eq(fmt->fields[0].type, RZ_PF_HEX32, "first field x4");
|
|
mu_assert_eq(fmt->fields[1].type, RZ_PF_DEC_S32, "second field d4");
|
|
rz_pf_format_free(fmt);
|
|
mu_end;
|
|
}
|
|
|
|
static bool test_pf_parse_no_union(void) {
|
|
RzPfFormat *fmt = rz_pf_parse("x4d4");
|
|
mu_assert_notnull(fmt, "parse no union");
|
|
mu_assert_false(fmt->is_union, "not union");
|
|
rz_pf_format_free(fmt);
|
|
mu_end;
|
|
}
|
|
|
|
/* --------------------------------------------------------------------
|
|
* 25. Parse: pointer prefix (*)
|
|
* -------------------------------------------------------------------- */
|
|
static bool test_pf_parse_pointer_prefix(void) {
|
|
RzPfFormat *fmt = rz_pf_parse("*x4x2");
|
|
mu_assert_notnull(fmt, "parse pointer prefix");
|
|
mu_assert_eq(fmt->nfields, 2, "2 fields");
|
|
mu_assert_true(fmt->fields[0].is_pointer, "field[0] is_pointer");
|
|
mu_assert_eq(fmt->fields[0].type, RZ_PF_HEX32, "field[0] HEX32");
|
|
mu_assert_false(fmt->fields[1].is_pointer, "field[1] not pointer");
|
|
mu_assert_eq(fmt->fields[1].type, RZ_PF_HEX16, "field[1] HEX16");
|
|
rz_pf_format_free(fmt);
|
|
mu_end;
|
|
}
|
|
|
|
/* --------------------------------------------------------------------
|
|
* 26. Parse: array prefix [N]
|
|
* -------------------------------------------------------------------- */
|
|
static bool test_pf_parse_array_prefix(void) {
|
|
RzPfFormat *fmt = rz_pf_parse("[4]x1[16]u1");
|
|
mu_assert_notnull(fmt, "parse array prefix");
|
|
mu_assert_eq(fmt->nfields, 2, "2 fields");
|
|
mu_assert_eq(fmt->fields[0].array_count, 4, "first array 4");
|
|
mu_assert_eq(fmt->fields[0].type, RZ_PF_HEX8, "first HEX8");
|
|
mu_assert_eq(fmt->fields[1].array_count, 16, "second array 16");
|
|
mu_assert_eq(fmt->fields[1].type, RZ_PF_DEC_U8, "second DEC_U8");
|
|
rz_pf_format_free(fmt);
|
|
mu_end;
|
|
}
|
|
|
|
static bool test_pf_parse_scalar_no_array(void) {
|
|
RzPfFormat *fmt = rz_pf_parse("x4");
|
|
mu_assert_notnull(fmt, "parse scalar");
|
|
mu_assert_eq(fmt->fields[0].array_count, -1, "scalar array_count = -1");
|
|
rz_pf_format_free(fmt);
|
|
mu_end;
|
|
}
|
|
|
|
/* --------------------------------------------------------------------
|
|
* 27. Parse: combined pointer + array
|
|
* -------------------------------------------------------------------- */
|
|
static bool test_pf_parse_pointer_array(void) {
|
|
RzPfFormat *fmt = rz_pf_parse("*[8]x4");
|
|
mu_assert_notnull(fmt, "parse *[8]x4");
|
|
mu_assert_eq(fmt->nfields, 1, "1 field");
|
|
mu_assert_true(fmt->fields[0].is_pointer, "is_pointer");
|
|
mu_assert_eq(fmt->fields[0].array_count, 8, "array 8");
|
|
mu_assert_eq(fmt->fields[0].type, RZ_PF_HEX32, "HEX32");
|
|
rz_pf_format_free(fmt);
|
|
mu_end;
|
|
}
|
|
|
|
/* --------------------------------------------------------------------
|
|
* 28. Parse: field names
|
|
* -------------------------------------------------------------------- */
|
|
static bool test_pf_parse_field_names(void) {
|
|
RzPfFormat *fmt = rz_pf_parse("x4d4 magic version");
|
|
mu_assert_notnull(fmt, "parse with names");
|
|
mu_assert_eq(fmt->nfields, 2, "2 fields");
|
|
mu_assert_notnull(fmt->fields[0].name, "field[0] name set");
|
|
mu_assert_streq(fmt->fields[0].name, "magic", "field[0] = magic");
|
|
mu_assert_notnull(fmt->fields[1].name, "field[1] name set");
|
|
mu_assert_streq(fmt->fields[1].name, "version", "field[1] = version");
|
|
rz_pf_format_free(fmt);
|
|
mu_end;
|
|
}
|
|
|
|
static bool test_pf_parse_skip_no_name(void) {
|
|
/* '.' fields do not consume a name */
|
|
RzPfFormat *fmt = rz_pf_parse("x4.d4 magic value");
|
|
mu_assert_notnull(fmt, "parse skip + names");
|
|
mu_assert_eq(fmt->nfields, 3, "3 fields");
|
|
mu_assert_streq(fmt->fields[0].name, "magic", "magic");
|
|
mu_assert_null(fmt->fields[1].name, "skip has no name");
|
|
mu_assert_streq(fmt->fields[2].name, "value", "value");
|
|
rz_pf_format_free(fmt);
|
|
mu_end;
|
|
}
|
|
|
|
/* --------------------------------------------------------------------
|
|
* 29. Parse: struct/enum/bitfield type_name via annotated name
|
|
* -------------------------------------------------------------------- */
|
|
static bool test_pf_parse_struct_type_name(void) {
|
|
RzPfFormat *fmt = rz_pf_parse("? (my_struct)child");
|
|
mu_assert_notnull(fmt, "parse struct type_name");
|
|
mu_assert_eq(fmt->nfields, 1, "1 field");
|
|
mu_assert_eq(fmt->fields[0].type, RZ_PF_STRUCT, "STRUCT");
|
|
mu_assert_notnull(fmt->fields[0].type_name, "type_name set");
|
|
mu_assert_streq(fmt->fields[0].type_name, "my_struct", "type_name");
|
|
mu_assert_notnull(fmt->fields[0].name, "field name set");
|
|
mu_assert_streq(fmt->fields[0].name, "child", "field name");
|
|
rz_pf_format_free(fmt);
|
|
mu_end;
|
|
}
|
|
|
|
static bool test_pf_parse_enum_type_name(void) {
|
|
RzPfFormat *fmt = rz_pf_parse("E (my_enum)flags");
|
|
mu_assert_notnull(fmt, "parse enum type_name");
|
|
mu_assert_eq(fmt->nfields, 1, "1 field");
|
|
mu_assert_eq(fmt->fields[0].type, RZ_PF_ENUM, "ENUM");
|
|
mu_assert_notnull(fmt->fields[0].type_name, "type_name set");
|
|
mu_assert_streq(fmt->fields[0].type_name, "my_enum", "type_name");
|
|
mu_assert_streq(fmt->fields[0].name, "flags", "field name");
|
|
rz_pf_format_free(fmt);
|
|
mu_end;
|
|
}
|
|
|
|
static bool test_pf_parse_bitfield_type_name(void) {
|
|
RzPfFormat *fmt = rz_pf_parse("B (my_bits)bits");
|
|
mu_assert_notnull(fmt, "parse bitfield type_name");
|
|
mu_assert_eq(fmt->nfields, 1, "1 field");
|
|
mu_assert_eq(fmt->fields[0].type, RZ_PF_BITFIELD, "BITFIELD");
|
|
mu_assert_streq(fmt->fields[0].type_name, "my_bits", "type_name");
|
|
mu_assert_streq(fmt->fields[0].name, "bits", "field name");
|
|
rz_pf_format_free(fmt);
|
|
mu_end;
|
|
}
|
|
|
|
/* --------------------------------------------------------------------
|
|
* 30. Parse: repeat + union combined
|
|
* -------------------------------------------------------------------- */
|
|
static bool test_pf_parse_repeat_union_combined(void) {
|
|
RzPfFormat *fmt = rz_pf_parse("3 0x4d4");
|
|
mu_assert_notnull(fmt, "parse repeat + union");
|
|
mu_assert_eq(fmt->repeat, 3, "repeat = 3");
|
|
mu_assert_true(fmt->is_union, "is_union");
|
|
mu_assert_eq(fmt->nfields, 2, "2 fields");
|
|
rz_pf_format_free(fmt);
|
|
mu_end;
|
|
}
|
|
|
|
/* --------------------------------------------------------------------
|
|
* 31. Parse: whitespace handling
|
|
* -------------------------------------------------------------------- */
|
|
static bool test_pf_parse_leading_whitespace(void) {
|
|
RzPfFormat *fmt = rz_pf_parse(" x4");
|
|
mu_assert_notnull(fmt, "leading whitespace");
|
|
mu_assert_eq(fmt->nfields, 1, "1 field");
|
|
mu_assert_eq(fmt->fields[0].type, RZ_PF_HEX32, "HEX32");
|
|
rz_pf_format_free(fmt);
|
|
mu_end;
|
|
}
|
|
|
|
/* --------------------------------------------------------------------
|
|
* 32. Parse: complex mixed format string
|
|
* -------------------------------------------------------------------- */
|
|
static bool test_pf_parse_complex_mixed(void) {
|
|
RzPfFormat *fmt = rz_pf_parse(
|
|
"x4d2u8f4.z magic ver size weight name");
|
|
mu_assert_notnull(fmt, "parse complex");
|
|
mu_assert_eq(fmt->nfields, 6, "6 fields (including skip)");
|
|
|
|
mu_assert_eq(fmt->fields[0].type, RZ_PF_HEX32, "x4");
|
|
mu_assert_streq(fmt->fields[0].name, "magic", "magic");
|
|
|
|
mu_assert_eq(fmt->fields[1].type, RZ_PF_DEC_S16, "d2");
|
|
mu_assert_streq(fmt->fields[1].name, "ver", "ver");
|
|
|
|
mu_assert_eq(fmt->fields[2].type, RZ_PF_DEC_U64, "u8");
|
|
mu_assert_streq(fmt->fields[2].name, "size", "size");
|
|
|
|
mu_assert_eq(fmt->fields[3].type, RZ_PF_FLOAT32, "f4");
|
|
mu_assert_streq(fmt->fields[3].name, "weight", "weight");
|
|
|
|
/* Skip fields do not consume a name from the name list. */
|
|
mu_assert_eq(fmt->fields[4].type, RZ_PF_SKIP, "skip");
|
|
mu_assert_null(fmt->fields[4].name, "skip no name");
|
|
|
|
mu_assert_eq(fmt->fields[5].type, RZ_PF_ZSTRING, "z");
|
|
mu_assert_streq(fmt->fields[5].name, "name", "name");
|
|
|
|
rz_pf_format_free(fmt);
|
|
mu_end;
|
|
}
|
|
|
|
/* --------------------------------------------------------------------
|
|
* 33. Parse: deprecated bare 'X' (hexdump)
|
|
* -------------------------------------------------------------------- */
|
|
static bool test_pf_parse_deprecated_bare_X(void) {
|
|
RzPfFormat *fmt = rz_pf_parse("X");
|
|
mu_assert_notnull(fmt, "parse bare X");
|
|
mu_assert_eq(fmt->nfields, 1, "1 field");
|
|
mu_assert_eq(fmt->fields[0].type, RZ_PF_HEXDUMP, "bare X -> HEXDUMP");
|
|
rz_pf_format_free(fmt);
|
|
mu_end;
|
|
}
|
|
|
|
/* --------------------------------------------------------------------
|
|
* 34. rz_pf_read: unsigned 8-bit hex
|
|
* -------------------------------------------------------------------- */
|
|
static bool test_pf_read_hex8(void) {
|
|
RzPfFormat *fmt = rz_pf_parse("x1 val");
|
|
mu_assert_notnull(fmt, "parse x1");
|
|
|
|
const ut8 buf[] = { 0xAB };
|
|
RzPfCtx *ctx = rz_pf_ctx_new();
|
|
mu_assert_notnull(ctx, "ctx");
|
|
rz_pf_ctx_setup(ctx, NULL, false, 32, NULL, NULL);
|
|
|
|
int count = 0;
|
|
RzPfValue *vals = rz_pf_read(fmt, buf, sizeof(buf), 0x1000, ctx, &count);
|
|
mu_assert_notnull(vals, "read x1");
|
|
mu_assert_eq(count, 1, "1 value");
|
|
mu_assert_eq(vals[0].type, RZ_PF_HEX8, "type HEX8");
|
|
mu_assert_eq(vals[0].count, 1, "scalar count");
|
|
mu_assert_eq(vals[0].scalars[0].v_u8, 0xAB, "value 0xAB");
|
|
mu_assert_eq(vals[0].offset, 0x1000, "offset");
|
|
|
|
rz_pf_values_free(vals, count);
|
|
rz_pf_format_free(fmt);
|
|
rz_pf_ctx_free(ctx);
|
|
mu_end;
|
|
}
|
|
|
|
/* --------------------------------------------------------------------
|
|
* 35. rz_pf_read: 16-bit LE / BE hex
|
|
* -------------------------------------------------------------------- */
|
|
static bool test_pf_read_hex16_le(void) {
|
|
RzPfFormat *fmt = rz_pf_parse("x2 val");
|
|
mu_assert_notnull(fmt, "parse x2");
|
|
|
|
const ut8 buf[] = { 0x34, 0x12 }; /* LE -> 0x1234 */
|
|
RzPfCtx *ctx = rz_pf_ctx_new();
|
|
rz_pf_ctx_setup(ctx, NULL, false, 32, NULL, NULL);
|
|
|
|
int count = 0;
|
|
RzPfValue *vals = rz_pf_read(fmt, buf, sizeof(buf), 0, ctx, &count);
|
|
mu_assert_notnull(vals, "read x2 LE");
|
|
mu_assert_eq(vals[0].scalars[0].v_u16, 0x1234, "LE 0x1234");
|
|
|
|
rz_pf_values_free(vals, count);
|
|
rz_pf_format_free(fmt);
|
|
rz_pf_ctx_free(ctx);
|
|
mu_end;
|
|
}
|
|
|
|
static bool test_pf_read_hex16_be(void) {
|
|
RzPfFormat *fmt = rz_pf_parse("X2 val");
|
|
mu_assert_notnull(fmt, "parse X2");
|
|
|
|
const ut8 buf[] = { 0x12, 0x34 }; /* BE -> 0x1234 */
|
|
RzPfCtx *ctx = rz_pf_ctx_new();
|
|
rz_pf_ctx_setup(ctx, NULL, false, 32, NULL, NULL);
|
|
|
|
int count = 0;
|
|
RzPfValue *vals = rz_pf_read(fmt, buf, sizeof(buf), 0, ctx, &count);
|
|
mu_assert_notnull(vals, "read X2 BE");
|
|
mu_assert_eq(vals[0].scalars[0].v_u16, 0x1234, "BE 0x1234");
|
|
|
|
rz_pf_values_free(vals, count);
|
|
rz_pf_format_free(fmt);
|
|
rz_pf_ctx_free(ctx);
|
|
mu_end;
|
|
}
|
|
|
|
/* --------------------------------------------------------------------
|
|
* 36. rz_pf_read: 32-bit LE hex
|
|
* -------------------------------------------------------------------- */
|
|
static bool test_pf_read_hex32_le(void) {
|
|
RzPfFormat *fmt = rz_pf_parse("x4 val");
|
|
mu_assert_notnull(fmt, "parse x4");
|
|
|
|
const ut8 buf[] = { 0x78, 0x56, 0x34, 0x12 }; /* LE -> 0x12345678 */
|
|
RzPfCtx *ctx = rz_pf_ctx_new();
|
|
rz_pf_ctx_setup(ctx, NULL, false, 32, NULL, NULL);
|
|
|
|
int count = 0;
|
|
RzPfValue *vals = rz_pf_read(fmt, buf, sizeof(buf), 0, ctx, &count);
|
|
mu_assert_notnull(vals, "read x4");
|
|
mu_assert_eq(vals[0].scalars[0].v_u32, 0x12345678, "LE 0x12345678");
|
|
|
|
rz_pf_values_free(vals, count);
|
|
rz_pf_format_free(fmt);
|
|
rz_pf_ctx_free(ctx);
|
|
mu_end;
|
|
}
|
|
|
|
/* --------------------------------------------------------------------
|
|
* 37. rz_pf_read: 64-bit LE hex
|
|
* -------------------------------------------------------------------- */
|
|
static bool test_pf_read_hex64_le(void) {
|
|
RzPfFormat *fmt = rz_pf_parse("x8 val");
|
|
mu_assert_notnull(fmt, "parse x8");
|
|
|
|
const ut8 buf[] = {
|
|
0xEF, 0xCD, 0xAB, 0x90,
|
|
0x78, 0x56, 0x34, 0x12
|
|
}; /* LE -> 0x1234567890ABCDEF */
|
|
RzPfCtx *ctx = rz_pf_ctx_new();
|
|
rz_pf_ctx_setup(ctx, NULL, false, 64, NULL, NULL);
|
|
|
|
int count = 0;
|
|
RzPfValue *vals = rz_pf_read(fmt, buf, sizeof(buf), 0, ctx, &count);
|
|
mu_assert_notnull(vals, "read x8");
|
|
mu_assert_eq(vals[0].scalars[0].v_u64, 0x1234567890ABCDEFULL, "LE 64-bit");
|
|
|
|
rz_pf_values_free(vals, count);
|
|
rz_pf_format_free(fmt);
|
|
rz_pf_ctx_free(ctx);
|
|
mu_end;
|
|
}
|
|
|
|
/* --------------------------------------------------------------------
|
|
* 38. rz_pf_read: signed 32-bit LE (negative value)
|
|
* -------------------------------------------------------------------- */
|
|
static bool test_pf_read_signed32_le(void) {
|
|
RzPfFormat *fmt = rz_pf_parse("d4 val");
|
|
mu_assert_notnull(fmt, "parse d4");
|
|
|
|
/* -1 in LE 32-bit = 0xFFFFFFFF */
|
|
const ut8 buf[] = { 0xFF, 0xFF, 0xFF, 0xFF };
|
|
RzPfCtx *ctx = rz_pf_ctx_new();
|
|
rz_pf_ctx_setup(ctx, NULL, false, 32, NULL, NULL);
|
|
|
|
int count = 0;
|
|
RzPfValue *vals = rz_pf_read(fmt, buf, sizeof(buf), 0, ctx, &count);
|
|
mu_assert_notnull(vals, "read d4");
|
|
mu_assert_eq(vals[0].scalars[0].v_s32, -1, "signed -1");
|
|
|
|
rz_pf_values_free(vals, count);
|
|
rz_pf_format_free(fmt);
|
|
rz_pf_ctx_free(ctx);
|
|
mu_end;
|
|
}
|
|
|
|
/* --------------------------------------------------------------------
|
|
* 39. rz_pf_read: unsigned 32-bit LE
|
|
* -------------------------------------------------------------------- */
|
|
static bool test_pf_read_unsigned32_le(void) {
|
|
RzPfFormat *fmt = rz_pf_parse("u4 val");
|
|
mu_assert_notnull(fmt, "parse u4");
|
|
|
|
/* 0x80000001 in LE */
|
|
const ut8 buf[] = { 0x01, 0x00, 0x00, 0x80 };
|
|
RzPfCtx *ctx = rz_pf_ctx_new();
|
|
rz_pf_ctx_setup(ctx, NULL, false, 32, NULL, NULL);
|
|
|
|
int count = 0;
|
|
RzPfValue *vals = rz_pf_read(fmt, buf, sizeof(buf), 0, ctx, &count);
|
|
mu_assert_notnull(vals, "read u4");
|
|
mu_assert_eq(vals[0].scalars[0].v_u32, 0x80000001U, "unsigned 0x80000001");
|
|
|
|
rz_pf_values_free(vals, count);
|
|
rz_pf_format_free(fmt);
|
|
rz_pf_ctx_free(ctx);
|
|
mu_end;
|
|
}
|
|
|
|
/* --------------------------------------------------------------------
|
|
* 40. rz_pf_read: float32 LE
|
|
* -------------------------------------------------------------------- */
|
|
static bool test_pf_read_float32_le(void) {
|
|
RzPfFormat *fmt = rz_pf_parse("f4 val");
|
|
mu_assert_notnull(fmt, "parse f4");
|
|
|
|
/* IEEE 754 LE representation of 3.14f:
|
|
3.14f = 0x4048F5C3 -> bytes C3 F5 48 40 */
|
|
const ut8 buf[] = { 0xC3, 0xF5, 0x48, 0x40 };
|
|
RzPfCtx *ctx = rz_pf_ctx_new();
|
|
rz_pf_ctx_setup(ctx, NULL, false, 32, NULL, NULL);
|
|
|
|
int count = 0;
|
|
RzPfValue *vals = rz_pf_read(fmt, buf, sizeof(buf), 0, ctx, &count);
|
|
mu_assert_notnull(vals, "read f4");
|
|
/* Allow small epsilon for floating point comparison */
|
|
float diff = vals[0].scalars[0].v_f32 - 3.14f;
|
|
mu_assert_true(diff > -0.001f && diff < 0.001f, "float ~3.14");
|
|
|
|
rz_pf_values_free(vals, count);
|
|
rz_pf_format_free(fmt);
|
|
rz_pf_ctx_free(ctx);
|
|
mu_end;
|
|
}
|
|
|
|
/* --------------------------------------------------------------------
|
|
* 41. rz_pf_read: float64 LE
|
|
* -------------------------------------------------------------------- */
|
|
static bool test_pf_read_float64_le(void) {
|
|
RzPfFormat *fmt = rz_pf_parse("f8 val");
|
|
mu_assert_notnull(fmt, "parse f8");
|
|
|
|
/* IEEE 754 LE representation of 3.14:
|
|
3.14 = 0x40091EB851EB851F
|
|
bytes: 1F 85 EB 51 B8 1E 09 40 */
|
|
const ut8 buf[] = { 0x1F, 0x85, 0xEB, 0x51, 0xB8, 0x1E, 0x09, 0x40 };
|
|
RzPfCtx *ctx = rz_pf_ctx_new();
|
|
rz_pf_ctx_setup(ctx, NULL, false, 64, NULL, NULL);
|
|
|
|
int count = 0;
|
|
RzPfValue *vals = rz_pf_read(fmt, buf, sizeof(buf), 0, ctx, &count);
|
|
mu_assert_notnull(vals, "read f8");
|
|
double diff = vals[0].scalars[0].v_f64 - 3.14;
|
|
mu_assert_true(diff > -0.0001 && diff < 0.0001, "double ~3.14");
|
|
|
|
rz_pf_values_free(vals, count);
|
|
rz_pf_format_free(fmt);
|
|
rz_pf_ctx_free(ctx);
|
|
mu_end;
|
|
}
|
|
|
|
/* --------------------------------------------------------------------
|
|
* 42. rz_pf_read: char
|
|
* -------------------------------------------------------------------- */
|
|
static bool test_pf_read_char(void) {
|
|
RzPfFormat *fmt = rz_pf_parse("c val");
|
|
mu_assert_notnull(fmt, "parse c");
|
|
|
|
const ut8 buf[] = { 'A' };
|
|
RzPfCtx *ctx = rz_pf_ctx_new();
|
|
rz_pf_ctx_setup(ctx, NULL, false, 32, NULL, NULL);
|
|
|
|
int count = 0;
|
|
RzPfValue *vals = rz_pf_read(fmt, buf, sizeof(buf), 0, ctx, &count);
|
|
mu_assert_notnull(vals, "read char");
|
|
mu_assert_eq(vals[0].scalars[0].v_u8, 'A', "char A");
|
|
|
|
rz_pf_values_free(vals, count);
|
|
rz_pf_format_free(fmt);
|
|
rz_pf_ctx_free(ctx);
|
|
mu_end;
|
|
}
|
|
|
|
/* --------------------------------------------------------------------
|
|
* 43. rz_pf_read: multiple fields (offset tracking)
|
|
* -------------------------------------------------------------------- */
|
|
static bool test_pf_read_multi_field_offsets(void) {
|
|
RzPfFormat *fmt = rz_pf_parse("x1x2x4 a b c");
|
|
mu_assert_notnull(fmt, "parse multi");
|
|
|
|
const ut8 buf[] = {
|
|
0xAA, /* x1: offset 0 */
|
|
0xBB, 0xCC, /* x2: offset 1 */
|
|
0xDD, 0xEE, 0xFF, 0x11 /* x4: offset 3 */
|
|
};
|
|
RzPfCtx *ctx = rz_pf_ctx_new();
|
|
rz_pf_ctx_setup(ctx, NULL, false, 32, NULL, NULL);
|
|
|
|
int count = 0;
|
|
RzPfValue *vals = rz_pf_read(fmt, buf, sizeof(buf), 0x100, ctx, &count);
|
|
mu_assert_notnull(vals, "read multi");
|
|
mu_assert_eq(count, 3, "3 values");
|
|
|
|
mu_assert_eq(vals[0].offset, 0x100, "offset[0]");
|
|
mu_assert_eq(vals[0].scalars[0].v_u8, 0xAA, "val[0]");
|
|
|
|
mu_assert_eq(vals[1].offset, 0x101, "offset[1]");
|
|
mu_assert_eq(vals[1].scalars[0].v_u16, 0xCCBB, "val[1] LE");
|
|
|
|
mu_assert_eq(vals[2].offset, 0x103, "offset[2]");
|
|
mu_assert_eq(vals[2].scalars[0].v_u32, 0x11FFEEDD, "val[2] LE");
|
|
|
|
rz_pf_values_free(vals, count);
|
|
rz_pf_format_free(fmt);
|
|
rz_pf_ctx_free(ctx);
|
|
mu_end;
|
|
}
|
|
|
|
/* --------------------------------------------------------------------
|
|
* 44. rz_pf_read: skip field
|
|
* -------------------------------------------------------------------- */
|
|
static bool test_pf_read_skip(void) {
|
|
RzPfFormat *fmt = rz_pf_parse("x1.x1 a b");
|
|
mu_assert_notnull(fmt, "parse skip");
|
|
|
|
const ut8 buf[] = { 0x11, 0xFF, 0x22 };
|
|
RzPfCtx *ctx = rz_pf_ctx_new();
|
|
rz_pf_ctx_setup(ctx, NULL, false, 32, NULL, NULL);
|
|
|
|
int count = 0;
|
|
RzPfValue *vals = rz_pf_read(fmt, buf, sizeof(buf), 0, ctx, &count);
|
|
mu_assert_notnull(vals, "read skip");
|
|
/* The skip field is present but offset advances by 1 */
|
|
/* Check that last field read the byte after the skipped one */
|
|
bool found_0x22 = false;
|
|
for (int i = 0; i < count; i++) {
|
|
if (vals[i].type == RZ_PF_HEX8 && vals[i].offset == 2) {
|
|
mu_assert_eq(vals[i].scalars[0].v_u8, 0x22, "after skip = 0x22");
|
|
found_0x22 = true;
|
|
}
|
|
}
|
|
mu_assert_true(found_0x22, "found value after skip");
|
|
|
|
rz_pf_values_free(vals, count);
|
|
rz_pf_format_free(fmt);
|
|
rz_pf_ctx_free(ctx);
|
|
mu_end;
|
|
}
|
|
|
|
/* --------------------------------------------------------------------
|
|
* 45. rz_pf_read: array of u8
|
|
* -------------------------------------------------------------------- */
|
|
static bool test_pf_read_array(void) {
|
|
RzPfFormat *fmt = rz_pf_parse("[4]x1 arr");
|
|
mu_assert_notnull(fmt, "parse array");
|
|
|
|
const ut8 buf[] = { 0x0A, 0x0B, 0x0C, 0x0D };
|
|
RzPfCtx *ctx = rz_pf_ctx_new();
|
|
rz_pf_ctx_setup(ctx, NULL, false, 32, NULL, NULL);
|
|
|
|
int count = 0;
|
|
RzPfValue *vals = rz_pf_read(fmt, buf, sizeof(buf), 0, ctx, &count);
|
|
mu_assert_notnull(vals, "read array");
|
|
mu_assert_eq(count, 1, "1 value (array)");
|
|
mu_assert_eq(vals[0].count, 4, "4 elements");
|
|
mu_assert_eq(vals[0].scalars[0].v_u8, 0x0A, "elem[0]");
|
|
mu_assert_eq(vals[0].scalars[1].v_u8, 0x0B, "elem[1]");
|
|
mu_assert_eq(vals[0].scalars[2].v_u8, 0x0C, "elem[2]");
|
|
mu_assert_eq(vals[0].scalars[3].v_u8, 0x0D, "elem[3]");
|
|
|
|
rz_pf_values_free(vals, count);
|
|
rz_pf_format_free(fmt);
|
|
rz_pf_ctx_free(ctx);
|
|
mu_end;
|
|
}
|
|
|
|
/* --------------------------------------------------------------------
|
|
* 46. rz_pf_read: buffer too short
|
|
* -------------------------------------------------------------------- */
|
|
static bool test_pf_read_buffer_too_short(void) {
|
|
RzPfFormat *fmt = rz_pf_parse("x4 val");
|
|
mu_assert_notnull(fmt, "parse x4 for short buf");
|
|
|
|
const ut8 buf[] = { 0x01, 0x02 }; /* only 2 bytes, need 4 */
|
|
RzPfCtx *ctx = rz_pf_ctx_new();
|
|
rz_pf_ctx_setup(ctx, NULL, false, 32, NULL, NULL);
|
|
|
|
int count = 0;
|
|
RzPfValue *vals = rz_pf_read(fmt, buf, sizeof(buf), 0, ctx, &count);
|
|
/* Reader returns a value slot but does not read past the end:
|
|
* the scalar stays zero-initialised. Caller can inspect names and
|
|
* see the value is unset. */
|
|
mu_assert_notnull(vals, "vals returned");
|
|
mu_assert_eq(count, 1, "one slot for the named field");
|
|
mu_assert_eq(vals[0].scalars[0].v_u32, 0, "no bytes read, zero value");
|
|
|
|
rz_pf_values_free(vals, count);
|
|
rz_pf_format_free(fmt);
|
|
rz_pf_ctx_free(ctx);
|
|
mu_end;
|
|
}
|
|
|
|
/* --------------------------------------------------------------------
|
|
* 47. rz_pf_read: timestamp unix32 LE
|
|
* -------------------------------------------------------------------- */
|
|
static bool test_pf_read_timestamp_unix32(void) {
|
|
RzPfFormat *fmt = rz_pf_parse("t(unix32) ts");
|
|
mu_assert_notnull(fmt, "parse t(unix32)");
|
|
|
|
/* 1700000000 = 0x65571A80 -> LE: 80 1A 57 65 */
|
|
const ut8 buf[] = { 0x80, 0x1A, 0x57, 0x65 };
|
|
RzPfCtx *ctx = rz_pf_ctx_new();
|
|
rz_pf_ctx_setup(ctx, NULL, false, 32, NULL, NULL);
|
|
|
|
int count = 0;
|
|
RzPfValue *vals = rz_pf_read(fmt, buf, sizeof(buf), 0, ctx, &count);
|
|
mu_assert_notnull(vals, "read unix32 ts");
|
|
mu_assert_eq(count, 1, "1 value");
|
|
mu_assert_eq(vals[0].type, RZ_PF_TIMESTAMP, "TIMESTAMP type");
|
|
mu_assert_eq(vals[0].timefmt, RZ_PF_TIMEFMT_UNIX32, "unix32 fmt");
|
|
mu_assert_eq(vals[0].scalars[0].v_u32, 0x65571A80U, "raw value");
|
|
|
|
rz_pf_values_free(vals, count);
|
|
rz_pf_format_free(fmt);
|
|
rz_pf_ctx_free(ctx);
|
|
mu_end;
|
|
}
|
|
|
|
/* --------------------------------------------------------------------
|
|
* 48. rz_pf_read: z-string (null terminated UTF-8)
|
|
* -------------------------------------------------------------------- */
|
|
static bool test_pf_read_zstring(void) {
|
|
RzPfFormat *fmt = rz_pf_parse("z name");
|
|
mu_assert_notnull(fmt, "parse z");
|
|
|
|
const ut8 buf[] = { 'H', 'e', 'l', 'l', 'o', '\0', 0xFF };
|
|
RzPfCtx *ctx = rz_pf_ctx_new();
|
|
rz_pf_ctx_setup(ctx, NULL, false, 32, NULL, NULL);
|
|
|
|
int count = 0;
|
|
RzPfValue *vals = rz_pf_read(fmt, buf, sizeof(buf), 0, ctx, &count);
|
|
mu_assert_notnull(vals, "read zstring");
|
|
mu_assert_eq(count, 1, "1 value");
|
|
mu_assert_eq(vals[0].type, RZ_PF_ZSTRING, "ZSTRING type");
|
|
mu_assert_notnull(vals[0].scalars[0].v_str, "string not null");
|
|
mu_assert_streq(vals[0].scalars[0].v_str, "Hello", "string = Hello");
|
|
|
|
rz_pf_values_free(vals, count);
|
|
rz_pf_format_free(fmt);
|
|
rz_pf_ctx_free(ctx);
|
|
mu_end;
|
|
}
|
|
|
|
/* --------------------------------------------------------------------
|
|
* 49. rz_pf_timestamp_to_tm: unix32
|
|
* -------------------------------------------------------------------- */
|
|
static bool test_pf_timestamp_to_tm_unix32(void) {
|
|
RzPfScalar raw = { 0 };
|
|
raw.v_u32 = 0; /* epoch: 1970-01-01 00:00:00 UTC */
|
|
struct tm tm_out;
|
|
int subsec = -1;
|
|
bool ok = rz_pf_timestamp_to_tm(RZ_PF_TIMEFMT_UNIX32,
|
|
&raw, &tm_out, &subsec);
|
|
mu_assert_true(ok, "unix32 epoch conversion");
|
|
mu_assert_eq(tm_out.tm_year, 70, "1970");
|
|
mu_assert_eq(tm_out.tm_mon, 0, "January");
|
|
mu_assert_eq(tm_out.tm_mday, 1, "1st");
|
|
mu_assert_eq(tm_out.tm_hour, 0, "00h");
|
|
mu_assert_eq(tm_out.tm_min, 0, "00m");
|
|
mu_assert_eq(tm_out.tm_sec, 0, "00s");
|
|
mu_assert_eq(subsec, 0, "no subsec");
|
|
mu_end;
|
|
}
|
|
|
|
/* --------------------------------------------------------------------
|
|
* 50. rz_pf_timestamp_to_tm: unixms (with sub-second)
|
|
* -------------------------------------------------------------------- */
|
|
static bool test_pf_timestamp_to_tm_unixms(void) {
|
|
RzPfScalar raw = { 0 };
|
|
raw.v_u64 = 1000500; /* 1000 seconds + 500 ms */
|
|
struct tm tm_out;
|
|
int subsec = -1;
|
|
bool ok = rz_pf_timestamp_to_tm(RZ_PF_TIMEFMT_UNIXMS,
|
|
&raw, &tm_out, &subsec);
|
|
mu_assert_true(ok, "unixms conversion");
|
|
/* 1000 seconds from epoch = 00:16:40 */
|
|
mu_assert_eq(tm_out.tm_min, 16, "16 min");
|
|
mu_assert_eq(tm_out.tm_sec, 40, "40 sec");
|
|
mu_assert_eq(subsec, 500000000, "500ms in ns");
|
|
mu_end;
|
|
}
|
|
|
|
/* --------------------------------------------------------------------
|
|
* 51. rz_pf_format_free: NULL safety
|
|
* -------------------------------------------------------------------- */
|
|
static bool test_pf_format_free_null(void) {
|
|
/* Should not crash */
|
|
rz_pf_format_free(NULL);
|
|
mu_end;
|
|
}
|
|
|
|
/* --------------------------------------------------------------------
|
|
* 52. rz_pf_values_free: NULL safety
|
|
* -------------------------------------------------------------------- */
|
|
static bool test_pf_values_free_null(void) {
|
|
/* Should not crash */
|
|
rz_pf_values_free(NULL, 0);
|
|
rz_pf_values_free(NULL, 5);
|
|
mu_end;
|
|
}
|
|
|
|
/* --------------------------------------------------------------------
|
|
* 53. rz_pf_ctx_new / free: lifecycle
|
|
* -------------------------------------------------------------------- */
|
|
static bool test_pf_ctx_lifecycle(void) {
|
|
RzPfCtx *ctx = rz_pf_ctx_new();
|
|
mu_assert_notnull(ctx, "ctx alloc");
|
|
rz_pf_ctx_setup(ctx, NULL, true, 64, NULL, NULL);
|
|
mu_assert_true(ctx->big_endian, "big_endian set");
|
|
mu_assert_eq(ctx->bits, 64, "bits = 64");
|
|
rz_pf_ctx_free(ctx);
|
|
|
|
/* NULL free should not crash */
|
|
rz_pf_ctx_free(NULL);
|
|
mu_end;
|
|
}
|
|
|
|
/* --------------------------------------------------------------------
|
|
* 54. Parse: many fields (realloc path, >16 fields)
|
|
* -------------------------------------------------------------------- */
|
|
static bool test_pf_parse_many_fields(void) {
|
|
/* 20 x1 fields to trigger realloc beyond initial cap=16 */
|
|
RzPfFormat *fmt = rz_pf_parse("x1x1x1x1x1x1x1x1x1x1x1x1x1x1x1x1x1x1x1x1");
|
|
mu_assert_notnull(fmt, "parse 20 fields");
|
|
mu_assert_eq(fmt->nfields, 20, "20 fields");
|
|
for (int i = 0; i < 20; i++) {
|
|
mu_assert_eq(fmt->fields[i].type, RZ_PF_HEX8, "all HEX8");
|
|
}
|
|
rz_pf_format_free(fmt);
|
|
mu_end;
|
|
}
|
|
|
|
/* --------------------------------------------------------------------
|
|
* 55. rz_pf_read: union mode (all fields at offset 0)
|
|
* -------------------------------------------------------------------- */
|
|
static bool test_pf_read_union(void) {
|
|
RzPfFormat *fmt = rz_pf_parse("0x4d4 hex_val dec_val");
|
|
mu_assert_notnull(fmt, "parse union");
|
|
mu_assert_true(fmt->is_union, "is_union");
|
|
|
|
const ut8 buf[] = { 0x01, 0x00, 0x00, 0x00 };
|
|
RzPfCtx *ctx = rz_pf_ctx_new();
|
|
rz_pf_ctx_setup(ctx, NULL, false, 32, NULL, NULL);
|
|
|
|
int count = 0;
|
|
RzPfValue *vals = rz_pf_read(fmt, buf, sizeof(buf), 0x200, ctx, &count);
|
|
mu_assert_notnull(vals, "read union");
|
|
mu_assert_eq(count, 2, "2 values");
|
|
/* Both fields should share offset 0x200 in union mode */
|
|
mu_assert_eq(vals[0].offset, 0x200, "union offset[0]");
|
|
mu_assert_eq(vals[1].offset, 0x200, "union offset[1]");
|
|
mu_assert_eq(vals[0].scalars[0].v_u32, 1, "hex val");
|
|
mu_assert_eq(vals[1].scalars[0].v_s32, 1, "dec val");
|
|
|
|
rz_pf_values_free(vals, count);
|
|
rz_pf_format_free(fmt);
|
|
rz_pf_ctx_free(ctx);
|
|
mu_end;
|
|
}
|
|
|
|
/* --------------------------------------------------------------------
|
|
* 56. rz_pf_read: BE 32-bit integer via X4
|
|
* -------------------------------------------------------------------- */
|
|
static bool test_pf_read_hex32_be(void) {
|
|
RzPfFormat *fmt = rz_pf_parse("X4 val");
|
|
mu_assert_notnull(fmt, "parse X4");
|
|
|
|
const ut8 buf[] = { 0x12, 0x34, 0x56, 0x78 }; /* BE -> 0x12345678 */
|
|
RzPfCtx *ctx = rz_pf_ctx_new();
|
|
rz_pf_ctx_setup(ctx, NULL, false, 32, NULL, NULL);
|
|
|
|
int count = 0;
|
|
RzPfValue *vals = rz_pf_read(fmt, buf, sizeof(buf), 0, ctx, &count);
|
|
mu_assert_notnull(vals, "read X4 BE");
|
|
mu_assert_eq(vals[0].scalars[0].v_u32, 0x12345678U, "BE 0x12345678");
|
|
|
|
rz_pf_values_free(vals, count);
|
|
rz_pf_format_free(fmt);
|
|
rz_pf_ctx_free(ctx);
|
|
mu_end;
|
|
}
|
|
|
|
/* --------------------------------------------------------------------
|
|
* 57. rz_pf_read: signed 8-bit
|
|
* -------------------------------------------------------------------- */
|
|
static bool test_pf_read_signed8(void) {
|
|
RzPfFormat *fmt = rz_pf_parse("d1 val");
|
|
mu_assert_notnull(fmt, "parse d1");
|
|
|
|
const ut8 buf[] = { 0x80 }; /* -128 as signed */
|
|
RzPfCtx *ctx = rz_pf_ctx_new();
|
|
rz_pf_ctx_setup(ctx, NULL, false, 32, NULL, NULL);
|
|
|
|
int count = 0;
|
|
RzPfValue *vals = rz_pf_read(fmt, buf, sizeof(buf), 0, ctx, &count);
|
|
mu_assert_notnull(vals, "read d1");
|
|
mu_assert_eq(vals[0].scalars[0].v_s8, -128, "signed -128");
|
|
|
|
rz_pf_values_free(vals, count);
|
|
rz_pf_format_free(fmt);
|
|
rz_pf_ctx_free(ctx);
|
|
mu_end;
|
|
}
|
|
|
|
/* --------------------------------------------------------------------
|
|
* 58. rz_pf_read: signed 16-bit LE
|
|
* -------------------------------------------------------------------- */
|
|
static bool test_pf_read_signed16_le(void) {
|
|
RzPfFormat *fmt = rz_pf_parse("d2 val");
|
|
mu_assert_notnull(fmt, "parse d2");
|
|
|
|
/* -256 = 0xFF00 -> LE: 00 FF */
|
|
const ut8 buf[] = { 0x00, 0xFF };
|
|
RzPfCtx *ctx = rz_pf_ctx_new();
|
|
rz_pf_ctx_setup(ctx, NULL, false, 32, NULL, NULL);
|
|
|
|
int count = 0;
|
|
RzPfValue *vals = rz_pf_read(fmt, buf, sizeof(buf), 0, ctx, &count);
|
|
mu_assert_notnull(vals, "read d2");
|
|
mu_assert_eq(vals[0].scalars[0].v_s16, -256, "signed -256");
|
|
|
|
rz_pf_values_free(vals, count);
|
|
rz_pf_format_free(fmt);
|
|
rz_pf_ctx_free(ctx);
|
|
mu_end;
|
|
}
|
|
|
|
/* --------------------------------------------------------------------
|
|
* 59. rz_pf_read: signed 64-bit LE
|
|
* -------------------------------------------------------------------- */
|
|
static bool test_pf_read_signed64_le(void) {
|
|
RzPfFormat *fmt = rz_pf_parse("d8 val");
|
|
mu_assert_notnull(fmt, "parse d8");
|
|
|
|
/* -1 as 64-bit LE */
|
|
const ut8 buf[] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF };
|
|
RzPfCtx *ctx = rz_pf_ctx_new();
|
|
rz_pf_ctx_setup(ctx, NULL, false, 64, NULL, NULL);
|
|
|
|
int count = 0;
|
|
RzPfValue *vals = rz_pf_read(fmt, buf, sizeof(buf), 0, ctx, &count);
|
|
mu_assert_notnull(vals, "read d8");
|
|
mu_assert_eq(vals[0].scalars[0].v_s64, -1LL, "signed -1 64-bit");
|
|
|
|
rz_pf_values_free(vals, count);
|
|
rz_pf_format_free(fmt);
|
|
rz_pf_ctx_free(ctx);
|
|
mu_end;
|
|
}
|
|
|
|
/* --------------------------------------------------------------------
|
|
* 60. rz_pf_read: array of x4 LE
|
|
* -------------------------------------------------------------------- */
|
|
static bool test_pf_read_array_x4(void) {
|
|
RzPfFormat *fmt = rz_pf_parse("[3]x4 items");
|
|
mu_assert_notnull(fmt, "parse [3]x4");
|
|
|
|
const ut8 buf[] = {
|
|
0x01,
|
|
0x00,
|
|
0x00,
|
|
0x00,
|
|
0x02,
|
|
0x00,
|
|
0x00,
|
|
0x00,
|
|
0x03,
|
|
0x00,
|
|
0x00,
|
|
0x00,
|
|
};
|
|
RzPfCtx *ctx = rz_pf_ctx_new();
|
|
rz_pf_ctx_setup(ctx, NULL, false, 32, NULL, NULL);
|
|
|
|
int count = 0;
|
|
RzPfValue *vals = rz_pf_read(fmt, buf, sizeof(buf), 0, ctx, &count);
|
|
mu_assert_notnull(vals, "read [3]x4");
|
|
mu_assert_eq(count, 1, "1 array value");
|
|
mu_assert_eq(vals[0].count, 3, "3 elements");
|
|
mu_assert_eq(vals[0].scalars[0].v_u32, 1, "elem[0] = 1");
|
|
mu_assert_eq(vals[0].scalars[1].v_u32, 2, "elem[1] = 2");
|
|
mu_assert_eq(vals[0].scalars[2].v_u32, 3, "elem[2] = 3");
|
|
|
|
rz_pf_values_free(vals, count);
|
|
rz_pf_format_free(fmt);
|
|
rz_pf_ctx_free(ctx);
|
|
mu_end;
|
|
}
|
|
|
|
/* DSL extensions: parse-side and read-side coverage. */
|
|
|
|
static bool test_pf_parse_align(void) {
|
|
RzPfFormat *fmt = rz_pf_parse("x1@4x4 a b");
|
|
mu_assert_notnull(fmt, "parse @4");
|
|
mu_assert_eq(fmt->nfields, 3, "3 fields incl align");
|
|
mu_assert_eq(fmt->fields[0].type, RZ_PF_HEX8, "x1");
|
|
mu_assert_eq(fmt->fields[1].type, RZ_PF_ALIGN, "@");
|
|
mu_assert_eq(fmt->fields[1].align_to, 4, "align to 4");
|
|
mu_assert_eq(fmt->fields[2].type, RZ_PF_HEX32, "x4");
|
|
rz_pf_format_free(fmt);
|
|
mu_end;
|
|
}
|
|
|
|
static bool test_pf_read_align(void) {
|
|
/* x1 takes 1 byte, @4 pads 3, x4 reads next 4 bytes. */
|
|
RzPfFormat *fmt = rz_pf_parse("x1@4x4 a b");
|
|
const ut8 buf[] = {
|
|
0xAA,
|
|
0xFF,
|
|
0xFF,
|
|
0xFF, /* padding bytes are skipped */
|
|
0x44,
|
|
0x33,
|
|
0x22,
|
|
0x11,
|
|
};
|
|
int count = 0;
|
|
RzPfValue *vals = rz_pf_read(fmt, buf, sizeof(buf), 0, NULL, &count);
|
|
mu_assert_notnull(vals, "read align");
|
|
mu_assert_eq(count, 3, "3 values incl align");
|
|
mu_assert_eq(vals[0].scalars[0].v_u8, 0xAA, "first byte");
|
|
mu_assert_eq(vals[2].scalars[0].v_u32, 0x11223344u, "post-align u32 LE");
|
|
rz_pf_values_free(vals, count);
|
|
rz_pf_format_free(fmt);
|
|
mu_end;
|
|
}
|
|
|
|
static bool test_pf_parse_bits(void) {
|
|
RzPfFormat *fmt = rz_pf_parse(":3:5x1 mode rest tail");
|
|
mu_assert_notnull(fmt, "parse :3:5");
|
|
mu_assert_eq(fmt->nfields, 3, "3 fields");
|
|
mu_assert_eq(fmt->fields[0].type, RZ_PF_BITS, "first bits");
|
|
mu_assert_eq(fmt->fields[0].bit_width, 3, "3 bits");
|
|
mu_assert_eq(fmt->fields[1].type, RZ_PF_BITS, "second bits");
|
|
mu_assert_eq(fmt->fields[1].bit_width, 5, "5 bits");
|
|
mu_assert_eq(fmt->fields[2].type, RZ_PF_HEX8, "tail x1");
|
|
rz_pf_format_free(fmt);
|
|
mu_end;
|
|
}
|
|
|
|
static bool test_pf_read_bits_msb(void) {
|
|
/* Byte 0xA5 = 0b10100101. MSB-first :4 -> 0xA, :4 -> 0x5. */
|
|
RzPfFormat *fmt = rz_pf_parse(":4:4 hi lo");
|
|
const ut8 buf[] = { 0xA5, 0x00 };
|
|
int count = 0;
|
|
RzPfValue *vals = rz_pf_read(fmt, buf, sizeof(buf), 0, NULL, &count);
|
|
mu_assert_notnull(vals, "read bits");
|
|
mu_assert_eq(count, 2, "2 values");
|
|
mu_assert_eq(vals[0].scalars[0].v_u64, 0xA, "high nibble");
|
|
mu_assert_eq(vals[1].scalars[0].v_u64, 0x5, "low nibble");
|
|
rz_pf_values_free(vals, count);
|
|
rz_pf_format_free(fmt);
|
|
mu_end;
|
|
}
|
|
|
|
static bool test_pf_read_bits_lsb(void) {
|
|
/* Byte 0xA5. LSB-first :4< -> 0x5, :4< -> 0xA. */
|
|
RzPfFormat *fmt = rz_pf_parse(":4<:4< lo hi");
|
|
const ut8 buf[] = { 0xA5, 0x00 };
|
|
int count = 0;
|
|
RzPfValue *vals = rz_pf_read(fmt, buf, sizeof(buf), 0, NULL, &count);
|
|
mu_assert_notnull(vals, "read bits LSB");
|
|
mu_assert_eq(vals[0].scalars[0].v_u64, 0x5, "LSB nibble");
|
|
mu_assert_eq(vals[1].scalars[0].v_u64, 0xA, "next nibble");
|
|
rz_pf_values_free(vals, count);
|
|
rz_pf_format_free(fmt);
|
|
mu_end;
|
|
}
|
|
|
|
/* --------- bitvector (v(N)) tests ----------------------------------- */
|
|
|
|
static bool test_pf_parse_bitvec_basic(void) {
|
|
RzPfFormat *fmt = rz_pf_parse("v(8) bits");
|
|
mu_assert_notnull(fmt, "parse v(8)");
|
|
mu_assert_eq(fmt->nfields, 1, "1 field");
|
|
mu_assert_eq(fmt->fields[0].type, RZ_PF_BITVEC, "BITVEC");
|
|
mu_assert_eq(fmt->fields[0].bit_width, 8, "8-bit");
|
|
mu_assert_eq(fmt->fields[0].bit_order, RZ_PF_BITORDER_MSB,
|
|
"default MSB");
|
|
rz_pf_format_free(fmt);
|
|
mu_end;
|
|
}
|
|
|
|
static bool test_pf_parse_bitvec_partial_byte(void) {
|
|
/* v(12) reads 2 bytes but only 12 bits are exposed. */
|
|
RzPfFormat *fmt = rz_pf_parse("v(12) bits");
|
|
mu_assert_notnull(fmt, "parse v(12)");
|
|
mu_assert_eq(fmt->fields[0].bit_width, 12, "12-bit width");
|
|
rz_pf_format_free(fmt);
|
|
mu_end;
|
|
}
|
|
|
|
static bool test_pf_parse_bitvec_lsb_msb_kw(void) {
|
|
RzPfFormat *fmt = rz_pf_parse("v(16,lsb) bits");
|
|
mu_assert_notnull(fmt, "parse v(16,lsb)");
|
|
mu_assert_eq(fmt->fields[0].bit_order, RZ_PF_BITORDER_LSB,
|
|
"LSB order");
|
|
rz_pf_format_free(fmt);
|
|
|
|
fmt = rz_pf_parse("v(16,msb) bits");
|
|
mu_assert_notnull(fmt, "parse v(16,msb)");
|
|
mu_assert_eq(fmt->fields[0].bit_order, RZ_PF_BITORDER_MSB,
|
|
"MSB order");
|
|
rz_pf_format_free(fmt);
|
|
mu_end;
|
|
}
|
|
|
|
static bool test_pf_parse_bitvec_clamp_low(void) {
|
|
/* v(0) -> clamped to 1 with a warning. */
|
|
RzPfFormat *fmt = rz_pf_parse("v(0) bits");
|
|
mu_assert_notnull(fmt, "parse v(0)");
|
|
mu_assert_eq(fmt->fields[0].bit_width, 1, "clamped low to 1");
|
|
rz_pf_format_free(fmt);
|
|
mu_end;
|
|
}
|
|
|
|
static bool test_pf_parse_bitvec_clamp_high(void) {
|
|
/* v(99999) -> clamped to 4096 with a warning. */
|
|
RzPfFormat *fmt = rz_pf_parse("v(99999) bits");
|
|
mu_assert_notnull(fmt, "parse v(99999)");
|
|
mu_assert_eq(fmt->fields[0].bit_width, 4096, "clamped high to 4096");
|
|
rz_pf_format_free(fmt);
|
|
mu_end;
|
|
}
|
|
|
|
static bool test_pf_parse_bitvec_missing_paren(void) {
|
|
/* `v` without `(` is a syntax error; parser logs and skips. */
|
|
RzPfFormat *fmt = rz_pf_parse("v bits");
|
|
mu_assert_notnull(fmt, "parse v (degraded)");
|
|
mu_assert("diagnostic recorded", fmt->nerrors > 0);
|
|
rz_pf_format_free(fmt);
|
|
mu_end;
|
|
}
|
|
|
|
static bool test_pf_read_bitvec_msb(void) {
|
|
/* Byte 0xAB = 0b10101011, MSB-first => bits 1,0,1,0,1,0,1,1 */
|
|
RzPfFormat *fmt = rz_pf_parse("v(8) bits");
|
|
const ut8 buf[] = { 0xAB, 0x00 };
|
|
int count = 0;
|
|
RzPfValue *vals = rz_pf_read(fmt, buf, sizeof(buf), 0, NULL, &count);
|
|
mu_assert_notnull(vals, "read v(8) MSB");
|
|
mu_assert_eq(count, 1, "1 value");
|
|
mu_assert_eq(vals[0].type, RZ_PF_BITVEC, "BITVEC type");
|
|
mu_assert_eq(vals[0].count, 8, "8 bit-scalars");
|
|
mu_assert_eq(vals[0].bit_width, 8, "bit_width=8");
|
|
const ut8 expected[] = { 1, 0, 1, 0, 1, 0, 1, 1 };
|
|
for (int i = 0; i < 8; i++) {
|
|
mu_assert_eq(vals[0].scalars[i].v_u8, expected[i],
|
|
"MSB bit i");
|
|
}
|
|
rz_pf_values_free(vals, count);
|
|
rz_pf_format_free(fmt);
|
|
mu_end;
|
|
}
|
|
|
|
static bool test_pf_read_bitvec_lsb(void) {
|
|
/* Byte 0xAB, LSB-first => bits 1,1,0,1,0,1,0,1 */
|
|
RzPfFormat *fmt = rz_pf_parse("v(8,lsb) bits");
|
|
const ut8 buf[] = { 0xAB, 0x00 };
|
|
int count = 0;
|
|
RzPfValue *vals = rz_pf_read(fmt, buf, sizeof(buf), 0, NULL, &count);
|
|
mu_assert_notnull(vals, "read v(8) LSB");
|
|
const ut8 expected[] = { 1, 1, 0, 1, 0, 1, 0, 1 };
|
|
for (int i = 0; i < 8; i++) {
|
|
mu_assert_eq(vals[0].scalars[i].v_u8, expected[i],
|
|
"LSB bit i");
|
|
}
|
|
rz_pf_values_free(vals, count);
|
|
rz_pf_format_free(fmt);
|
|
mu_end;
|
|
}
|
|
|
|
static bool test_pf_read_bitvec_partial(void) {
|
|
/* v(12) over 0xAB 0xCD MSB-first:
|
|
* 0xAB = 10101011, 0xCD = 11001101 -> first 12 bits =
|
|
* 1 0 1 0 1 0 1 1 | 1 1 0 0
|
|
* Consumes 2 bytes from buf. */
|
|
RzPfFormat *fmt = rz_pf_parse("v(12)x1 bits tail");
|
|
const ut8 buf[] = { 0xAB, 0xCD, 0xEF };
|
|
int count = 0;
|
|
RzPfValue *vals = rz_pf_read(fmt, buf, sizeof(buf), 0, NULL, &count);
|
|
mu_assert_notnull(vals, "read v(12)");
|
|
mu_assert_eq(count, 2, "2 values (bits + tail)");
|
|
mu_assert_eq(vals[0].count, 12, "12 bits");
|
|
const ut8 expected[] = { 1, 0, 1, 0, 1, 0, 1, 1,
|
|
1, 1, 0, 0 };
|
|
for (int i = 0; i < 12; i++) {
|
|
mu_assert_eq(vals[0].scalars[i].v_u8, expected[i],
|
|
"bit i");
|
|
}
|
|
/* tail should be the third byte 0xEF, proving v(12) consumed
|
|
* exactly ceil(12/8) = 2 bytes. */
|
|
mu_assert_eq(vals[1].scalars[0].v_u8, 0xEF, "tail after bitvec");
|
|
rz_pf_values_free(vals, count);
|
|
rz_pf_format_free(fmt);
|
|
mu_end;
|
|
}
|
|
|
|
static bool test_pf_render_bitvec_text(void) {
|
|
RzPfFormat *fmt = rz_pf_parse("v(12) bits");
|
|
const ut8 buf[] = { 0xAB, 0xCD };
|
|
int count = 0;
|
|
RzPfValue *vals = rz_pf_read(fmt, buf, sizeof(buf), 0, NULL, &count);
|
|
char *out = rz_pf_render(vals, count, RZ_PF_MODE_TEXT, NULL);
|
|
mu_assert_notnull(out, "render text");
|
|
/* Format: `[ 1 0 1 0 1 0 1 1 | 1 1 0 0 ] (12-bit)` */
|
|
mu_assert("grouped-by-8 rendering present",
|
|
strstr(out, "[ 1 0 1 0 1 0 1 1 | 1 1 0 0 ]"));
|
|
mu_assert("width suffix present", strstr(out, "(12-bit)"));
|
|
free(out);
|
|
rz_pf_values_free(vals, count);
|
|
rz_pf_format_free(fmt);
|
|
mu_end;
|
|
}
|
|
|
|
static bool test_pf_parse_guid(void) {
|
|
RzPfFormat *fmt = rz_pf_parse("G uuid");
|
|
mu_assert_notnull(fmt, "parse G");
|
|
mu_assert_eq(fmt->nfields, 1, "1 field");
|
|
mu_assert_eq(fmt->fields[0].type, RZ_PF_GUID, "GUID");
|
|
mu_assert_eq(fmt->fields[0].guid_layout, RZ_PF_GUID_MS, "default MS");
|
|
rz_pf_format_free(fmt);
|
|
|
|
fmt = rz_pf_parse("G(be) uuid");
|
|
mu_assert_notnull(fmt, "parse G(be)");
|
|
mu_assert_eq(fmt->fields[0].guid_layout, RZ_PF_GUID_BE, "BE layout");
|
|
rz_pf_format_free(fmt);
|
|
mu_end;
|
|
}
|
|
|
|
static bool test_pf_read_guid(void) {
|
|
RzPfFormat *fmt = rz_pf_parse("G uuid");
|
|
const ut8 buf[16] = {
|
|
0x44,
|
|
0x33,
|
|
0x22,
|
|
0x11, /* MS D1 LE -> 0x11223344 */
|
|
0x66,
|
|
0x55, /* D2 LE -> 0x5566 */
|
|
0x88,
|
|
0x77, /* D3 LE -> 0x7788 */
|
|
0x99,
|
|
0xAA, /* D4 raw */
|
|
0xBB,
|
|
0xCC,
|
|
0xDD,
|
|
0xEE,
|
|
0xFF,
|
|
0x00,
|
|
};
|
|
int count = 0;
|
|
RzPfValue *vals = rz_pf_read(fmt, buf, sizeof(buf), 0, NULL, &count);
|
|
mu_assert_notnull(vals, "read GUID");
|
|
mu_assert_eq(vals[0].raw_len, 16, "16 bytes");
|
|
mu_assert_eq(vals[0].scalars[0].v_raw[0], 0x44, "raw byte 0");
|
|
rz_pf_values_free(vals, count);
|
|
rz_pf_format_free(fmt);
|
|
mu_end;
|
|
}
|
|
|
|
static bool test_pf_parse_lenref_array(void) {
|
|
RzPfFormat *fmt = rz_pf_parse("u1[@count]x4 count items");
|
|
mu_assert_notnull(fmt, "parse [@count]");
|
|
mu_assert_eq(fmt->nfields, 2, "2 fields");
|
|
mu_assert_streq(fmt->fields[1].length_ref, "count",
|
|
"length_ref set");
|
|
mu_assert_eq(fmt->fields[1].array_count, -1, "no literal");
|
|
rz_pf_format_free(fmt);
|
|
mu_end;
|
|
}
|
|
|
|
static bool test_pf_read_lenref_array(void) {
|
|
RzPfFormat *fmt = rz_pf_parse("u1[@count]x4 count items");
|
|
const ut8 buf[] = {
|
|
0x03, /* count = 3 */
|
|
0x01,
|
|
0x00,
|
|
0x00,
|
|
0x00, /* items[0] LE */
|
|
0x02,
|
|
0x00,
|
|
0x00,
|
|
0x00,
|
|
0x03,
|
|
0x00,
|
|
0x00,
|
|
0x00,
|
|
};
|
|
int count = 0;
|
|
RzPfValue *vals = rz_pf_read(fmt, buf, sizeof(buf), 0, NULL, &count);
|
|
mu_assert_notnull(vals, "read lenref array");
|
|
mu_assert_eq(count, 2, "2 fields");
|
|
mu_assert_eq(vals[0].scalars[0].v_u8, 3, "count = 3");
|
|
mu_assert_eq(vals[1].count, 3, "items has 3 elements");
|
|
mu_assert_eq(vals[1].scalars[0].v_u32, 1, "items[0]=1");
|
|
mu_assert_eq(vals[1].scalars[2].v_u32, 3, "items[2]=3");
|
|
rz_pf_values_free(vals, count);
|
|
rz_pf_format_free(fmt);
|
|
mu_end;
|
|
}
|
|
|
|
static bool test_pf_parse_lenprefix_string(void) {
|
|
RzPfFormat *fmt = rz_pf_parse("z[1] name");
|
|
mu_assert_notnull(fmt, "parse z[1]");
|
|
mu_assert_eq(fmt->fields[0].type, RZ_PF_ZSTRING, "z");
|
|
mu_assert_eq(fmt->fields[0].str_len_prefix, 1, "1-byte prefix");
|
|
mu_assert_false(fmt->fields[0].str_len_in_bytes, "chars by default");
|
|
rz_pf_format_free(fmt);
|
|
|
|
fmt = rz_pf_parse("z(utf16le)[2b] name");
|
|
mu_assert_notnull(fmt, "parse z(utf16le)[2b]");
|
|
mu_assert_eq(fmt->fields[0].str_len_prefix, 2, "2-byte prefix");
|
|
mu_assert_true(fmt->fields[0].str_len_in_bytes, "bytes flag set");
|
|
mu_assert_eq(fmt->fields[0].encoding, RZ_STRING_ENC_UTF16LE,
|
|
"utf16le encoding");
|
|
rz_pf_format_free(fmt);
|
|
mu_end;
|
|
}
|
|
|
|
static bool test_pf_read_lenprefix_string(void) {
|
|
RzPfFormat *fmt = rz_pf_parse("z[1] greet");
|
|
const ut8 buf[] = { 0x05, 'h', 'e', 'l', 'l', 'o' };
|
|
int count = 0;
|
|
RzPfValue *vals = rz_pf_read(fmt, buf, sizeof(buf), 0, NULL, &count);
|
|
mu_assert_notnull(vals, "read pascal string");
|
|
mu_assert_streq(vals[0].scalars[0].v_str, "hello", "decoded body");
|
|
rz_pf_values_free(vals, count);
|
|
rz_pf_format_free(fmt);
|
|
mu_end;
|
|
}
|
|
|
|
static bool test_pf_parse_inline_bitfield(void) {
|
|
RzPfFormat *fmt = rz_pf_parse("B4(R=1,W=2,X=4) perms");
|
|
mu_assert_notnull(fmt, "parse B4(...)");
|
|
mu_assert_eq(fmt->fields[0].type, RZ_PF_BITFIELD,
|
|
"reinterpreted as BITFIELD");
|
|
mu_assert_eq(fmt->fields[0].bitfield_size, 4, "4 bytes");
|
|
mu_assert_eq(fmt->fields[0].bitflag_count, 3, "3 flags");
|
|
mu_assert_streq(fmt->fields[0].bitflags[0].name, "R", "flag R");
|
|
mu_assert_eq(fmt->fields[0].bitflags[0].value, 1, "R=1");
|
|
mu_assert_eq(fmt->fields[0].bitflags[2].value, 4, "X=4");
|
|
rz_pf_format_free(fmt);
|
|
mu_end;
|
|
}
|
|
|
|
static bool test_pf_read_inline_bitfield_render(void) {
|
|
/* The text renderer should expand named flags.
|
|
* `B4` is upper-case so the integer is read BE. Bytes laid out
|
|
* for value 0x00000005 = R | X. */
|
|
RzPfFormat *fmt = rz_pf_parse("B4(R=1,W=2,X=4) perms");
|
|
const ut8 buf[] = { 0x00, 0x00, 0x00, 0x05 };
|
|
int count = 0;
|
|
RzPfValue *vals = rz_pf_read(fmt, buf, sizeof(buf), 0, NULL, &count);
|
|
mu_assert_notnull(vals, "read bitfield");
|
|
char *out = rz_pf_render(vals, count, RZ_PF_MODE_TEXT, NULL);
|
|
mu_assert_notnull(out, "render");
|
|
mu_assert_true(strstr(out, "R") != NULL, "R present in output");
|
|
mu_assert_true(strstr(out, "X") != NULL, "X present in output");
|
|
mu_assert_true(strstr(out, "W") == NULL, "W not present");
|
|
free(out);
|
|
rz_pf_values_free(vals, count);
|
|
rz_pf_format_free(fmt);
|
|
mu_end;
|
|
}
|
|
|
|
static bool test_pf_parse_tlv(void) {
|
|
RzPfFormat *fmt = rz_pf_parse("V(t=u1,l=u2,d=my_table) record");
|
|
mu_assert_notnull(fmt, "parse V(...)");
|
|
mu_assert_eq(fmt->fields[0].type, RZ_PF_TLV, "TLV");
|
|
mu_assert_notnull(fmt->fields[0].tlv_spec, "spec set");
|
|
mu_assert_eq(fmt->fields[0].tlv_spec->tag_size, 1, "tag u1");
|
|
mu_assert_eq(fmt->fields[0].tlv_spec->len_size, 2, "len u2");
|
|
mu_assert_streq(fmt->fields[0].tlv_spec->dispatch_name,
|
|
"my_table", "dispatch name");
|
|
rz_pf_format_free(fmt);
|
|
mu_end;
|
|
}
|
|
|
|
static bool test_pf_read_tlv_raw(void) {
|
|
/* No dispatch registered -> value stored as raw bytes. */
|
|
RzPfFormat *fmt = rz_pf_parse("V(t=u1,l=u2) rec");
|
|
const ut8 buf[] = {
|
|
0x42, /* tag */
|
|
0x03, 0x00, /* length = 3 LE */
|
|
0xDE, 0xAD, 0xBE, /* value bytes */
|
|
};
|
|
int count = 0;
|
|
RzPfValue *vals = rz_pf_read(fmt, buf, sizeof(buf), 0, NULL, &count);
|
|
mu_assert_notnull(vals, "read TLV");
|
|
mu_assert_eq(vals[0].tlv_tag, 0x42, "tag");
|
|
mu_assert_eq(vals[0].tlv_length, 3, "value len");
|
|
mu_assert_eq(vals[0].raw_len, 3, "raw bytes stored");
|
|
mu_assert_eq(vals[0].scalars[0].v_raw[0], 0xDE, "value[0]");
|
|
rz_pf_values_free(vals, count);
|
|
rz_pf_format_free(fmt);
|
|
mu_end;
|
|
}
|
|
|
|
/* Phase 5: render-mode coverage, every timestamp format, pointer
|
|
* dereference via the mem_read_at callback, and malformed-input /
|
|
* recursion-bound smoke tests. */
|
|
|
|
static bool test_pf_render_modes_basic(void) {
|
|
RzPfFormat *fmt = rz_pf_parse("x4 magic");
|
|
const ut8 buf[] = { 0xEF, 0xBE, 0xAD, 0xDE };
|
|
int count = 0;
|
|
RzPfValue *vals = rz_pf_read(fmt, buf, sizeof(buf), 0, NULL, &count);
|
|
|
|
char *t = rz_pf_render(vals, count, RZ_PF_MODE_TEXT, NULL);
|
|
mu_assert_notnull(t, "text");
|
|
mu_assert_true(strstr(t, "magic") != NULL, "text has name");
|
|
mu_assert_true(strstr(t, "0xdeadbeef") != NULL, "text has hex");
|
|
free(t);
|
|
|
|
char *j = rz_pf_render(vals, count, RZ_PF_MODE_JSON, NULL);
|
|
mu_assert_notnull(j, "json");
|
|
mu_assert_true(j[0] == '[' && j[1] == '{', "json array of objects");
|
|
mu_assert_true(strstr(j, "\"name\":\"magic\"") != NULL, "json name");
|
|
mu_assert_true(strstr(j, "\"endian\":\"little\"") != NULL,
|
|
"json endian");
|
|
free(j);
|
|
|
|
char *c = rz_pf_render(vals, count, RZ_PF_MODE_CSTRUCT, NULL);
|
|
mu_assert_notnull(c, "cstruct");
|
|
mu_assert_true(strstr(c, "uint32_t magic;") != NULL,
|
|
"cstruct decl");
|
|
free(c);
|
|
|
|
char *q = rz_pf_render(vals, count, RZ_PF_MODE_QUIET, NULL);
|
|
mu_assert_notnull(q, "quiet");
|
|
mu_assert_true(strstr(q, "0xdeadbeef") != NULL, "quiet value");
|
|
mu_assert_true(strstr(q, "magic") == NULL,
|
|
"quiet has no field name");
|
|
free(q);
|
|
|
|
rz_pf_values_free(vals, count);
|
|
rz_pf_format_free(fmt);
|
|
mu_end;
|
|
}
|
|
|
|
static bool test_pf_render_quiet_ofield_filter(void) {
|
|
RzPfFormat *fmt = rz_pf_parse("x4d4 a b");
|
|
const ut8 buf[] = {
|
|
0xEF,
|
|
0xBE,
|
|
0xAD,
|
|
0xDE,
|
|
0x39,
|
|
0x05,
|
|
0x00,
|
|
0x00,
|
|
};
|
|
int count = 0;
|
|
RzPfValue *vals = rz_pf_read(fmt, buf, sizeof(buf), 0, NULL, &count);
|
|
mu_assert_eq(count, 2, "two values");
|
|
|
|
char *t = rz_pf_render(vals, count, RZ_PF_MODE_TEXT, &(RzPfRenderOpts){ .field_filter = "b" });
|
|
mu_assert_notnull(t, "filtered text");
|
|
mu_assert_true(strstr(t, "1337") != NULL, "b value present");
|
|
mu_assert_true(strstr(t, "0xdeadbeef") == NULL,
|
|
"a value not present");
|
|
free(t);
|
|
|
|
rz_pf_values_free(vals, count);
|
|
rz_pf_format_free(fmt);
|
|
mu_end;
|
|
}
|
|
|
|
static bool test_pf_timestamp_all_formats_size(void) {
|
|
const struct {
|
|
RzPfTimeFmt fmt;
|
|
int expected_sz;
|
|
const char *name;
|
|
} cases[] = {
|
|
{ RZ_PF_TIMEFMT_UNIX32, 4, "unix32" },
|
|
{ RZ_PF_TIMEFMT_UNIX64, 8, "unix64" },
|
|
{ RZ_PF_TIMEFMT_UNIXMS, 8, "unixms" },
|
|
{ RZ_PF_TIMEFMT_UNIXUS, 8, "unixus" },
|
|
{ RZ_PF_TIMEFMT_UNIXNS, 8, "unixns" },
|
|
{ RZ_PF_TIMEFMT_FILETIME, 8, "filetime" },
|
|
{ RZ_PF_TIMEFMT_DOS, 4, "dos" },
|
|
{ RZ_PF_TIMEFMT_HFS, 4, "hfs" },
|
|
{ RZ_PF_TIMEFMT_OLETIME, 8, "oletime" },
|
|
{ RZ_PF_TIMEFMT_WEBKIT, 8, "webkit" },
|
|
{ RZ_PF_TIMEFMT_COCOA, 8, "cocoa" },
|
|
};
|
|
for (size_t i = 0; i < sizeof(cases) / sizeof(cases[0]); i++) {
|
|
int sz = rz_pf_timefmt_size(cases[i].fmt);
|
|
mu_assert_eq(sz, cases[i].expected_sz, cases[i].name);
|
|
}
|
|
mu_end;
|
|
}
|
|
|
|
static bool test_pf_timestamp_filetime_decode(void) {
|
|
/* 0x01c126cea1130000 ~= 2001 epoch in Windows FILETIME. */
|
|
RzPfFormat *fmt = rz_pf_parse("t(filetime) ts");
|
|
const ut64 ft_ticks = 0x01c126cea1130000ULL;
|
|
ut8 buf[8];
|
|
for (int i = 0; i < 8; i++) {
|
|
buf[i] = (ut8)(ft_ticks >> (i * 8));
|
|
}
|
|
int count = 0;
|
|
RzPfValue *vals = rz_pf_read(fmt, buf, sizeof(buf), 0, NULL, &count);
|
|
mu_assert_notnull(vals, "decode filetime");
|
|
mu_assert_eq(count, 1, "1 value");
|
|
char *q = rz_pf_render(vals, count, RZ_PF_MODE_QUIET, NULL);
|
|
mu_assert_notnull(q, "quiet");
|
|
mu_assert_true(strstr(q, "2001") != NULL,
|
|
"filetime renders year 2001");
|
|
free(q);
|
|
rz_pf_values_free(vals, count);
|
|
rz_pf_format_free(fmt);
|
|
mu_end;
|
|
}
|
|
|
|
static bool test_pf_pointer_deref_via_callback(void) {
|
|
ut8 mem[0x300];
|
|
memset(mem, 0, sizeof(mem));
|
|
mem[0x100] = 0x00;
|
|
mem[0x101] = 0x02;
|
|
const char *body = "world";
|
|
memcpy(mem + 0x200, body, strlen(body) + 1);
|
|
|
|
RzPfCtx *ctx = rz_pf_ctx_new();
|
|
MemBuf mb = { .data = mem, .len = sizeof(mem) };
|
|
rz_pf_ctx_setup(ctx, NULL, false, 64, mem_read_at, &mb);
|
|
|
|
RzPfFormat *fmt = rz_pf_parse("s greeting");
|
|
int count = 0;
|
|
RzPfValue *vals = rz_pf_read(fmt, mem + 0x100, 8, 0, ctx, &count);
|
|
mu_assert_notnull(vals, "deref");
|
|
mu_assert_eq(count, 1, "1 value");
|
|
mu_assert_eq(vals[0].ptr_addr, 0x200, "ptr addr");
|
|
mu_assert_notnull(vals[0].scalars[0].v_str, "string decoded");
|
|
mu_assert_streq(vals[0].scalars[0].v_str, "world",
|
|
"deref body matches");
|
|
rz_pf_values_free(vals, count);
|
|
rz_pf_format_free(fmt);
|
|
rz_pf_ctx_free(ctx);
|
|
mu_end;
|
|
}
|
|
|
|
static bool test_pf_parse_empty_returns_empty(void) {
|
|
RzPfFormat *fmt = rz_pf_parse("");
|
|
mu_assert_notnull(fmt, "empty input gives empty format");
|
|
mu_assert_eq(fmt->nfields, 0, "zero fields");
|
|
rz_pf_format_free(fmt);
|
|
mu_end;
|
|
}
|
|
|
|
static bool test_pf_read_zero_length_buffer(void) {
|
|
RzPfFormat *fmt = rz_pf_parse("x4 a");
|
|
const ut8 buf[1] = { 0 };
|
|
int count = 0;
|
|
RzPfValue *vals = rz_pf_read(fmt, buf, 0, 0, NULL, &count);
|
|
if (vals) {
|
|
mu_assert_true(count <= 1, "no extra values");
|
|
rz_pf_values_free(vals, count);
|
|
}
|
|
rz_pf_format_free(fmt);
|
|
mu_end;
|
|
}
|
|
|
|
static bool test_pf_format_oneshot(void) {
|
|
const ut8 buf[] = { 0xEF, 0xBE, 0xAD, 0xDE };
|
|
char *out = rz_pf_format("x4 magic", buf, sizeof(buf), 0,
|
|
NULL, RZ_PF_MODE_TEXT, NULL);
|
|
mu_assert_notnull(out, "oneshot");
|
|
mu_assert_true(strstr(out, "magic") != NULL, "has name");
|
|
mu_assert_true(strstr(out, "0xdeadbeef") != NULL, "has value");
|
|
free(out);
|
|
mu_end;
|
|
}
|
|
|
|
/* Diagnostic / error-reporting tests -- exercise the position-aware
|
|
* RzPfError pipeline introduced for issue-driven error reporting. */
|
|
|
|
static bool test_pf_diag_unknown_specifier_position(void) {
|
|
/* '!' is not a known specifier; parser records a positioned
|
|
* syntax diagnostic. */
|
|
RzPfFormat *fmt = rz_pf_parse("x4!x4 a b");
|
|
mu_assert_notnull(fmt, "parse with bad mid-token");
|
|
mu_assert_true(fmt->nerrors >= 1, "diagnostic emitted");
|
|
bool found = false;
|
|
for (int i = 0; i < fmt->nerrors; i++) {
|
|
if (fmt->errors[i].category == RZ_PF_ERRC_SYNTAX && fmt->errors[i].pos == 2) {
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
mu_assert_true(found, "syntax error at position 2");
|
|
rz_pf_format_free(fmt);
|
|
mu_end;
|
|
}
|
|
|
|
static bool test_pf_diag_bits_without_width(void) {
|
|
RzPfFormat *fmt = rz_pf_parse(":x4 a b");
|
|
mu_assert_notnull(fmt, "parse with bare ':'");
|
|
bool found = false;
|
|
for (int i = 0; i < fmt->nerrors; i++) {
|
|
if (fmt->errors[i].category == RZ_PF_ERRC_SYNTAX && fmt->errors[i].pos == 0) {
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
mu_assert_true(found, "diagnostic at position 0");
|
|
rz_pf_format_free(fmt);
|
|
mu_end;
|
|
}
|
|
|
|
static bool test_pf_diag_align_without_n(void) {
|
|
RzPfFormat *fmt = rz_pf_parse("x1@x4 a b");
|
|
mu_assert_notnull(fmt, "parse with bare '@'");
|
|
bool found = false;
|
|
for (int i = 0; i < fmt->nerrors; i++) {
|
|
if (fmt->errors[i].category == RZ_PF_ERRC_SYNTAX && fmt->errors[i].pos == 2) {
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
mu_assert_true(found, "diagnostic at @ position");
|
|
rz_pf_format_free(fmt);
|
|
mu_end;
|
|
}
|
|
|
|
static bool test_pf_diag_deprecated_w(void) {
|
|
RzPfFormat *fmt = rz_pf_parse("w foo");
|
|
mu_assert_notnull(fmt, "parse deprecated w");
|
|
bool found = false;
|
|
for (int i = 0; i < fmt->nerrors; i++) {
|
|
if (fmt->errors[i].category == RZ_PF_ERRC_DEPRECATED && fmt->errors[i].severity == RZ_PF_ERR_WARN) {
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
mu_assert_true(found, "deprecation warning recorded");
|
|
rz_pf_format_free(fmt);
|
|
mu_end;
|
|
}
|
|
|
|
static bool test_pf_diag_tlv_missing_close_paren(void) {
|
|
RzPfFormat *fmt = rz_pf_parse("V(t=u1,l=u2 record");
|
|
mu_assert_notnull(fmt, "parse V(...");
|
|
bool found = false;
|
|
for (int i = 0; i < fmt->nerrors; i++) {
|
|
if (fmt->errors[i].category == RZ_PF_ERRC_SYNTAX && fmt->errors[i].severity == RZ_PF_ERR_ERROR && fmt->errors[i].message && strstr(fmt->errors[i].message, "closing")) {
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
mu_assert_true(found, "TLV missing-paren diagnostic");
|
|
rz_pf_format_free(fmt);
|
|
mu_end;
|
|
}
|
|
|
|
static bool test_pf_diag_tlv_bad_tag_type(void) {
|
|
RzPfFormat *fmt = rz_pf_parse("V(t=u3) record");
|
|
mu_assert_notnull(fmt, "parse V(t=u3)");
|
|
bool found = false;
|
|
for (int i = 0; i < fmt->nerrors; i++) {
|
|
if (fmt->errors[i].category == RZ_PF_ERRC_RANGE && fmt->errors[i].message && strstr(fmt->errors[i].message, "tag")) {
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
mu_assert_true(found, "u3 rejected as tag type");
|
|
rz_pf_format_free(fmt);
|
|
mu_end;
|
|
}
|
|
|
|
static bool test_pf_diag_tlv_unknown_key(void) {
|
|
RzPfFormat *fmt = rz_pf_parse("V(t=u1,xyz=1) record");
|
|
mu_assert_notnull(fmt, "parse V with bad key");
|
|
bool found = false;
|
|
for (int i = 0; i < fmt->nerrors; i++) {
|
|
if (fmt->errors[i].category == RZ_PF_ERRC_SEMANTIC && fmt->errors[i].message && strstr(fmt->errors[i].message, "xyz")) {
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
mu_assert_true(found, "unknown TLV key reported");
|
|
rz_pf_format_free(fmt);
|
|
mu_end;
|
|
}
|
|
|
|
static bool test_pf_diag_errors_to_string(void) {
|
|
RzPfFormat *fmt = rz_pf_parse("x4!x4 a b");
|
|
mu_assert_notnull(fmt, "parse");
|
|
char *s = rz_pf_format_errors_to_string(fmt);
|
|
mu_assert_notnull(s, "rendered diagnostics");
|
|
mu_assert_true(strstr(s, "syntax") != NULL, "category tag");
|
|
mu_assert_true(strstr(s, "x4!x4") != NULL, "source line");
|
|
mu_assert_true(strstr(s, "^") != NULL, "caret line");
|
|
free(s);
|
|
rz_pf_format_free(fmt);
|
|
mu_end;
|
|
}
|
|
|
|
static bool test_pf_diag_clean_parse_no_errors(void) {
|
|
RzPfFormat *fmt = rz_pf_parse("x4d4 a b");
|
|
mu_assert_notnull(fmt, "parse");
|
|
mu_assert_eq(fmt->nerrors, 0, "no spurious diagnostics");
|
|
char *s = rz_pf_format_errors_to_string(fmt);
|
|
mu_assert_null(s, "no string for clean parse");
|
|
rz_pf_format_free(fmt);
|
|
mu_end;
|
|
}
|
|
|
|
static bool test_pf_diag_source_captured(void) {
|
|
const char *src = "x4 a";
|
|
RzPfFormat *fmt = rz_pf_parse(src);
|
|
mu_assert_notnull(fmt, "parse");
|
|
mu_assert_notnull(fmt->source, "source captured");
|
|
mu_assert_streq(fmt->source, src, "source matches input");
|
|
rz_pf_format_free(fmt);
|
|
mu_end;
|
|
}
|
|
|
|
/* Ambiguity audit -- exercises the boundaries between superficially
|
|
* unambiguous field type. These tests document the parser's
|
|
* disambiguation rules so future changes don't accidentally regress
|
|
* them.
|
|
*
|
|
* b4 -> BIN32 (sized binary)
|
|
* B4 -> BITFIELD (typedb-resolved by default)
|
|
* B4(K=V) -> inline BITFIELD discriminated by `=` in parens
|
|
* t4 -> TIMESTAMP unix32 (deprecated bare form)
|
|
* t(unix32) -> TIMESTAMP unix32 (canonical)
|
|
* z(utf16le) -> ZSTRING utf16-le
|
|
* z(utf8)[N] -> length-prefixed zstring
|
|
* V -> TLV with defaults
|
|
* V(...) -> TLV with key=value spec
|
|
*/
|
|
|
|
static bool test_pf_ambig_b4_vs_B4(void) {
|
|
/* Both lowercase and uppercase `b4`/`B4` produce BIN32; the
|
|
* difference is endian. Their interpretation as BITFIELD
|
|
* happens only when a (typename) qualifier is attached, which
|
|
* the next test exercises. */
|
|
RzPfFormat *bin = rz_pf_parse("b4 lower");
|
|
RzPfFormat *be = rz_pf_parse("B4 upper");
|
|
mu_assert_notnull(bin, "lowercase b4");
|
|
mu_assert_notnull(be, "uppercase B4");
|
|
mu_assert_eq(bin->fields[0].type, RZ_PF_BIN32, "b4 = BIN32 LE");
|
|
mu_assert_eq(bin->fields[0].endian, RZ_PF_ENDIAN_LE, "b4 LE");
|
|
mu_assert_eq(be->fields[0].type, RZ_PF_BIN32, "B4 = BIN32 BE");
|
|
mu_assert_eq(be->fields[0].endian, RZ_PF_ENDIAN_BE, "B4 BE");
|
|
rz_pf_format_free(bin);
|
|
rz_pf_format_free(be);
|
|
mu_end;
|
|
}
|
|
|
|
static bool test_pf_ambig_inline_bitfield_vs_typed(void) {
|
|
/* `B4(R=1,W=2)` carries `=` inside parens -> inline bitfield
|
|
* (the parser discriminates on the presence of `=`).
|
|
* `B4 (perm) flags` references the typedb-named `perm` bitfield. */
|
|
RzPfFormat *inline_bf = rz_pf_parse("B4(R=1,W=2,X=4) perm");
|
|
RzPfFormat *typed_bf = rz_pf_parse("B4 (perm)flags");
|
|
mu_assert_notnull(inline_bf, "inline");
|
|
mu_assert_notnull(typed_bf, "typed");
|
|
mu_assert_eq(inline_bf->fields[0].type, RZ_PF_BITFIELD,
|
|
"inline is BITFIELD");
|
|
mu_assert_eq(inline_bf->fields[0].bitflag_count, 3,
|
|
"3 inline bitflags");
|
|
mu_assert_null(inline_bf->fields[0].type_name,
|
|
"inline has no typedb name");
|
|
/* Typed B4 (perm) attaches the typedb name. Whether the
|
|
* field's discrim type ends up as BITFIELD or sized BIN
|
|
* depends on whether resolver runs at parse-time vs read-time;
|
|
* the contract we lock down here is that the type_name is
|
|
* recorded so the read pass can dispatch correctly. */
|
|
mu_assert_streq(typed_bf->fields[0].type_name, "perm",
|
|
"typed has typedb name");
|
|
mu_assert_eq(typed_bf->fields[0].bitflag_count, 0,
|
|
"typed has no inline bitflags");
|
|
rz_pf_format_free(inline_bf);
|
|
rz_pf_format_free(typed_bf);
|
|
mu_end;
|
|
}
|
|
|
|
static bool test_pf_ambig_t4_vs_t_paren(void) {
|
|
/* `t4` should map to deprecated unix32; `t(unix32)` is canonical. */
|
|
RzPfFormat *legacy = rz_pf_parse("t4 ts1");
|
|
RzPfFormat *canon = rz_pf_parse("t(unix32) ts2");
|
|
mu_assert_notnull(legacy, "t4");
|
|
mu_assert_notnull(canon, "t(unix32)");
|
|
mu_assert_eq(legacy->fields[0].type, RZ_PF_TIMESTAMP,
|
|
"t4 is TIMESTAMP");
|
|
mu_assert_eq(legacy->fields[0].timefmt, RZ_PF_TIMEFMT_UNIX32,
|
|
"t4 = unix32");
|
|
mu_assert_eq(canon->fields[0].type, RZ_PF_TIMESTAMP,
|
|
"t(unix32) is TIMESTAMP");
|
|
mu_assert_eq(canon->fields[0].timefmt, RZ_PF_TIMEFMT_UNIX32,
|
|
"t(unix32) = unix32");
|
|
/* The deprecated form should produce a deprecation warning. */
|
|
bool has_dep = false;
|
|
for (int i = 0; i < legacy->nerrors; i++) {
|
|
if (legacy->errors[i].category == RZ_PF_ERRC_DEPRECATED) {
|
|
has_dep = true;
|
|
break;
|
|
}
|
|
}
|
|
mu_assert_true(has_dep, "t4 emits deprecation warning");
|
|
rz_pf_format_free(legacy);
|
|
rz_pf_format_free(canon);
|
|
mu_end;
|
|
}
|
|
|
|
static bool test_pf_ambig_z_vs_z_paren_vs_z_lenprefix(void) {
|
|
/* `z` = NUL-terminated string with default encoding;
|
|
* `z(utf16le)` = encoding override; `z[2]` = length-prefixed. */
|
|
RzPfFormat *bare = rz_pf_parse("z s1");
|
|
RzPfFormat *enc = rz_pf_parse("z(utf16le) s2");
|
|
RzPfFormat *lp = rz_pf_parse("z[2] s3");
|
|
mu_assert_notnull(bare, "bare z");
|
|
mu_assert_notnull(enc, "z(...)");
|
|
mu_assert_notnull(lp, "z[2]");
|
|
mu_assert_eq(bare->fields[0].type, RZ_PF_ZSTRING, "z=ZSTRING");
|
|
mu_assert_eq(enc->fields[0].type, RZ_PF_ZSTRING,
|
|
"z(utf16le)=ZSTRING");
|
|
mu_assert_eq(enc->fields[0].encoding, RZ_STRING_ENC_UTF16LE,
|
|
"utf16le encoding stored");
|
|
mu_assert_eq(lp->fields[0].type, RZ_PF_ZSTRING,
|
|
"z[2]=ZSTRING");
|
|
mu_assert_eq(lp->fields[0].str_len_prefix, 2,
|
|
"z[2] has 2-byte prefix");
|
|
rz_pf_format_free(bare);
|
|
rz_pf_format_free(enc);
|
|
rz_pf_format_free(lp);
|
|
mu_end;
|
|
}
|
|
|
|
static bool test_pf_ambig_V_bare_vs_V_paren(void) {
|
|
/* Bare `V` uses defaults; `V(...)` configures the TLV. */
|
|
RzPfFormat *bare = rz_pf_parse("V rec1");
|
|
RzPfFormat *full = rz_pf_parse("V(t=u2,l=u4) rec2");
|
|
mu_assert_notnull(bare, "V");
|
|
mu_assert_notnull(full, "V(...)");
|
|
mu_assert_eq(bare->fields[0].type, RZ_PF_TLV, "V=TLV");
|
|
mu_assert_eq(bare->fields[0].tlv_spec->tag_size, 1,
|
|
"V defaults to t=u1");
|
|
mu_assert_eq(bare->fields[0].tlv_spec->len_size, 2,
|
|
"V defaults to l=u2");
|
|
mu_assert_eq(full->fields[0].tlv_spec->tag_size, 2,
|
|
"V(t=u2) uses t=u2");
|
|
mu_assert_eq(full->fields[0].tlv_spec->len_size, 4,
|
|
"V(l=u4) uses l=u4");
|
|
rz_pf_format_free(bare);
|
|
rz_pf_format_free(full);
|
|
mu_end;
|
|
}
|
|
|
|
static bool test_pf_ambig_struct_vs_skip(void) {
|
|
/* `?(name)var` is a nested struct; `?` alone is also a struct.
|
|
* `.` is SKIP. They differ in `type`. */
|
|
RzPfFormat *st = rz_pf_parse("?(inner) nested");
|
|
RzPfFormat *sk = rz_pf_parse("x1.x1 a b");
|
|
mu_assert_notnull(st, "?(...)");
|
|
mu_assert_notnull(sk, "x.x");
|
|
mu_assert_eq(st->fields[0].type, RZ_PF_STRUCT,
|
|
"? is STRUCT");
|
|
mu_assert_streq(st->fields[0].type_name, "inner",
|
|
"struct typename");
|
|
mu_assert_eq(sk->fields[1].type, RZ_PF_SKIP,
|
|
". is SKIP");
|
|
rz_pf_format_free(st);
|
|
rz_pf_format_free(sk);
|
|
mu_end;
|
|
}
|
|
|
|
static bool test_pf_ambig_x4_vs_x(void) {
|
|
/* `x` (bare) is deprecated for HEXDUMP; `x4` is sized HEX32.
|
|
* Since I retired bare-x in the new DSL, bare `x` is now a
|
|
* deprecated alias for HEX32 by default. The sized form is
|
|
* the canonical one. */
|
|
RzPfFormat *sized = rz_pf_parse("x4 a");
|
|
mu_assert_notnull(sized, "x4");
|
|
mu_assert_eq(sized->fields[0].type, RZ_PF_HEX32, "x4=HEX32");
|
|
mu_assert_eq(rz_pf_field_size(sized->fields[0].type), 4,
|
|
"x4 is 4 bytes");
|
|
rz_pf_format_free(sized);
|
|
mu_end;
|
|
}
|
|
|
|
static bool test_pf_ambig_d_vs_D_endian(void) {
|
|
/* `d4` LE-decimal, `D4` BE-decimal. */
|
|
RzPfFormat *le = rz_pf_parse("d4 a");
|
|
RzPfFormat *be = rz_pf_parse("D4 a");
|
|
mu_assert_notnull(le, "d4");
|
|
mu_assert_notnull(be, "D4");
|
|
mu_assert_eq(le->fields[0].endian, RZ_PF_ENDIAN_LE, "d=LE");
|
|
mu_assert_eq(be->fields[0].endian, RZ_PF_ENDIAN_BE, "D=BE");
|
|
rz_pf_format_free(le);
|
|
rz_pf_format_free(be);
|
|
mu_end;
|
|
}
|
|
|
|
static bool test_pf_ambig_align_vs_skip(void) {
|
|
/* `@4` aligns to next multiple of 4; `.` skips one byte. Both
|
|
* are name-less. */
|
|
RzPfFormat *al = rz_pf_parse("x1@4x1 a b");
|
|
RzPfFormat *sk = rz_pf_parse("x1.x1 a b");
|
|
mu_assert_notnull(al, "@4");
|
|
mu_assert_notnull(sk, ".");
|
|
mu_assert_eq(al->fields[1].type, RZ_PF_ALIGN, "@ is ALIGN");
|
|
mu_assert_eq(al->fields[1].align_to, 4, "align_to=4");
|
|
mu_assert_eq(sk->fields[1].type, RZ_PF_SKIP, ". is SKIP");
|
|
rz_pf_format_free(al);
|
|
rz_pf_format_free(sk);
|
|
mu_end;
|
|
}
|
|
|
|
static bool test_pf_ambig_bits_lt_gt(void) {
|
|
/* `:8` defaults to MSB-first; `:8<` is LSB-first explicit;
|
|
* `:8>` is MSB-first explicit. */
|
|
RzPfFormat *def = rz_pf_parse(":8 a");
|
|
RzPfFormat *lsb = rz_pf_parse(":8< a");
|
|
RzPfFormat *msb = rz_pf_parse(":8> a");
|
|
mu_assert_notnull(def, ":8");
|
|
mu_assert_notnull(lsb, ":8<");
|
|
mu_assert_notnull(msb, ":8>");
|
|
mu_assert_eq(def->fields[0].type, RZ_PF_BITS, "default BITS");
|
|
mu_assert_eq(def->fields[0].bit_order, RZ_PF_BITORDER_MSB,
|
|
"default = MSB");
|
|
mu_assert_eq(lsb->fields[0].bit_order, RZ_PF_BITORDER_LSB,
|
|
"< = LSB");
|
|
mu_assert_eq(msb->fields[0].bit_order, RZ_PF_BITORDER_MSB,
|
|
"> = MSB");
|
|
rz_pf_format_free(def);
|
|
rz_pf_format_free(lsb);
|
|
rz_pf_format_free(msb);
|
|
mu_end;
|
|
}
|
|
|
|
/* Palette in render (issue rizinorg/rizin#782) -- verifies that the
|
|
* text renderer emits ANSI escapes inline when an RzPfPalette is
|
|
* supplied via RzPfRenderOpts. The renderer with NULL palette
|
|
* remains color-free so integration tests keep matching exact bytes. */
|
|
|
|
static const RzPfPalette pal_test = {
|
|
.offset = "\x1b[32m",
|
|
.name = "\x1b[33m",
|
|
.endian = "\x1b[34m",
|
|
.hex_literal = "\x1b[36m",
|
|
.label = "\x1b[35m",
|
|
.reset = "\x1b[0m",
|
|
};
|
|
|
|
static bool test_pf_palette_text_basic(void) {
|
|
const ut8 buf[] = { 0xEF, 0xBE, 0xAD, 0xDE };
|
|
RzPfRenderOpts opts = { .palette = &pal_test };
|
|
char *colored = rz_pf_format("x4 magic", buf, sizeof(buf), 0,
|
|
NULL, RZ_PF_MODE_TEXT, &opts);
|
|
mu_assert_notnull(colored, "render");
|
|
mu_assert_true(strstr(colored, "\x1b[32m") != NULL,
|
|
"green offset escape present");
|
|
mu_assert_true(strstr(colored, "\x1b[33mmagic\x1b[0m") != NULL,
|
|
"yellow name");
|
|
mu_assert_true(strstr(colored, "\x1b[34m [LE]\x1b[0m") != NULL,
|
|
"blue endian tag (trailing)");
|
|
mu_assert_true(strstr(colored, "\x1b[36m0xdeadbeef\x1b[0m") != NULL,
|
|
"cyan hex literal");
|
|
free(colored);
|
|
mu_end;
|
|
}
|
|
|
|
static bool test_pf_palette_null_no_color(void) {
|
|
/* NULL palette yields canonical color-free output. */
|
|
const ut8 buf[] = { 0xEF, 0xBE, 0xAD, 0xDE };
|
|
char *plain = rz_pf_format("x4 magic", buf, sizeof(buf), 0,
|
|
NULL, RZ_PF_MODE_TEXT, NULL);
|
|
mu_assert_notnull(plain, "render");
|
|
mu_assert_true(strchr(plain, '\x1b') == NULL,
|
|
"no escape codes when palette is NULL");
|
|
free(plain);
|
|
mu_end;
|
|
}
|
|
|
|
static bool test_pf_palette_partial_palette(void) {
|
|
/* A palette with only some fields set highlights only those.
|
|
* Other tokens are uncolored. */
|
|
const ut8 buf[] = { 0xEF, 0xBE, 0xAD, 0xDE };
|
|
RzPfPalette partial = { 0 };
|
|
partial.hex_literal = "\x1b[36m";
|
|
partial.reset = "\x1b[0m";
|
|
RzPfRenderOpts opts = { .palette = &partial };
|
|
char *out = rz_pf_format("x4 magic", buf, sizeof(buf), 0,
|
|
NULL, RZ_PF_MODE_TEXT, &opts);
|
|
mu_assert_notnull(out, "render");
|
|
mu_assert_true(strstr(out, "\x1b[36m0xdeadbeef\x1b[0m") != NULL,
|
|
"hex literal colored");
|
|
/* "magic" appears uncolored (no escape before it). */
|
|
const char *m = strstr(out, "magic");
|
|
mu_assert_notnull(m, "name present");
|
|
mu_assert_true(m == out || m[-1] != 'm',
|
|
"name not preceded by escape sequence");
|
|
free(out);
|
|
mu_end;
|
|
}
|
|
|
|
/* When the palette is NULL, the renderer must not emit any color
|
|
* escapes itself. Even when the input bytes contain ESC characters
|
|
* (which would otherwise leak through if the renderer accidentally
|
|
* passed raw bytes), no ESC byte should appear in the renderer
|
|
* scaffolding (before the value). The value column escapes
|
|
* non-printable bytes (including ESC) as \xNN so the rendered
|
|
* string is always 7-bit-clean. Surfaced by the property-based
|
|
* test harness (pf-property-harness). */
|
|
static bool test_pf_palette_null_passes_through_input_esc(void) {
|
|
const ut8 buf[] = { 'A', '\x1b', 'B', 0 };
|
|
char *out = rz_pf_format("z s", buf, sizeof(buf), 0,
|
|
NULL, RZ_PF_MODE_TEXT, NULL);
|
|
mu_assert_notnull(out, "render");
|
|
/* Whole output must be ESC-free with NULL palette. */
|
|
mu_assert_true(strchr(out, '\x1b') == NULL,
|
|
"no ESC bytes in output when palette is NULL");
|
|
/* Renderer must escape the input ESC byte as the literal
|
|
* sequence \x1b in the value cell. */
|
|
mu_assert_true(strstr(out, "\\x1b") != NULL,
|
|
"input ESC byte escaped as \\x1b in value");
|
|
free(out);
|
|
mu_end;
|
|
}
|
|
|
|
/* The DOT renderer must call into rz_pf_timestamp_format_str for
|
|
* timestamp fields rather than falling through to scalar_text(),
|
|
* which would emit "?". Surfaced while building the PR showcase
|
|
* with `pfd "t(unix32)" created_at`. */
|
|
static bool test_pf_render_dot_timestamp(void) {
|
|
/* 2021-02-09 13:48:14 UTC = 0x6022929e */
|
|
const ut8 buf[] = { 0x9e, 0x92, 0x22, 0x60 };
|
|
char *out = rz_pf_format("t(unix32) ts", buf, sizeof(buf), 0,
|
|
NULL, RZ_PF_MODE_DOT, NULL);
|
|
mu_assert_notnull(out, "render");
|
|
mu_assert_true(strstr(out, "2021-02-09") != NULL,
|
|
"date present in DOT output");
|
|
mu_assert_true(strstr(out, ">?") == NULL,
|
|
"no fallback placeholder");
|
|
free(out);
|
|
mu_end;
|
|
}
|
|
|
|
/* Column-aligned DOT layout: each row (offset / type / name / value)
|
|
* must contain the same number of cells as the visible-field count,
|
|
* so Graphviz produces a regular grid with aligned columns. */
|
|
static bool test_pf_render_dot_column_aligned(void) {
|
|
const ut8 buf[] = { 0x7f, 0x45, 0x4c, 0x46 };
|
|
char *out = rz_pf_format("x1x1x1x1 magic class data version",
|
|
buf, sizeof(buf), 0, NULL, RZ_PF_MODE_DOT, NULL);
|
|
mu_assert_notnull(out, "render");
|
|
/* Each row group is present exactly once and contains the
|
|
* header + 4 field cells. */
|
|
mu_assert_true(strstr(out, "{offset|0x0|0x1|0x2|0x3}") != NULL,
|
|
"offset row aligned");
|
|
mu_assert_true(strstr(out, "{type|x|x|x|x}") != NULL,
|
|
"type row aligned");
|
|
mu_assert_true(strstr(out,
|
|
"{name|<magic>magic|<class>class|<data>data|<version>version}") != NULL,
|
|
"name row aligned");
|
|
mu_assert_true(strstr(out, "{value|0x7f|0x45|0x4c|0x46}") != NULL,
|
|
"value row aligned");
|
|
free(out);
|
|
mu_end;
|
|
}
|
|
|
|
/* When only one field passes the filter, columns degenerate to a
|
|
* single value-cell per row, but the layout structure stays the
|
|
* same so a downstream tool can rely on the row order. */
|
|
static bool test_pf_render_dot_single_field(void) {
|
|
const ut8 buf[] = { 0xde, 0xad, 0xbe, 0xef };
|
|
char *out = rz_pf_format("x4 magic", buf, sizeof(buf), 0,
|
|
NULL, RZ_PF_MODE_DOT, NULL);
|
|
mu_assert_notnull(out, "render");
|
|
mu_assert_true(strstr(out, "{offset|0x0}") != NULL,
|
|
"offset row single");
|
|
mu_assert_true(strstr(out, "{type|x}") != NULL,
|
|
"type row single");
|
|
mu_assert_true(strstr(out, "{name|<magic>magic}") != NULL,
|
|
"name row single");
|
|
mu_assert_true(strstr(out, "{value|0xefbeadde}") != NULL,
|
|
"value row single");
|
|
free(out);
|
|
mu_end;
|
|
}
|
|
|
|
/* When no fields are visible (everything filtered out or empty),
|
|
* we still emit a well-formed digraph -- just without any rows. */
|
|
static bool test_pf_render_dot_empty(void) {
|
|
const ut8 buf[] = { 0xde, 0xad, 0xbe, 0xef };
|
|
RzPfRenderOpts opts = { .field_filter = "nonexistent" };
|
|
char *out = rz_pf_format("x4 magic", buf, sizeof(buf), 0,
|
|
NULL, RZ_PF_MODE_DOT, &opts);
|
|
mu_assert_notnull(out, "render");
|
|
mu_assert_true(strstr(out, "digraph g") != NULL,
|
|
"well-formed digraph header");
|
|
mu_assert_true(strstr(out, "{offset") == NULL,
|
|
"no offset row when nothing visible");
|
|
free(out);
|
|
mu_end;
|
|
}
|
|
|
|
/* TLV in DOT mode: was falling through to scalar_text and emitting
|
|
* "?". Now renders "tag=0xN len=M" in the value cell. */
|
|
static bool test_pf_render_dot_tlv(void) {
|
|
/* tag=0x0005 (BE), length=0x0004 (BE), value=de ad be ef */
|
|
const ut8 buf[] = { 0x00, 0x05, 0x00, 0x04, 0xde, 0xad, 0xbe, 0xef };
|
|
char *out = rz_pf_format("V(t=u2,l=u2,e=be) rec",
|
|
buf, sizeof(buf), 0, NULL, RZ_PF_MODE_DOT, NULL);
|
|
mu_assert_notnull(out, "render");
|
|
mu_assert_true(strstr(out, "tag=0x5 len=4") != NULL,
|
|
"TLV header in value cell");
|
|
mu_assert_true(strstr(out, "{value|?}") == NULL,
|
|
"no fallback placeholder for TLV");
|
|
free(out);
|
|
mu_end;
|
|
}
|
|
|
|
/* Empty input must parse to an empty format object rather than NULL.
|
|
* Surfaced by p_parse_empty_input in the property harness; the old
|
|
* behaviour was an rz_return_val_if_fail() that forced every caller
|
|
* to special-case the empty string. */
|
|
static bool test_pf_parse_empty_input(void) {
|
|
RzPfFormat *fmt = rz_pf_parse("");
|
|
mu_assert_notnull(fmt, "empty input gives empty format, not NULL");
|
|
mu_assert_eq(fmt->nfields, 0, "zero fields");
|
|
mu_assert_eq(fmt->nerrors, 0, "zero diagnostics");
|
|
mu_assert_notnull(fmt->source, "source preserved");
|
|
mu_assert_streq(fmt->source, "", "source is empty");
|
|
rz_pf_format_free(fmt);
|
|
mu_end;
|
|
}
|
|
|
|
/* NULL input must yield NULL safely (no crash, no diagnostic). */
|
|
static bool test_pf_parse_null_input(void) {
|
|
RzPfFormat *fmt = rz_pf_parse(NULL);
|
|
mu_assert_null(fmt, "NULL input -> NULL");
|
|
mu_end;
|
|
}
|
|
|
|
/* Bitfield entry without '=' must produce an ERROR diagnostic
|
|
* instead of being silently skipped. Surfaced by the malformed
|
|
* input generator with B4(=1). */
|
|
static bool test_pf_parse_bitfield_missing_eq_diagnostic(void) {
|
|
RzPfFormat *fmt = rz_pf_parse("B4(BADNAME) flags");
|
|
mu_assert_notnull(fmt, "parse returns object");
|
|
int n_err = 0;
|
|
for (int i = 0; i < fmt->nerrors; i++) {
|
|
if (fmt->errors[i].severity == RZ_PF_ERR_ERROR) {
|
|
n_err++;
|
|
}
|
|
}
|
|
mu_assert_true(n_err > 0,
|
|
"bitfield entry without '=' produces ERROR diagnostic");
|
|
rz_pf_format_free(fmt);
|
|
mu_end;
|
|
}
|
|
|
|
/* Bitfield entry with empty name/value (e.g. B4(=1) or B4(a=))
|
|
* must also produce an ERROR diagnostic. */
|
|
static bool test_pf_parse_bitfield_empty_field_diagnostic(void) {
|
|
RzPfFormat *fmt = rz_pf_parse("B4(=1) flags");
|
|
mu_assert_notnull(fmt, "parse returns object");
|
|
int n_err = 0;
|
|
for (int i = 0; i < fmt->nerrors; i++) {
|
|
if (fmt->errors[i].severity == RZ_PF_ERR_ERROR) {
|
|
n_err++;
|
|
}
|
|
}
|
|
mu_assert_true(n_err > 0,
|
|
"bitfield entry with empty name produces ERROR");
|
|
rz_pf_format_free(fmt);
|
|
mu_end;
|
|
}
|
|
|
|
/* Inline bitfield render must be deterministic across calls.
|
|
* Surfaced by the property harness `render_is_idempotent`: the
|
|
* legacy rz_pf_format() freed the parsed format before rendering,
|
|
* but RzPfValue::bitflags borrowed pointers into that format's
|
|
* RzPfBitflag::name strings. The use-after-free caused the
|
|
* bitfield's flag-name column to print uninitialized memory and
|
|
* differ on every call. */
|
|
static bool test_pf_render_bitfield_inline_idempotent(void) {
|
|
const ut8 buf[] = { 0x01, 0x02, 0x03, 0x07 };
|
|
char *a = rz_pf_format("B4(R=1,W=2,X=4) flags",
|
|
buf, sizeof(buf), 0, NULL, RZ_PF_MODE_TEXT, NULL);
|
|
char *b = rz_pf_format("B4(R=1,W=2,X=4) flags",
|
|
buf, sizeof(buf), 0, NULL, RZ_PF_MODE_TEXT, NULL);
|
|
mu_assert_notnull(a, "first render");
|
|
mu_assert_notnull(b, "second render");
|
|
mu_assert_streq(a, b, "rendering must be deterministic");
|
|
mu_assert_true(strstr(a, "R | W | X") != NULL,
|
|
"flag names are decoded, not uninitialized memory");
|
|
free(a);
|
|
free(b);
|
|
mu_end;
|
|
}
|
|
|
|
/* --------------------------------------------------------------------
|
|
* RzStructuredData renderer (rz_pf_render_sd)
|
|
* -------------------------------------------------------------------- */
|
|
|
|
/* Helper: parse + read + render to SDB, then serialise to JSON for easy
|
|
* assertions. Returns an owned JSON string (caller frees) or NULL. */
|
|
static char *sd_json_of(const char *fmt, const ut8 *buf, int len) {
|
|
RzPfFormat *f = rz_pf_parse(fmt);
|
|
if (!f) {
|
|
return NULL;
|
|
}
|
|
int n = 0;
|
|
RzPfValue *vals = rz_pf_read(f, buf, len, 0, NULL, &n);
|
|
char *json = NULL;
|
|
if (vals && n > 0) {
|
|
RzStructuredData *sd = rz_pf_render_sd(vals, n, NULL);
|
|
if (sd) {
|
|
json = rz_structured_data_to_json(sd);
|
|
rz_structured_data_free(sd);
|
|
}
|
|
}
|
|
rz_pf_values_free(vals, n);
|
|
rz_pf_format_free(f);
|
|
return json;
|
|
}
|
|
|
|
/* Scalars map to typed values keyed by field name. */
|
|
static bool test_pf_render_sd_scalars(void) {
|
|
const ut8 buf[] = { 0x7f, 0x45, 0x4c, 0x46, 0x2a, 0, 0, 0, 'h', 'i', 0 };
|
|
char *json = sd_json_of("x4d4z magic count name", buf, sizeof(buf));
|
|
mu_assert_notnull(json, "sd json");
|
|
mu_assert_true(strstr(json, "\"magic\":1179403647") != NULL,
|
|
"x4 magic as unsigned");
|
|
mu_assert_true(strstr(json, "\"count\":42") != NULL,
|
|
"d4 count as signed");
|
|
mu_assert_true(strstr(json, "\"name\":\"hi\"") != NULL,
|
|
"z name as string");
|
|
free(json);
|
|
mu_end;
|
|
}
|
|
|
|
/* A bitvector becomes an array of 0/1 entries. */
|
|
static bool test_pf_render_sd_bitvec(void) {
|
|
const ut8 buf[] = { 0xAB };
|
|
char *json = sd_json_of("v(8) flags", buf, sizeof(buf));
|
|
mu_assert_notnull(json, "sd json");
|
|
mu_assert_true(strstr(json, "\"flags\":[1,0,1,0,1,0,1,1]") != NULL,
|
|
"bitvector as 0/1 array");
|
|
free(json);
|
|
mu_end;
|
|
}
|
|
|
|
/* A GUID renders as a colon-separated byte block. */
|
|
static bool test_pf_render_sd_guid(void) {
|
|
const ut8 buf[] = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
|
|
0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10 };
|
|
char *json = sd_json_of("G uuid", buf, sizeof(buf));
|
|
mu_assert_notnull(json, "sd json");
|
|
mu_assert_true(strstr(json, "01:02:03:04") != NULL,
|
|
"GUID as colon-separated bytes");
|
|
free(json);
|
|
mu_end;
|
|
}
|
|
|
|
/* A nested struct becomes a sub-map carrying a "_type" tag. */
|
|
static bool test_pf_render_sd_nested_struct(void) {
|
|
RzTypeDB *typedb = rz_type_db_new();
|
|
mu_assert_notnull(typedb, "typedb");
|
|
rz_type_db_format_set(typedb, "point", "d4d4 x y");
|
|
|
|
const ut8 buf[] = { 0x01, 0, 0, 0, 0x02, 0, 0, 0 };
|
|
RzPfCtx *ctx = rz_pf_ctx_new();
|
|
rz_pf_ctx_setup(ctx, typedb, false, 32, NULL, NULL);
|
|
RzPfFormat *f = rz_pf_parse("? (point)origin");
|
|
int n = 0;
|
|
RzPfValue *vals = rz_pf_read(f, buf, sizeof(buf), 0, ctx, &n);
|
|
RzStructuredData *sd = rz_pf_render_sd(vals, n, NULL);
|
|
char *json = sd ? rz_structured_data_to_json(sd) : NULL;
|
|
mu_assert_notnull(json, "sd json");
|
|
mu_assert_true(strstr(json, "\"origin\":{") != NULL,
|
|
"nested struct as sub-map");
|
|
mu_assert_true(strstr(json, "\"_type\":\"point\"") != NULL,
|
|
"struct type recorded");
|
|
mu_assert_true(strstr(json, "\"x\":1") != NULL, "child x");
|
|
mu_assert_true(strstr(json, "\"y\":2") != NULL, "child y");
|
|
free(json);
|
|
rz_structured_data_free(sd);
|
|
rz_pf_values_free(vals, n);
|
|
rz_pf_format_free(f);
|
|
rz_pf_ctx_free(ctx);
|
|
rz_type_db_free(typedb);
|
|
mu_end;
|
|
}
|
|
|
|
/* Unnamed fields get stable field_<index> keys. */
|
|
static bool test_pf_render_sd_autoname(void) {
|
|
const ut8 buf[] = { 0x11, 0x22 };
|
|
char *json = sd_json_of("x1x1", buf, sizeof(buf));
|
|
mu_assert_notnull(json, "sd json");
|
|
mu_assert_true(strstr(json, "\"field_0\":") != NULL,
|
|
"first unnamed field auto-keyed");
|
|
mu_assert_true(strstr(json, "\"field_1\":") != NULL,
|
|
"second unnamed field auto-keyed");
|
|
free(json);
|
|
mu_end;
|
|
}
|
|
|
|
/* Signed, unsigned-decimal, and float scalars keep their kind. */
|
|
static bool test_pf_render_sd_signed_and_float(void) {
|
|
/* d4 = -1 (0xffffffff LE), u4 = 5, f4 = 1.5 */
|
|
const ut8 buf[] = { 0xff, 0xff, 0xff, 0xff,
|
|
0x05, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0xc0, 0x3f };
|
|
char *json = sd_json_of("d4u4f4 neg pos frac", buf, sizeof(buf));
|
|
mu_assert_notnull(json, "sd json");
|
|
mu_assert_true(strstr(json, "\"neg\":-1") != NULL,
|
|
"signed decimal stays signed");
|
|
mu_assert_true(strstr(json, "\"pos\":5") != NULL,
|
|
"unsigned decimal");
|
|
mu_assert_true(strstr(json, "\"frac\":1.5") != NULL,
|
|
"float renders as double");
|
|
free(json);
|
|
mu_end;
|
|
}
|
|
|
|
/* A fixed-count array of scalars renders as a JSON array. */
|
|
static bool test_pf_render_sd_scalar_array(void) {
|
|
const ut8 buf[] = { 0x0a, 0x14, 0x1e };
|
|
char *json = sd_json_of("[3]x1 items", buf, sizeof(buf));
|
|
mu_assert_notnull(json, "sd json");
|
|
mu_assert_true(strstr(json, "\"items\":[10,20,30]") != NULL,
|
|
"array of scalars as JSON array");
|
|
free(json);
|
|
mu_end;
|
|
}
|
|
|
|
/* The field filter restricts output to the named field. */
|
|
static bool test_pf_render_sd_filter(void) {
|
|
const ut8 buf[] = { 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00 };
|
|
RzPfFormat *f = rz_pf_parse("x4x4 alpha beta");
|
|
int n = 0;
|
|
RzPfValue *vals = rz_pf_read(f, buf, sizeof(buf), 0, NULL, &n);
|
|
RzPfRenderOpts opts = { 0 };
|
|
opts.field_filter = "beta";
|
|
RzStructuredData *sd = rz_pf_render_sd(vals, n, &opts);
|
|
char *json = sd ? rz_structured_data_to_json(sd) : NULL;
|
|
mu_assert_notnull(json, "sd json");
|
|
mu_assert_true(strstr(json, "\"beta\":") != NULL,
|
|
"filtered field present");
|
|
mu_assert_true(strstr(json, "\"alpha\":") == NULL,
|
|
"non-matching field absent");
|
|
free(json);
|
|
rz_structured_data_free(sd);
|
|
rz_pf_values_free(vals, n);
|
|
rz_pf_format_free(f);
|
|
mu_end;
|
|
}
|
|
|
|
/* A timestamp emits both a formatted string and a "<name>_raw" sibling. */
|
|
static bool test_pf_render_sd_timestamp(void) {
|
|
/* unix32 = 0 -> 1970-01-01T00:00:00Z */
|
|
const ut8 buf[] = { 0x00, 0x00, 0x00, 0x00 };
|
|
char *json = sd_json_of("t(unix32) when", buf, sizeof(buf));
|
|
mu_assert_notnull(json, "sd json");
|
|
mu_assert_true(strstr(json, "\"when\":") != NULL,
|
|
"timestamp formatted string present");
|
|
mu_assert_true(strstr(json, "\"when_raw\":0") != NULL,
|
|
"raw timestamp sibling present");
|
|
free(json);
|
|
mu_end;
|
|
}
|
|
|
|
/* The tree round-trips to YAML as well as JSON. */
|
|
static bool test_pf_render_sd_yaml(void) {
|
|
const ut8 buf[] = { 0x2a, 0x00, 0x00, 0x00 };
|
|
RzPfFormat *f = rz_pf_parse("d4 answer");
|
|
int n = 0;
|
|
RzPfValue *vals = rz_pf_read(f, buf, sizeof(buf), 0, NULL, &n);
|
|
RzStructuredData *sd = rz_pf_render_sd(vals, n, NULL);
|
|
char *yaml = sd ? rz_structured_data_to_yaml(sd) : NULL;
|
|
mu_assert_notnull(yaml, "sd yaml");
|
|
mu_assert_true(strstr(yaml, "answer: 42") != NULL,
|
|
"YAML serialisation of the SD tree");
|
|
free(yaml);
|
|
rz_structured_data_free(sd);
|
|
rz_pf_values_free(vals, n);
|
|
rz_pf_format_free(f);
|
|
mu_end;
|
|
}
|
|
|
|
/* A format whose only fields are skip/align produces a valid but empty
|
|
* top-level map (an object with no members), not NULL and not a crash. */
|
|
static bool test_pf_render_sd_only_skips(void) {
|
|
const ut8 buf[] = { 0x11, 0x22, 0x33, 0x44 };
|
|
RzPfFormat *f = rz_pf_parse("....");
|
|
mu_assert_notnull(f, "parse skip-only format");
|
|
int n = 0;
|
|
RzPfValue *vals = rz_pf_read(f, buf, sizeof(buf), 0, NULL, &n);
|
|
RzStructuredData *sd = rz_pf_render_sd(vals, n, NULL);
|
|
char *json = sd ? rz_structured_data_to_json(sd) : NULL;
|
|
mu_assert_notnull(json, "sd json");
|
|
mu_assert_streq(json, "{}", "skip-only format renders empty object");
|
|
free(json);
|
|
rz_structured_data_free(sd);
|
|
rz_pf_values_free(vals, n);
|
|
rz_pf_format_free(f);
|
|
mu_end;
|
|
}
|
|
|
|
int all_tests(void) {
|
|
/* Field size */
|
|
mu_run_test(test_pf_field_size_1byte);
|
|
mu_run_test(test_pf_field_size_2byte);
|
|
mu_run_test(test_pf_field_size_4byte);
|
|
mu_run_test(test_pf_field_size_8byte);
|
|
mu_run_test(test_pf_field_size_special);
|
|
|
|
/* Field C type */
|
|
mu_run_test(test_pf_field_ctype_unsigned);
|
|
mu_run_test(test_pf_field_ctype_signed);
|
|
mu_run_test(test_pf_field_ctype_float);
|
|
mu_run_test(test_pf_field_ctype_misc);
|
|
|
|
/* Encoding null unit size */
|
|
mu_run_test(test_pf_enc_null_unit_size);
|
|
|
|
/* Timestamp utilities */
|
|
mu_run_test(test_pf_timefmt_from_string);
|
|
mu_run_test(test_pf_timefmt_from_string_case_insensitive);
|
|
mu_run_test(test_pf_timefmt_from_string_unknown);
|
|
mu_run_test(test_pf_timefmt_as_string);
|
|
mu_run_test(test_pf_timefmt_size);
|
|
mu_run_test(test_pf_timefmt_is_float);
|
|
|
|
/* Parse: edge cases */
|
|
mu_run_test(test_pf_parse_null);
|
|
mu_run_test(test_pf_parse_leading_whitespace);
|
|
|
|
/* Parse: sized integers */
|
|
mu_run_test(test_pf_parse_hex_le);
|
|
mu_run_test(test_pf_parse_hex_be);
|
|
mu_run_test(test_pf_parse_dec_signed);
|
|
mu_run_test(test_pf_parse_dec_unsigned);
|
|
mu_run_test(test_pf_parse_octal);
|
|
mu_run_test(test_pf_parse_binary);
|
|
|
|
/* Parse: floats */
|
|
mu_run_test(test_pf_parse_float_sized);
|
|
mu_run_test(test_pf_parse_float_bare_deprecated);
|
|
|
|
/* Parse: single-char specifiers */
|
|
mu_run_test(test_pf_parse_single_char_specifiers);
|
|
|
|
/* Parse: strings */
|
|
mu_run_test(test_pf_parse_string_default);
|
|
mu_run_test(test_pf_parse_string_encoding);
|
|
mu_run_test(test_pf_parse_deprecated_Z);
|
|
mu_run_test(test_pf_parse_deprecated_w);
|
|
mu_run_test(test_pf_parse_deprecated_bare_X);
|
|
|
|
/* Parse: timestamps */
|
|
mu_run_test(test_pf_parse_timestamp_named);
|
|
mu_run_test(test_pf_parse_timestamp_all_formats);
|
|
mu_run_test(test_pf_parse_timestamp_deprecated);
|
|
|
|
/* Parse: repeat / union / prefix */
|
|
mu_run_test(test_pf_parse_repeat_digit);
|
|
mu_run_test(test_pf_parse_repeat_brace);
|
|
mu_run_test(test_pf_parse_repeat_default);
|
|
mu_run_test(test_pf_parse_union);
|
|
mu_run_test(test_pf_parse_no_union);
|
|
mu_run_test(test_pf_parse_repeat_union_combined);
|
|
mu_run_test(test_pf_parse_pointer_prefix);
|
|
mu_run_test(test_pf_parse_array_prefix);
|
|
mu_run_test(test_pf_parse_scalar_no_array);
|
|
mu_run_test(test_pf_parse_pointer_array);
|
|
|
|
/* Parse: names / type_name */
|
|
mu_run_test(test_pf_parse_field_names);
|
|
mu_run_test(test_pf_parse_skip_no_name);
|
|
mu_run_test(test_pf_parse_struct_type_name);
|
|
mu_run_test(test_pf_parse_enum_type_name);
|
|
mu_run_test(test_pf_parse_bitfield_type_name);
|
|
|
|
/* Parse: complex */
|
|
mu_run_test(test_pf_parse_complex_mixed);
|
|
mu_run_test(test_pf_parse_many_fields);
|
|
|
|
/* Read: integers */
|
|
mu_run_test(test_pf_read_hex8);
|
|
mu_run_test(test_pf_read_hex16_le);
|
|
mu_run_test(test_pf_read_hex16_be);
|
|
mu_run_test(test_pf_read_hex32_le);
|
|
mu_run_test(test_pf_read_hex32_be);
|
|
mu_run_test(test_pf_read_hex64_le);
|
|
mu_run_test(test_pf_read_signed8);
|
|
mu_run_test(test_pf_read_signed16_le);
|
|
mu_run_test(test_pf_read_signed32_le);
|
|
mu_run_test(test_pf_read_signed64_le);
|
|
mu_run_test(test_pf_read_unsigned32_le);
|
|
|
|
/* Read: floats */
|
|
mu_run_test(test_pf_read_float32_le);
|
|
mu_run_test(test_pf_read_float64_le);
|
|
|
|
/* Read: char */
|
|
mu_run_test(test_pf_read_char);
|
|
|
|
/* Read: multi-field offsets */
|
|
mu_run_test(test_pf_read_multi_field_offsets);
|
|
|
|
/* Read: skip */
|
|
mu_run_test(test_pf_read_skip);
|
|
|
|
/* Read: arrays */
|
|
mu_run_test(test_pf_read_array);
|
|
mu_run_test(test_pf_read_array_x4);
|
|
|
|
/* Read: buffer boundary */
|
|
mu_run_test(test_pf_read_buffer_too_short);
|
|
|
|
/* Read: union */
|
|
mu_run_test(test_pf_read_union);
|
|
|
|
/* Read: timestamps */
|
|
mu_run_test(test_pf_read_timestamp_unix32);
|
|
|
|
/* Read: strings */
|
|
mu_run_test(test_pf_read_zstring);
|
|
|
|
/* Timestamp conversion */
|
|
mu_run_test(test_pf_timestamp_to_tm_unix32);
|
|
mu_run_test(test_pf_timestamp_to_tm_unixms);
|
|
|
|
/* Lifecycle / NULL safety */
|
|
mu_run_test(test_pf_format_free_null);
|
|
mu_run_test(test_pf_values_free_null);
|
|
mu_run_test(test_pf_ctx_lifecycle);
|
|
|
|
/* DSL extensions */
|
|
mu_run_test(test_pf_parse_align);
|
|
mu_run_test(test_pf_read_align);
|
|
mu_run_test(test_pf_parse_bits);
|
|
mu_run_test(test_pf_read_bits_msb);
|
|
mu_run_test(test_pf_read_bits_lsb);
|
|
mu_run_test(test_pf_parse_bitvec_basic);
|
|
mu_run_test(test_pf_parse_bitvec_partial_byte);
|
|
mu_run_test(test_pf_parse_bitvec_lsb_msb_kw);
|
|
mu_run_test(test_pf_parse_bitvec_clamp_low);
|
|
mu_run_test(test_pf_parse_bitvec_clamp_high);
|
|
mu_run_test(test_pf_parse_bitvec_missing_paren);
|
|
mu_run_test(test_pf_read_bitvec_msb);
|
|
mu_run_test(test_pf_read_bitvec_lsb);
|
|
mu_run_test(test_pf_read_bitvec_partial);
|
|
mu_run_test(test_pf_render_bitvec_text);
|
|
mu_run_test(test_pf_parse_guid);
|
|
mu_run_test(test_pf_read_guid);
|
|
mu_run_test(test_pf_parse_lenref_array);
|
|
mu_run_test(test_pf_read_lenref_array);
|
|
mu_run_test(test_pf_parse_lenprefix_string);
|
|
mu_run_test(test_pf_read_lenprefix_string);
|
|
mu_run_test(test_pf_parse_inline_bitfield);
|
|
mu_run_test(test_pf_read_inline_bitfield_render);
|
|
mu_run_test(test_pf_parse_tlv);
|
|
mu_run_test(test_pf_read_tlv_raw);
|
|
|
|
/* Phase 5: render modes, timestamp coverage, pointer deref */
|
|
mu_run_test(test_pf_render_modes_basic);
|
|
mu_run_test(test_pf_render_quiet_ofield_filter);
|
|
mu_run_test(test_pf_timestamp_all_formats_size);
|
|
mu_run_test(test_pf_timestamp_filetime_decode);
|
|
mu_run_test(test_pf_pointer_deref_via_callback);
|
|
mu_run_test(test_pf_parse_empty_returns_empty);
|
|
mu_run_test(test_pf_read_zero_length_buffer);
|
|
mu_run_test(test_pf_format_oneshot);
|
|
|
|
/* Diagnostic / error reporting */
|
|
mu_run_test(test_pf_diag_unknown_specifier_position);
|
|
mu_run_test(test_pf_diag_bits_without_width);
|
|
mu_run_test(test_pf_diag_align_without_n);
|
|
mu_run_test(test_pf_diag_deprecated_w);
|
|
mu_run_test(test_pf_diag_tlv_missing_close_paren);
|
|
mu_run_test(test_pf_diag_tlv_bad_tag_type);
|
|
mu_run_test(test_pf_diag_tlv_unknown_key);
|
|
mu_run_test(test_pf_diag_errors_to_string);
|
|
mu_run_test(test_pf_diag_clean_parse_no_errors);
|
|
mu_run_test(test_pf_diag_source_captured);
|
|
|
|
/* Ambiguity audit -- disambiguation between similar DSL forms */
|
|
mu_run_test(test_pf_ambig_b4_vs_B4);
|
|
mu_run_test(test_pf_ambig_inline_bitfield_vs_typed);
|
|
mu_run_test(test_pf_ambig_t4_vs_t_paren);
|
|
mu_run_test(test_pf_ambig_z_vs_z_paren_vs_z_lenprefix);
|
|
mu_run_test(test_pf_ambig_V_bare_vs_V_paren);
|
|
mu_run_test(test_pf_ambig_struct_vs_skip);
|
|
mu_run_test(test_pf_ambig_x4_vs_x);
|
|
mu_run_test(test_pf_ambig_d_vs_D_endian);
|
|
mu_run_test(test_pf_ambig_align_vs_skip);
|
|
mu_run_test(test_pf_ambig_bits_lt_gt);
|
|
|
|
/* Palette / color (issue #782) */
|
|
mu_run_test(test_pf_palette_text_basic);
|
|
mu_run_test(test_pf_palette_null_no_color);
|
|
mu_run_test(test_pf_palette_partial_palette);
|
|
mu_run_test(test_pf_palette_null_passes_through_input_esc);
|
|
|
|
/* DOT mode regression (surfaced building PR showcase) */
|
|
mu_run_test(test_pf_render_dot_timestamp);
|
|
mu_run_test(test_pf_render_dot_column_aligned);
|
|
mu_run_test(test_pf_render_dot_single_field);
|
|
mu_run_test(test_pf_render_dot_empty);
|
|
mu_run_test(test_pf_render_dot_tlv);
|
|
|
|
/* Regressions surfaced by the property harness */
|
|
mu_run_test(test_pf_parse_empty_input);
|
|
mu_run_test(test_pf_parse_null_input);
|
|
mu_run_test(test_pf_parse_bitfield_missing_eq_diagnostic);
|
|
mu_run_test(test_pf_parse_bitfield_empty_field_diagnostic);
|
|
mu_run_test(test_pf_render_bitfield_inline_idempotent);
|
|
|
|
/* RzStructuredData renderer */
|
|
mu_run_test(test_pf_render_sd_scalars);
|
|
mu_run_test(test_pf_render_sd_bitvec);
|
|
mu_run_test(test_pf_render_sd_guid);
|
|
mu_run_test(test_pf_render_sd_nested_struct);
|
|
mu_run_test(test_pf_render_sd_autoname);
|
|
mu_run_test(test_pf_render_sd_signed_and_float);
|
|
mu_run_test(test_pf_render_sd_scalar_array);
|
|
mu_run_test(test_pf_render_sd_filter);
|
|
mu_run_test(test_pf_render_sd_timestamp);
|
|
mu_run_test(test_pf_render_sd_yaml);
|
|
mu_run_test(test_pf_render_sd_only_skips);
|
|
|
|
return tests_passed != tests_run;
|
|
}
|
|
|
|
mu_main(all_tests)
|