add byteutils

This commit is contained in:
Jacek Sieka 2019-07-06 20:07:41 +02:00
parent a9612d7320
commit c95cee45bc
No known key found for this signature in database
GPG Key ID: A1B09461ABB656B8
4 changed files with 195 additions and 2 deletions

View File

@ -1,10 +1,17 @@
# stew - status e-something w-something # stew - status e-something w-something
[![Build Status (Travis)](https://img.shields.io/travis/status-im/nim-stew/master.svg?label=Linux%20/%20macOS "Linux/macOS build status (Travis)")](https://travis-ci.org/status-im/nim-stew)
[![Windows build status (Appveyor)](https://img.shields.io/appveyor/ci/nimbus/nim-stew/master.svg?label=Windows "Windows build status (Appveyor)")](https://ci.appveyor.com/project/nimbus/nim-stew)
[![License: Apache](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
![Stability: experimental](https://img.shields.io/badge/stability-experimental-orange.svg)
`stew` is collection of utilities, std library extensions and budding libraries `stew` is collection of utilities, std library extensions and budding libraries
that are frequently used at Status, but are too small to deserve their own that are frequently used at Status, but are too small to deserve their own
git repository. git repository.
We use `stew` as a staging ground for code that has yet to be battle-tested. We also use `stew` as a staging ground for code that has yet to be
battle-tested.
Some of these libraries may eventually be proposed for inclusion in Nim or Some of these libraries may eventually be proposed for inclusion in Nim or
broken out into separate repositories. broken out into separate repositories.
@ -46,7 +53,8 @@ welcome patches.
Libraries are documented either in-module or on a separate README in their Libraries are documented either in-module or on a separate README in their
respective folders respective folders
- `bitops2` - an updated version of `bitops.nim`, filling in gaps in original code\ - `bitops2` - an updated version of `bitops.nim`, filling in gaps in original code
- `byteutils` - utilities that make working with the Nim `byte` type convenient
- `shims` - backports of nim `devel` code to the stable version that Status is using - `shims` - backports of nim `devel` code to the stable version that Status is using
## Using stew in your project ## Using stew in your project

125
stew/byteutils.nim Normal file
View File

@ -0,0 +1,125 @@
# byteutils
# Copyright (c) 2018 Status Research & Development GmbH
# Licensed and distributed under either of
# * MIT license (license terms in the root directory or at http://opensource.org/licenses/MIT).
# * Apache v2 license (license terms in the root directory or at http://www.apache.org/licenses/LICENSE-2.0).
# at your option. This file may not be copied, modified, or distributed except according to those terms.
########################################################################################################
#################################### Array utilities ###############################################
import algorithm
func initArrayWith*[N: static[int], T](value: T): array[N, T] {.noInit, inline.}=
result.fill(value)
func `&`*[N1, N2: static[int], T](
a: array[N1, T],
b: array[N2, T]
): array[N1 + N2, T] {.inline, noInit.}=
## Array concatenation
result[0 ..< N1] = a
result[N1 ..< result.len] = b
########################################################################################################
##################################### Hex utilities ################################################
proc readHexChar*(c: char): byte {.noSideEffect, inline.}=
## Converts an hex char to a byte
case c
of '0'..'9': result = byte(ord(c) - ord('0'))
of 'a'..'f': result = byte(ord(c) - ord('a') + 10)
of 'A'..'F': result = byte(ord(c) - ord('A') + 10)
else:
raise newException(ValueError, $c & "is not a hexademical character")
template skip0xPrefix(hexStr: string): int =
## Returns the index of the first meaningful char in `hexStr` by skipping
## "0x" prefix
if hexStr[0] == '0' and hexStr[1] in {'x', 'X'}: 2
else: 0
func hexToByteArray*(hexStr: string, output: var openArray[byte], fromIdx, toIdx: int) =
## Read a hex string and store it in a byte array `output`. No "endianness" reordering is done.
## Allows specifying the byte range to process into the array
var sIdx = skip0xPrefix(hexStr)
doAssert(fromIdx >= 0 and toIdx >= fromIdx and fromIdx < output.len and toIdx < output.len)
let sz = toIdx - fromIdx + 1
doAssert hexStr.len - sIdx >= 2*sz
sIdx += fromIdx * 2
for bIdx in fromIdx ..< sz + fromIdx:
output[bIdx] = hexStr[sIdx].readHexChar shl 4 or hexStr[sIdx + 1].readHexChar
inc(sIdx, 2)
func hexToByteArray*(hexStr: string, output: var openArray[byte]) {.inline.} =
## Read a hex string and store it in a byte array `output`. No "endianness" reordering is done.
hexToByteArray(hexStr, output, 0, output.high)
func hexToByteArray*[N: static[int]](hexStr: string): array[N, byte] {.noInit, inline.}=
## Read an hex string and store it in a byte array. No "endianness" reordering is done.
hexToByteArray(hexStr, result)
func hexToPaddedByteArray*[N: static[int]](hexStr: string): array[N, byte] =
## Read a hex string and store it in a byte array `output`.
## The string may be shorter than the byte array.
## No "endianness" reordering is done.
let
p = skip0xPrefix(hexStr)
sz = hexStr.len - p
maxStrSize = result.len * 2
var
bIdx: int
shift = 4
doAssert hexStr.len - p <= maxStrSize
if sz < maxStrSize:
# include extra byte if odd length
bIdx = result.len - (sz + 1) div 2
# start with shl of 4 if length is even
shift = 4 - sz mod 2 * 4
for sIdx in p ..< hexStr.len:
let nibble = hexStr[sIdx].readHexChar shl shift
result[bIdx] = result[bIdx] or nibble
shift = shift + 4 and 4
bIdx += shift shr 2
func hexToSeqByte*(hexStr: string): seq[byte] =
## Read an hex string and store it in a sequence of bytes. No "endianness" reordering is done.
doAssert (hexStr.len and 1) == 0
let skip = skip0xPrefix(hexStr)
let N = (hexStr.len - skip) div 2
result = newSeq[byte](N)
for i in 0 ..< N:
result[i] = hexStr[2*i + skip].readHexChar shl 4 or hexStr[2*i + 1 + skip].readHexChar
func toHexAux(ba: openarray[byte]): string =
## Convert a byte-array to its hex representation
## Output is in lowercase
## No "endianness" reordering is done.
const hexChars = "0123456789abcdef"
let sz = ba.len
result = newString(2 * sz)
for i in 0 ..< sz:
result[2*i] = hexChars[int ba[i] shr 4 and 0xF]
result[2*i+1] = hexChars[int ba[i] and 0xF]
func toHex*(ba: openarray[byte]): string {.inline.} =
## Convert a byte-array to its hex representation
## Output is in lowercase
## No "endianness" reordering is done.
toHexAux(ba)
func toHex*[N: static[int]](ba: array[N, byte]): string {.inline.} =
## Convert a big endian byte-array to its hex representation
## Output is in lowercase
## No "endianness" reordering is done.
toHexAux(ba)

View File

@ -6,3 +6,6 @@
# * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) # * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)
# #
# at your option. This file may not be copied, modified, or distributed except according to those terms. # at your option. This file may not be copied, modified, or distributed except according to those terms.
import
test_byteutils

57
tests/test_byteutils.nim Normal file
View File

@ -0,0 +1,57 @@
# byteutils
# Copyright (c) 2018 Status Research & Development GmbH
# Licensed and distributed under either of
# * MIT license (license terms in the root directory or at http://opensource.org/licenses/MIT).
# * Apache v2 license (license terms in the root directory or at http://www.apache.org/licenses/LICENSE-2.0).
# at your option. This file may not be copied, modified, or distributed except according to those terms.
import unittest,
../stew/byteutils
suite "Byte utils":
let simpleBArray = [0x12.byte, 0x34, 0x56, 0x78]
test "hexToByteArray: Inplace partial string":
let s = "0x1234567890"
var a: array[5, byte]
hexToByteArray(s, a, 1, 3)
check a == [0.byte, 0x34, 0x56, 0x78, 0]
test "hexToByteArray: Inplace full string":
let s = "0xffffffff"
var a: array[4, byte]
hexToByteArray(s, a)
check a == [255.byte, 255, 255, 255]
test "hexToByteArray: Return array":
let
s = "0x12345678"
a = hexToByteArray[4](s)
check a == simpleBArray
test "toHex":
check simpleBArray.toHex == "12345678"
test "Array concatenation":
check simpleBArray & simpleBArray ==
[0x12.byte, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78]
test "hexToPaddedByteArray":
block:
let a = hexToPaddedByteArray[4]("0x123")
check a.toHex == "00000123"
block:
let a = hexToPaddedByteArray[4]("0x1234")
check a.toHex == "00001234"
block:
let a = hexToPaddedByteArray[4]("0x1234567")
check a.toHex == "01234567"
block:
let a = hexToPaddedByteArray[4]("0x12345678")
check a.toHex == "12345678"
block:
let a = hexToPaddedByteArray[32]("0x68656c6c6f20776f726c64")
check a.toHex == "00000000000000000000000000000000000000000068656c6c6f20776f726c64"
block:
expect AssertionError:
let a = hexToPaddedByteArray[2]("0x12345")