# 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: [], noinit, gcsafe.} # 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: 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 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 = bigEndian): array[bits div 8, byte] {.inline.} = ## Default to bigEndian if endian == littleEndian: result = x.toBytesLE() else: result = x.toBytesBE() # Deserialization # ------------------------------------------------------------------------------------------ func fromBytesBE*[bits: static int]( T: typedesc[StUint[bits]], x: openArray[byte]): T {.raises: [], noinit, gcsafe.} = ## 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) var accum: Word = 0 var accumBits: int = 0 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 = Word(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 = Word(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 = 0 var accumBits: int = 0 var dstIdx: int when cpuEndian == littleEndian: # src and CPU are little-endian dstIdx = 0 for srcIdx in 0 ..< x.len: let srcByte = Word(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 = Word(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 = bigEndian): T {.inline.} = ## Read an source bytearray with the specified endianness and ## convert it to an integer ## Default to bigEndian if srcEndian == littleEndian: result = fromBytesLE(T, x) else: result = fromBytesBE(T, x) # Signed integer version of above funcs func toBytesLE*[bits: static int](src: StInt[bits]): array[bits div 8, byte] {.inline.} = toBytesLE(src.imp) func toBytesBE*[bits: static int](src: StInt[bits]): array[bits div 8, byte] {.inline.} = toBytesBE(src.imp) func toBytes*[bits: static int](x: StInt[bits], endian: Endianness = bigEndian): array[bits div 8, byte] {.inline.} = toBytes(x.imp, endian) func fromBytesBE*[bits: static int]( T: typedesc[StInt[bits]], x: openArray[byte]): T {.raises: [], noinit, gcsafe, inline.} = fromBytesBE(type result.imp, x) func fromBytesLE*[bits: static int]( T: typedesc[StInt[bits]], x: openArray[byte]): T {.inline.} = fromBytesLE(type result.imp, x) func fromBytes*[bits: static int]( T: typedesc[StInt[bits]], x: openArray[byte], srcEndian: Endianness = bigEndian): T {.inline.} = fromBytes(type result.imp, x, srcEndian)