code wiki / (root) / hex.nx

hex.nx source

↩ module page · 97 lines · 3331 B

1// hex.nx -- lowercase hex encoder + decoder. 2// 3// Complement to base64.nx for the common "print bytes as text" 4// use cases where readability matters more than density: 5// - SHA-256 / SHA-3 digest display 6// - Git-style object IDs 7// - TLS handshake transcripts for debugging 8// - URL query parameters carrying short binary values 9// 10// Deterministic output (lowercase always). Decoder tolerates 11// both upper and lower case. No "0x" prefix, no whitespace. 12// 13// Invariants: 14// H1 Encoder output is exactly 2*n chars for n input bytes. 15// H2 Decoder requires even-length input; rejects odd-length 16// with a negative return. 17// H3 Decoder rejects any non-hex char (not [0-9A-Fa-f]) with a 18// negative return -- no silent skip. 19// H4 Round-trip is exact: decode(encode(x)) == x for any byte 20// sequence. 21 22import "syscalls.nx" 23 24const HEX_ERR_ODD_LENGTH: i64 = -1 25const HEX_ERR_INVALID_CHAR: i64 = -2 26 27// Encode n bytes from `in_bytes` as lowercase hex into `out`. 28// Returns 2 * n (bytes written). 29func hex_encode(in_bytes: *u8, n: i64, out: *u8) -> i64 { 30 var i: i64 = 0 31 while i < n { 32 let b: i64 = in_bytes[i] 33 let hi: i64 = (b >> 4) & 0xF 34 let lo: i64 = b & 0xF 35 var hc: i64 = 0x30 + hi 36 if hi > 9 { hc = 0x61 + hi - 10 } // 'a'..'f' 37 var lc: i64 = 0x30 + lo 38 if lo > 9 { lc = 0x61 + lo - 10 } 39 out[i * 2] = hc 40 out[i * 2 + 1] = lc 41 i = i + 1 42 } 43 return n * 2 44} 45 46// Decode one hex char to its 0..15 value, or -1 if invalid. 47func hex_nibble(c: i64) -> i64 { 48 if c >= 0x30 { if c <= 0x39 { return c - 0x30 } } // 0-9 49 if c >= 0x41 { if c <= 0x46 { return c - 0x41 + 10 } } // A-F 50 if c >= 0x61 { if c <= 0x66 { return c - 0x61 + 10 } } // a-f 51 return -1 52} 53 54// Decode `n` hex chars into raw bytes; writes n/2 bytes to `out`. 55// Returns bytes written or a negative HEX_ERR_*. 56func hex_decode(in_chars: *u8, n: i64, out: *u8) -> i64 { 57 if (n & 1) != 0 { return HEX_ERR_ODD_LENGTH } 58 var i: i64 = 0 59 var out_pos: i64 = 0 60 while i < n { 61 let hi: i64 = hex_nibble(in_chars[i]) 62 if hi < 0 { return HEX_ERR_INVALID_CHAR } 63 let lo: i64 = hex_nibble(in_chars[i + 1]) 64 if lo < 0 { return HEX_ERR_INVALID_CHAR } 65 out[out_pos] = ((hi << 4) | lo) & 0xFF 66 out_pos = out_pos + 1 67 i = i + 2 68 } 69 return out_pos 70} 71 72// Compile-only smoke: encode 0x00, 0x0f, 0xff, 0xde 0xad 0xbe 0xef 73// -> "000fffdeadbeef" (14 chars), then decode back. 74func main() -> i64 { 75 let input: *u8 = sys_mmap(8) 76 input[0] = 0x00 77 input[1] = 0x0F 78 input[2] = 0xFF 79 input[3] = 0xDE 80 input[4] = 0xAD 81 input[5] = 0xBE 82 input[6] = 0xEF 83 let encoded: *u8 = sys_mmap(32) 84 let n_enc: i64 = hex_encode(input, 7, encoded) 85 if n_enc != 14 { return 1 } 86 // First char should be '0' = 0x30. 87 if encoded[0] != 0x30 { return 2 } 88 // Check 'd' of deadbeef at offset 6 = 0x64. 89 if encoded[6] != 0x64 { return 3 } 90 91 let decoded: *u8 = sys_mmap(16) 92 let n_dec: i64 = hex_decode(encoded, n_enc, decoded) 93 if n_dec != 7 { return 4 } 94 if decoded[0] != 0x00 { return 5 } 95 if decoded[3] != 0xDE { return 6 } 96 return 0 97}