From ea4f6fd48f907bd56a280b66278b78938d98491e Mon Sep 17 00:00:00 2001 From: Miccah Date: Sun, 15 Aug 2021 13:42:51 -0500 Subject: [PATCH] encoding: add a hex sub-module (#11193) --- vlib/encoding/hex/hex.v | 38 +++++++++++++++++++++++++++++++++ vlib/encoding/hex/hex_test.v | 41 ++++++++++++++++++++++++++++++++++++ 2 files changed, 79 insertions(+) create mode 100644 vlib/encoding/hex/hex.v create mode 100644 vlib/encoding/hex/hex_test.v diff --git a/vlib/encoding/hex/hex.v b/vlib/encoding/hex/hex.v new file mode 100644 index 0000000000..6f75f2fff6 --- /dev/null +++ b/vlib/encoding/hex/hex.v @@ -0,0 +1,38 @@ +module hex + +import strconv +import strings + +// decode converts a hex string into an array of bytes. The expected +// input format is 2 ASCII characters for each output byte. If the provided +// string length is not a multiple of 2, an implicit `0` is prepended to it. +pub fn decode(s string) ?[]byte { + if s.len == 0 { + return []byte{} + } else if s.len <= 2 { + return [byte(strconv.parse_uint(s, 16, 8) ?)] + } + // calculate the first byte depending on if s.len is odd + val := byte(strconv.parse_uint(s[..2 - (s.len & 1)], 16, 8) ?) + // set cap to s.len/2 rounded up + mut bytes := []byte{len: 1, cap: (s.len + 1) >> 1, init: val} + // iterate over every 2 bytes + // the start index depends on if s.len is odd + for i := 2 - (s.len & 1); i < s.len; i += 2 { + bytes << byte(strconv.parse_uint(s[i..i + 2], 16, 8) ?) + } + return bytes +} + +// encode converts an array of bytes into a string of ASCII hex bytes. The +// output will always be a string with length a multiple of 2. +[manualfree] +pub fn encode(bytes []byte) string { + mut sb := strings.new_builder(bytes.len << 1) + for b in bytes { + sb.write_string(b.hex()) + } + res := sb.str() + unsafe { sb.free() } + return res +} diff --git a/vlib/encoding/hex/hex_test.v b/vlib/encoding/hex/hex_test.v new file mode 100644 index 0000000000..1be8f80e7e --- /dev/null +++ b/vlib/encoding/hex/hex_test.v @@ -0,0 +1,41 @@ +module hex + +fn test_decode() ? { + assert decode('') ? == [] + assert decode('0') ? == [byte(0x0)] + assert decode('f') ? == [byte(0xf)] + assert decode('0f') ? == [byte(0x0f)] + assert decode('ff') ? == [byte(0xff)] + assert decode('123') ? == [byte(0x1), 0x23] + assert decode('1234') ? == [byte(0x12), 0x34] + assert decode('12345') ? == [byte(0x1), 0x23, 0x45] +} + +fn test_decode_fails() ? { + if x := decode('foo') { + return error('expected decode to fail, got $x') + } + if x := decode('g') { + return error('expected decode to fail, got $x') + } + if x := decode('000000000g') { + return error('expected decode to fail, got $x') + } + if x := decode('_') { + return error('expected decode to fail, got $x') + } + if x := decode('!') { + return error('expected decode to fail, got $x') + } +} + +fn test_encode() ? { + assert encode(decode('') ?) == '' + assert encode(decode('0') ?) == '00' + assert encode(decode('f') ?) == '0f' + assert encode(decode('0f') ?) == '0f' + assert encode(decode('ff') ?) == 'ff' + assert encode(decode('123') ?) == '0123' + assert encode(decode('1234') ?) == '1234' + assert encode(decode('12345') ?) == '012345' +}