///|
pub struct DataHexSearchReport {
file_size : Int
sha256 : String
query_hex : String
query_size : Int
matching_rule : String
scope : String
data_segments : DataSegmentsReport
segment_count : Int
matched_segment_count : Int
unmatched_segment_count : Int
occurrence_count : Int
matches : Array[DataSearchMatch]
}
///|
pub enum DataHexSearchError {
InvalidDataHexQuery
DataHexSearchInputError(WasmError)
}
///|
pub fn describe_data_hex_search_error(error : DataHexSearchError) -> String {
match error {
InvalidDataHexQuery =>
"hex query must contain a nonempty even number of ASCII hexadecimal digits, without spaces, prefixes or separators"
DataHexSearchInputError(error) => describe_error(error)
}
}
///|
fn data_hex_nibble(code : Int) -> Int {
if code >= 0x30 && code <= 0x39 {
code - 0x30
} else if code >= 0x61 && code <= 0x66 {
code - 0x61 + 10
} else if code >= 0x41 && code <= 0x46 {
code - 0x41 + 10
} else {
-1
}
}
///|
/// Shared syntax check for library and CLI; no byte pattern allocation or trim.
fn data_hex_search_valid(query : String) -> Bool {
if query.is_empty() || query.length() % 2 != 0 {
return false
}
for ch in query.iter() {
if data_hex_nibble(ch.to_int()) < 0 {
return false
}
}
true
}
///|
/// Decode ASCII pairs directly into bytes; never use UTF-8 as an intermediate.
pub fn search_data_segments_hex(
data : Bytes,
query_hex : String,
) -> Result[DataHexSearchReport, DataHexSearchError] {
if !data_hex_search_valid(query_hex) {
return Err(InvalidDataHexQuery)
}
let bytes : Array[Byte] = []
let mut high = -1
for ch in query_hex.iter() {
let value = data_hex_nibble(ch.to_int())
if high < 0 {
high = value
} else {
bytes.push(((high << 4) | value).to_byte())
high = -1
}
}
let needle = Bytes::from_array(bytes)
let canonical = StringBuilder()
for byte in needle {
canonical.write_string(byte.to_hex())
}
let report = match inspect_data_segments(data) {
Ok(report) => report
Err(error) => return Err(DataHexSearchInputError(error))
}
let (matches, matched_segment_count) = data_search_matches(
data, report, needle,
)
Ok({
file_size: data.length(),
sha256: experiment_sha256(data),
query_hex: canonical.to_string(),
query_size: needle.length(),
matching_rule: "Exact bytes decoded from compact ASCII hex pairs; letter case is equivalent and reports use lowercase. Overlapping occurrences retained within each payload, never across entries or in encoding metadata. No trimming, spaces, separators, prefixes, escapes or wildcards. No UTF-8 conversion or automatic encoding detection.",
scope: "Hits record exact query bytes in initialization payloads, not independent strings, source constants or used resources. No match does not prove content is absent. File offsets are not memory addresses. No encoding/source attribution, safe merging or deletion/rebuilt/gzip savings are inferred. Overlapping occurrence lengths cannot be added as occupied content bytes. Offset expressions remain unevaluated. Top limits text position rows only, not search cost or full JSON; memory grows with occurrences. The normalized pattern itself is saved in reports; no payload context is output.",
data_segments: report,
segment_count: report.entries.length(),
matched_segment_count,
unmatched_segment_count: report.entries.length() - matched_segment_count,
occurrence_count: matches.length(),
matches,
})
}