mirror of
https://github.com/odin-lang/Odin.git
synced 2026-08-18 19:32:15 +00:00
Also minor fixes to documentation of hex.decode(). Adds tests of the new procedure with buffers the correct size, larger, and too small.
211 lines
4.8 KiB
Odin
211 lines
4.8 KiB
Odin
// 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:
|
|
- src: The `[]byte` to be hex-decoded
|
|
- allocator: (default: context.allocator)
|
|
- loc: The caller location for debugging purposes (default: #caller_location)
|
|
|
|
Returns:
|
|
- dst: The hex sequence decoded into bytes
|
|
- 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 a hex sequence into a byte slice
|
|
|
|
Inputs:
|
|
- src: The `[]byte` to be hex-decoded
|
|
- buf: A buffer large enough to hold the decoded sequence
|
|
|
|
Returns:
|
|
- dst: The hex sequence decoded into bytes
|
|
- ok: A bool, `true` if decoding succeeded, `false` otherwise
|
|
*/
|
|
decode_into_buffer :: proc(src: []byte, buf: []byte) -> (dst: []byte, ok: bool) #optional_ok {
|
|
if len(src) % 2 == 1 {
|
|
return
|
|
}
|
|
dst_len := len(src) / 2
|
|
if len(buf) < dst_len {
|
|
return
|
|
}
|
|
|
|
#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
|
|
|
|
buf[i] = (a << 4) | b
|
|
i += 1
|
|
}
|
|
|
|
return buf[:dst_len], 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
|
|
}
|
|
}
|