v / vlib / encoding / hex / hex.v
99 lines · 89 sloc · 2.51 KB · 7a7cf783a49229063175710d18385cb357b19b92
Raw
1module hex
2
3const hex_digits = '0123456789abcdef'
4const hex_digits_upper = '0123456789ABCDEF'
5
6// EncodeParams configures optional output formatting for encode.
7//
8// If `uppercase` is set, `encode` emits `A-F` instead of `a-f`.
9// If `with_prefix` is non-empty, `encode` prepends that exact string.
10@[params]
11pub struct EncodeParams {
12pub mut:
13 uppercase bool
14 with_prefix string
15}
16
17// decode converts a hex string into an array of bytes.
18// The expected input format is 2 ASCII characters for each output byte.
19// If the provided string length is not a multiple of 2, the first digit is
20// decoded as if an implicit `0` preceded it. An optional `0x` or `0X` prefix
21// is accepted.
22@[direct_array_access]
23pub fn decode(s string) ![]u8 {
24 if s.len == 0 {
25 return []u8{}
26 }
27
28 mut offset := 0
29 mut hex_bytes := if s.len >= 2 {
30 if s[0] == `0` && (s[1] == `x` || s[1] == `X`) {
31 offset = 2
32 s[2..].bytes()
33 } else {
34 s.bytes()
35 }
36 } else {
37 s.bytes()
38 }
39
40 if hex_bytes.len == 0 {
41 return []u8{}
42 }
43
44 mut bytes := []u8{cap: (hex_bytes.len + 1) >> 1}
45 mut start := 0
46 if hex_bytes.len & 1 == 1 {
47 bytes << char2nibble(hex_bytes[0], offset)!
48 start = 1
49 }
50
51 for i := start; i < hex_bytes.len; i += 2 {
52 n1 := char2nibble(hex_bytes[i], offset + i)!
53 n0 := char2nibble(hex_bytes[i + 1], offset + i + 1)!
54 bytes << (n1 << 4) | n0
55 }
56 return bytes
57}
58
59// encode converts an array of bytes into a string of ASCII hex bytes. The
60// output will always be a string whose length will be a multiple of 2.
61// If `EncodeParams.uppercase` is set, the output hex characters are emitted in
62// uppercase.
63// If `EncodeParams.with_prefix` is non-empty, the output string is prefixed
64// with the provided string.
65@[direct_array_access]
66pub fn encode(bytes []u8, params EncodeParams) string {
67 if bytes.len == 0 {
68 return ''
69 }
70 mut res := []u8{}
71 if params.with_prefix != '' {
72 res << params.with_prefix.bytes()
73 }
74 for _, b in bytes {
75 res << nibble2char(b >> 4, params)
76 res << nibble2char(b & 0xf, params)
77 }
78 return res.bytestr()
79}
80
81// nibble2char converts a 4-bit hex value to its ASCII character
82@[inline]
83fn nibble2char(nibble u8, params EncodeParams) u8 {
84 if params.uppercase {
85 return hex_digits_upper[nibble]
86 }
87 return hex_digits[nibble]
88}
89
90// char2nibble converts an ASCII hex character to its hex value
91@[inline]
92fn char2nibble(b u8, index int) !u8 {
93 match b {
94 `0`...`9` { return b - u8(`0`) }
95 `A`...`F` { return b - u8(`A`) + 10 }
96 `a`...`f` { return b - u8(`a`) + 10 }
97 else { return error('invalid hex char ${b.ascii_str()} at index ${index}') }
98 }
99}
100