nim-stint/stint/endians2.nim

273 lines
8.5 KiB
Nim
Raw Normal View History

# Stint
# Copyright 2018 Status Research & Development GmbH
# Licensed under either of
#
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0)
# * 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.
import private/datatypes
{.push raises: [IndexError], noInit, gcsafe.}
2020-06-13 14:44:13 +00:00
# Serialization
# ------------------------------------------------------------------------------------------
template toByte(x: SomeUnsignedInt): byte =
## At compile-time, conversion to bytes checks the range
## we want to ensure this is done at the register level
## at runtime in a single "mov byte" instruction
when nimvm:
byte(x and 0xFF)
else:
byte(x)
template blobFrom(dst: var openArray[byte], src: SomeUnsignedInt, startIdx: int, endian: static Endianness) =
## Write an integer into a raw binary blob
## Swapping endianness if needed
when endian == cpuEndian:
for i in 0 ..< sizeof(src):
dst[startIdx+i] = toByte((src shr (i * 8)))
else:
2020-06-13 14:44:13 +00:00
for i in 0 ..< sizeof(src):
dst[startIdx+sizeof(src)-1-i] = toByte((src shr (i * 8)))
func toBytesLE*[bits: static int](src: StUint[bits]): array[bits div 8, byte] =
var
src_idx, dst_idx = 0
acc: Word = 0
acc_len = 0
when cpuEndian == bigEndian:
srcIdx = src.limbs.len - 1
var tail = result.len
while tail > 0:
when cpuEndian == littleEndian:
let w = if src_idx < src.limbs.len: src.limbs[src_idx]
else: 0
inc src_idx
else:
let w = if src_idx >= 0: src.limbs[src_idx]
else: 0
dec src_idx
if acc_len == 0:
# We need to refill the buffer to output 64-bit
acc = w
acc_len = WordBitWidth
else:
let lo = acc
acc = w
2020-06-13 14:44:13 +00:00
if tail >= sizeof(Word):
# Unrolled copy
result.blobFrom(src = lo, dst_idx, littleEndian)
dst_idx += sizeof(Word)
tail -= sizeof(Word)
else:
# Process the tail and exit
when cpuEndian == littleEndian:
# When requesting little-endian on little-endian platform
# we can just copy each byte
# tail is inclusive
for i in 0 ..< tail:
result[dst_idx+i] = toByte(lo shr (i*8))
else: # TODO check this
# We need to copy from the end
for i in 0 ..< tail:
result[dst_idx+i] = toByte(lo shr ((tail-i)*8))
return
func toBytesBE*[bits: static int](src: StUint[bits]): array[bits div 8, byte] {.inline.} =
var
src_idx = 0
acc: Word = 0
acc_len = 0
when cpuEndian == bigEndian:
srcIdx = src.limbs.len - 1
var tail = result.len
while tail > 0:
when cpuEndian == littleEndian:
let w = if src_idx < src.limbs.len: src.limbs[src_idx]
else: 0
inc src_idx
else:
let w = if src_idx >= 0: src.limbs[src_idx]
else: 0
dec src_idx
if acc_len == 0:
# We need to refill the buffer to output 64-bit
acc = w
acc_len = WordBitWidth
else:
let lo = acc
acc = w
if tail >= sizeof(Word):
# Unrolled copy
tail -= sizeof(Word)
result.blobFrom(src = lo, tail, bigEndian)
else:
# Process the tail and exit
when cpuEndian == littleEndian:
# When requesting little-endian on little-endian platform
# we can just copy each byte
# tail is inclusive
for i in 0 ..< tail:
result[tail-1-i] = toByte(lo shr (i*8))
else:
# We need to copy from the end
for i in 0 ..< tail:
result[tail-1-i] = toByte(lo shr ((tail-i)*8))
return
func toBytes*[bits: static int](x: StUint[bits], endian: Endianness = system.cpuEndian): array[bits div 8, byte] {.inline.} =
if endian == littleEndian:
result = x.toBytesLE()
else:
result = x.toBytesBE()
2020-06-13 14:44:13 +00:00
# Deserialization
# ------------------------------------------------------------------------------------------
func fromBytesBE*[bits: static int](
T: typedesc[StUint[bits]],
x: openArray[byte]): T =
## Read big endian bytes and convert to an integer. At runtime, v must contain
## at least sizeof(T) bytes. Native endianess is used which is not
## portable! (i.e. use fixed-endian byte array or hex for serialization)
2019-10-09 03:24:34 +00:00
var accum: Word
var accumBits: int
var dstIdx: int
when cpuEndian == littleEndian: # src is bigEndian, CPU is little-endian
dstIdx = 0
for srcIdx in countdown(x.len-1, 0):
let srcByte = x[srcIdx]
accum = accum or (srcByte shl accumBits)
accumBits += 8
if accumBits >= WordBitWidth:
result.limbs[dstIdx] = accum
inc dstIdx
accumBits -= WordBitWidth
accum = srcByte shr (8 - accumBits)
if dstIdx < result.limbs.len:
result.limbs[dstIdx] = accum
for fillIdx in dstIdx+1 ..< result.limbs.len:
result.limbs[fillIdx] = 0
else: # src and CPU are bigEndian
dstIdx = result.limbs.len-1
for srcIdx in countdown(x.len-1, 0):
let srcByte = x[srcIdx]
accum = accum or (srcByte shl accumBits)
accumBits += 8
if accumBits >= WordBitWidth:
result.limbs[dstIdx] = accum
dec dstIdx
accumBits -= WordBitWidth
accum = srcByte shr (8 - accumBits)
if dstIdx > 0:
result.limbs[dstIdx] = accum
for fillIdx in 0 ..< dstIdx:
result.limbs[fillIdx] = 0
func fromBytesLE*[bits: static int](
T: typedesc[StUint[bits]],
x: openArray[byte]): T =
## Read little endian bytes and convert to an integer. At runtime, v must
## contain at least sizeof(T) bytes. By default, native endianess is used
## which is not portable! (i.e. use fixed-endian byte array or hex for serialization)
var accum: Word
var accumBits: int
var dstIdx: int
when cpuEndian == littleEndian: # src and CPU are little-endian
dstIdx = 0
for srcIdx in 0 ..< x.len:
let srcByte = x[srcIdx]
accum = accum or (srcByte shl accumBits)
accumBits += 8
if accumBits >= WordBitWidth:
result.limbs[dstIdx] = accum
inc dstIdx
accumBits -= WordBitWidth
accum = srcByte shr (8 - accumBits)
if dstIdx < result.limbs.len:
result.limbs[dstIdx] = accum
for fillIdx in dstIdx+1 ..< result.limbs.len:
result.limbs[fillIdx] = 0
else: # src is little endian, CPU is bigEndian
dstIdx = result.limbs.len-1
for srcIdx in 0 ..< x.len:
let srcByte = x[srcIdx]
accum = accum or (srcByte shl accumBits)
accumBits += 8
if accumBits >= WordBitWidth:
result.limbs[dstIdx] = accum
dec dstIdx
accumBits -= WordBitWidth
accum = srcByte shr (8 - accumBits)
if dstIdx > 0:
result.limbs[dstIdx] = accum
for fillIdx in 0 ..< dstIdx:
result.limbs[fillIdx] = 0
func fromBytes*[bits: static int](
T: typedesc[StUint[bits]],
x: openarray[byte],
srcEndian: Endianness = system.cpuEndian): T {.inline.} =
## Read an source bytearray with the specified endianness and
## convert it to an integer
when srcEndian == littleEndian:
result = fromBytesLE(T, x)
else:
result = fromBytesBE(T, x)
# TODO: What is the use-case for all the procs below?
# ------------------------------------------------------------------------------------------
func toBE*[bits: static int](x: StUint[bits]): StUint[bits] {.inline, deprecated: "Use toByteArrayBE instead".} =
## Convert a native endian value to big endian. Consider toBytesBE instead
## which may prevent some confusion.
if cpuEndian == bigEndian: x
else: x.swapBytes
func fromBE*[bits: static int](x: StUint[bits]): StUint[bits] {.inline, deprecated: "Use fromBytesBE instead".} =
## Read a big endian value and return the corresponding native endian
# there's no difference between this and toBE, except when reading the code
toBE(x)
func toLE*[bits: static int](x: StUint[bits]): StUint[bits] {.inline, deprecated.} =
## Convert a native endian value to little endian. Consider toBytesLE instead
## which may prevent some confusion.
if cpuEndian == littleEndian: x
else: x.swapBytes
func fromLE*[bits: static int](x: StUint[bits]): StUint[bits] {.inline, deprecated: "Use fromBytesLE instead".} =
## Read a little endian value and return the corresponding native endian
# there's no difference between this and toLE, except when reading the code
toLE(x)