aboutsummaryrefslogtreecommitdiff
path: root/core/encoding/hex/hex.odin
blob: c4726d9e90aeb296f9033a0865c6380813f34ed8 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
// Encoding and decoding of hex-encoded binary, e.g. `0x23` -> `#`.
package encoding_hex

import "base:runtime"
import "core:io"
import "core:strings"

/*
Encodes a byte slice into a lowercase hex sequence

*Allocates Using Provided Allocator*

Inputs:
- src: The `[]byte` to be hex-encoded
- allocator: (default: context.allocator)
- loc: The caller location for debugging purposes (default: #caller_location)

Returns:
- res: The hex-encoded result
- err: An optional allocator error if one occured, `.None` otherwise
*/
encode :: proc(src: []byte, allocator := context.allocator, loc := #caller_location) -> (res: []byte, err: runtime.Allocator_Error) #optional_allocator_error {
	res, err = make([]byte, len(src) * 2, allocator, loc)
	#no_bounds_check for i, j := 0, 0; i < len(src); i += 1 {
		v := src[i]
		res[j]   = LOWER[v>>4]
		res[j+1] = LOWER[v&0x0f]
		j += 2
	}
	return
}

/*
Encodes a byte slice as a lowercase hex sequence into an `io.Writer`

Inputs:
- dst: The `io.Writer` to encode into
- src: The `[]byte` to be hex-encoded

Returns:
- err: An `io.Error` if one occured, `.None` otherwise
*/
encode_into_writer :: proc(dst: io.Writer, src: []byte) -> (err: io.Error) {
	for v in src {
		io.write(dst, {LOWER[v>>4], LOWER[v&0x0f]}) or_return
	}
	return
}

/*
Encodes a byte slice into an uppercase hex sequence

*Allocates Using Provided Allocator*

Inputs:
- src: The `[]byte` to be hex-encoded
- allocator: (default: context.allocator)
- loc: The caller location for debugging purposes (default: #caller_location)

Returns:
- res: The hex-encoded result
- err: An optional allocator error if one occured, `.None` otherwise
*/
encode_upper :: proc(src: []byte, allocator := context.allocator, loc := #caller_location) -> (res: []byte, err: runtime.Allocator_Error) #optional_allocator_error {
	res, err = make([]byte, len(src) * 2, allocator, loc)
	#no_bounds_check for i, j := 0, 0; i < len(src); i += 1 {
		v := src[i]
		res[j]   = UPPER[v>>4]
		res[j+1] = UPPER[v&0x0f]
		j += 2
	}
	return
}

/*
Encodes a byte slice as an uppercase hex sequence into an `io.Writer`

Inputs:
- dst: The `io.Writer` to encode into
- src: The `[]byte` to be hex-encoded

Returns:
- err: An `io.Error` if one occured, `.None` otherwise
*/
encode_upper_into_writer :: proc(dst: io.Writer, src: []byte) -> (err: io.Error) {
	for v in src {
		io.write(dst, {UPPER[v>>4], UPPER[v&0x0f]}) or_return
	}
	return
}

/*
Decodes a hex sequence into a byte slice

*Allocates Using Provided Allocator*

Inputs:
- dst: The hex sequence decoded into bytes
- src: The `[]byte` to be hex-decoded
- allocator: (default: context.allocator)
- loc: The caller location for debugging purposes (default: #caller_location)

Returns:
- ok:  A bool, `true` if decoding succeeded, `false` otherwise
*/
decode :: proc(src: []byte, allocator := context.allocator, loc := #caller_location) -> (dst: []byte, ok: bool) {
	if len(src) % 2 == 1 {
		return
	}

	dst = make([]byte, len(src) / 2, allocator, loc)
	#no_bounds_check for i, j := 0, 1; j < len(src); j += 2 {
		p := src[j-1]
		q := src[j]

		a := hex_digit(p) or_return
		b := hex_digit(q) or_return

		dst[i] = (a << 4) | b
		i += 1
	}

	return dst, true
}

/*
Decodes the first byte in a hex sequence to a byte

Inputs:
- str: A hex-encoded `string`, e.g. `"0x23"`

Returns:
- res: The decoded byte, e.g. `'#'`
- ok:  A bool, `true` if decoding succeeded, `false` otherwise
*/
decode_sequence :: proc(str: string) -> (res: byte, ok: bool) {
	str := str
	if strings.has_prefix(str, "0x") || strings.has_prefix(str, "0X") {
		str = str[2:]
	}

	if len(str) != 2 {
		return 0, false
	}

	upper := hex_digit(str[0]) or_return
	lower := hex_digit(str[1]) or_return

	return upper << 4 | lower, true
}

@(private)
LOWER := [16]byte {
	'0', '1', '2', '3',
	'4', '5', '6', '7',
	'8', '9', 'a', 'b',
	'c', 'd', 'e', 'f',
}

@(private)
UPPER := [16]byte {
	'0', '1', '2', '3',
	'4', '5', '6', '7',
	'8', '9', 'A', 'B',
	'C', 'D', 'E', 'F',
}

@(private)
hex_digit :: proc(char: byte) -> (u8, bool) {
	switch char {
	case '0' ..= '9': return char - '0', true
	case 'a' ..= 'f': return char - 'a' + 10, true
	case 'A' ..= 'F': return char - 'A' + 10, true
	case:             return 0, false
	}
}