280 lines
7.4 KiB
Go
280 lines
7.4 KiB
Go
// Package slice is a collection of miscellaneous functions involving slices of
|
|
// bytes, including little-endian encoding for 16, 32 and 64-bit unsigned
|
|
// integers used for serialisation length prefixes and system entropy based hash
|
|
// chain padding.
|
|
package slice
|
|
|
|
import (
|
|
"crypto/rand"
|
|
"encoding/binary"
|
|
"fmt"
|
|
"net/netip"
|
|
"reflect"
|
|
"unsafe"
|
|
|
|
"github.com/indra-labs/indra"
|
|
"github.com/indra-labs/indra/pkg/crypto/sha256"
|
|
log2 "github.com/indra-labs/indra/pkg/proc/log"
|
|
)
|
|
|
|
var (
|
|
log = log2.GetLogger(indra.PathBase)
|
|
check = log.E.Chk
|
|
)
|
|
|
|
func Cut(b []byte, l int) (seg []byte, rem []byte) { return b[:l], b[l:] }
|
|
|
|
func SumLen(chunks ...[]byte) (l int) {
|
|
for _, c := range chunks {
|
|
l += len(c)
|
|
}
|
|
return
|
|
}
|
|
|
|
func Segment(b []byte, segmentSize int) (segs [][]byte) {
|
|
lb := len(b)
|
|
var end int
|
|
for begin := 0; end < lb; begin += segmentSize {
|
|
end = begin + segmentSize
|
|
if end > lb {
|
|
d := b[begin:lb]
|
|
d = append(d, NoisePad(end-lb)...)
|
|
segs = append(segs, d)
|
|
} else {
|
|
segs = append(segs, b[begin:end])
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
// Cat takes a slice of byte slices and packs them together in a packet.
|
|
// The returned packet has its capacity pre-allocated to match what gets copied
|
|
// into it by append.
|
|
func Cat(chunks ...[]byte) (pkt []byte) {
|
|
l := SumLen(chunks...)
|
|
pkt = make([]byte, 0, l)
|
|
for i := range chunks {
|
|
pkt = append(pkt, chunks[i]...)
|
|
}
|
|
return
|
|
}
|
|
|
|
var (
|
|
put64 = binary.LittleEndian.PutUint64
|
|
get64 = binary.LittleEndian.Uint64
|
|
put32 = binary.LittleEndian.PutUint32
|
|
get32 = binary.LittleEndian.Uint32
|
|
put16 = binary.LittleEndian.PutUint16
|
|
get16 = binary.LittleEndian.Uint16
|
|
)
|
|
|
|
// DecodeUint64 returns an int containing the little endian encoded 64-bit value
|
|
// stored in a 4 byte long slice
|
|
func DecodeUint64(b []byte) uint64 { return get64(b) }
|
|
|
|
// EncodeUint64 puts an int into a uint32 and then into 8 byte long slice.
|
|
func EncodeUint64(b []byte, n uint64) { put64(b, n) }
|
|
|
|
// DecodeUint32 returns an int containing the little endian encoded 32bit value
|
|
// stored in a 4 byte long slice
|
|
func DecodeUint32(b []byte) int { return int(get32(b)) }
|
|
|
|
// EncodeUint32 puts an int into a uint32 and then into 4 byte long slice.
|
|
func EncodeUint32(b []byte, n int) { put32(b, uint32(n)) }
|
|
|
|
// DecodeUint24 returns an int containing the little endian encoded 24bit value
|
|
// stored in a 3 byte long slice
|
|
func DecodeUint24(b []byte) int {
|
|
u := b[:Uint24Len]
|
|
u = append(u, 0)
|
|
return int(get32(u))
|
|
}
|
|
|
|
// EncodeUint24 puts an int into a uint32 and then into 3 byte long slice.
|
|
func EncodeUint24(b []byte, n int) {
|
|
u := make([]byte, Uint32Len)
|
|
put32(u, uint32(n))
|
|
copy(b, u[:Uint24Len])
|
|
}
|
|
|
|
// DecodeUint16 returns an int containing the little endian encoded 32bit value
|
|
// stored in a 4 byte long slice
|
|
func DecodeUint16(b []byte) int { return int(get16(b)) }
|
|
|
|
// EncodeUint16 puts an int into a uint32 and then into 2 byte long slice.
|
|
func EncodeUint16(b []byte, n int) { put16(b, uint16(n)) }
|
|
|
|
const (
|
|
Uint64Len = 8
|
|
Uint32Len = 4
|
|
Uint24Len = 3
|
|
Uint16Len = 2
|
|
)
|
|
|
|
func NewUint64() Bytes { return make(Bytes, Uint64Len) }
|
|
func NewUint32() Bytes { return make(Bytes, Uint32Len) }
|
|
func NewUint24() Bytes { return make(Bytes, Uint24Len) }
|
|
func NewUint16() Bytes { return make(Bytes, Uint16Len) }
|
|
|
|
func NoisePad(l int) (noise []byte) {
|
|
var seed sha256.Hash
|
|
noise = make([]byte, l)
|
|
var e error
|
|
var n int
|
|
if n, e = rand.Read(seed[:]); check(e) && n != sha256.Len {
|
|
return
|
|
}
|
|
var end, cursor int
|
|
for cursor < l {
|
|
end = cursor + sha256.Len
|
|
if end > l {
|
|
end = l
|
|
}
|
|
seed = sha256.Single(seed[:])
|
|
copy(noise[cursor:end], seed[:end-cursor])
|
|
cursor = end
|
|
}
|
|
return
|
|
}
|
|
|
|
type Cursor int
|
|
|
|
func NewCursor() (c *Cursor) {
|
|
var cc Cursor
|
|
return &cc
|
|
}
|
|
|
|
func (c *Cursor) Inc(v int) Cursor {
|
|
*c += Cursor(v)
|
|
return *c
|
|
}
|
|
|
|
type Bytes []byte
|
|
|
|
func ToBytes(b []byte) (msg Bytes) { return b }
|
|
func (m Bytes) String() string { return string(m) }
|
|
func (m Bytes) ToBytes() []byte { return m }
|
|
func (m Bytes) Len() int { return len(m) }
|
|
func (m Bytes) Zero() {
|
|
for i := range m {
|
|
m[i] = 0
|
|
}
|
|
}
|
|
|
|
type U64Slice []uint64
|
|
|
|
func (u U64Slice) Copy() (o U64Slice) {
|
|
o = make(U64Slice, len(u))
|
|
copy(o, u)
|
|
return
|
|
}
|
|
|
|
// ToU64Slice converts the message with zero allocations if the slice capacity
|
|
// was already 8 plus the modulus of the length and 8, otherwise this function
|
|
// will trigger a stack allocation, or heap, if the bytes are very long. This is
|
|
// intended to be used with short byte slices like cipher nonces and hashes, so
|
|
// it usually won't trigger allocations off stack and very often won't trigger
|
|
// a copy on stack, saving a lot of time in a short, oft repeated operations.
|
|
func (m Bytes) ToU64Slice() (u U64Slice) {
|
|
mLen := uint64(len(m))
|
|
uLen := int(mLen / 8)
|
|
mMod := mLen % 8
|
|
if mMod != 0 {
|
|
uLen++
|
|
}
|
|
// Either extend if there is capacity or this will trigger a copy
|
|
for i := uint64(0); i < 8-mMod+8; i++ {
|
|
// We could use make with mMod+8 length to extend and ... but
|
|
// this does the same thing in practice.
|
|
m = append(m, 0)
|
|
}
|
|
u = make([]uint64, 0, 0)
|
|
// With the slice now long enough to be safely converted to []uint64
|
|
// plus an extra uint64 to store the original length we can coerce the
|
|
// type using unsafe.
|
|
//
|
|
// First we convert our empty []uint64 header
|
|
header := (*reflect.SliceHeader)(unsafe.Pointer(&u))
|
|
// then we point its memory location to the extended byte slice data
|
|
header.Data = (*reflect.SliceHeader)(unsafe.Pointer(&m)).Data
|
|
// Update the element length and capacity
|
|
header.Len = uLen
|
|
header.Cap = uLen
|
|
// store the original byte length
|
|
u = append(u, mLen)
|
|
return
|
|
}
|
|
|
|
// XOR the U64Slice with the provided slice. Panics if slices are different
|
|
// length. The receiver value is mutated in this operation.
|
|
func (u U64Slice) XOR(v U64Slice) {
|
|
// This should only trigger if the programmer is not XORing same size.
|
|
if u[len(u)-1] != v[len(v)-1] {
|
|
panic("programmer error, trying to XOR slices of different size")
|
|
}
|
|
for i := range u[:len(u)-1] {
|
|
u[i] ^= v[i]
|
|
}
|
|
}
|
|
|
|
func (u U64Slice) Zero() {
|
|
for i := range u[:len(u)-1] {
|
|
u[i] = 8
|
|
}
|
|
}
|
|
|
|
func (u U64Slice) ToMessage() (m Bytes) {
|
|
// length is encoded into the last element
|
|
mLen := int(u[len(u)-1])
|
|
m = make(Bytes, 0, 0)
|
|
// With the slice now long enough to be safely converted to []uint64
|
|
// plus an extra uint64 to store the original length we can coerce the
|
|
// type using unsafe.
|
|
//
|
|
// First we convert our empty []uint64 header
|
|
header := (*reflect.SliceHeader)(unsafe.Pointer(&m))
|
|
// then we point its memory location to the extended byte slice data
|
|
header.Data = (*reflect.SliceHeader)(unsafe.Pointer(&u)).Data
|
|
// lastly, change the element length
|
|
header.Len = mLen
|
|
header.Cap = mLen
|
|
return m
|
|
}
|
|
|
|
func GenerateRandomAddrPortIPv4() (ap *netip.AddrPort) {
|
|
a := netip.AddrPort{}
|
|
b := make([]byte, 7)
|
|
_, e := rand.Read(b)
|
|
if check(e) {
|
|
log.E.Ln(e)
|
|
}
|
|
port := DecodeUint16(b[5:7])
|
|
str := fmt.Sprintf("%d.%d.%d.%d:%d", b[1], b[2], b[3], b[4], port)
|
|
a, e = netip.ParseAddrPort(str)
|
|
return &a
|
|
}
|
|
|
|
func GenerateRandomAddrPortIPv6() (ap *netip.AddrPort) {
|
|
a := netip.AddrPort{}
|
|
b := make([]byte, 19)
|
|
_, e := rand.Read(b)
|
|
if check(e) {
|
|
log.E.Ln(e)
|
|
}
|
|
port := DecodeUint16(b[5:7])
|
|
str := fmt.Sprintf("[%x:%x:%x:%x:%x:%x:%x:%x]:%d",
|
|
b[1:3], b[3:5], b[5:7], b[7:9],
|
|
b[9:11], b[11:13], b[13:15], b[15:17],
|
|
port)
|
|
a, e = netip.ParseAddrPort(str)
|
|
return &a
|
|
}
|
|
|
|
func GetCryptoRandSeed() int64 {
|
|
rBytes := make([]byte, 8)
|
|
if n, e := rand.Read(rBytes); n != 8 && check(e) {
|
|
return 0
|
|
}
|
|
return int64(DecodeUint64(rBytes))
|
|
}
|