package bao
import (
"bytes"
"encoding/binary"
"errors"
"io"
"math/bits"
"lukechampine.com/blake3/guts"
)
func bytesToCV(b []byte ) (cv [8 ]uint32 ) {
_ = b [31 ]
for i := range cv {
cv [i ] = binary .LittleEndian .Uint32 (b [4 *i :])
}
return cv
}
func cvToBytes(cv *[8 ]uint32 ) *[32 ]byte {
var b [32 ]byte
for i , w := range cv {
binary .LittleEndian .PutUint32 (b [4 *i :], w )
}
return &b
}
func compressGroup(p []byte , counter uint64 ) guts .Node {
var stack [54 - guts .MaxSIMD ][8 ]uint32
var sc uint64
pushSubtree := func (cv [8 ]uint32 ) {
i := 0
for sc &(1 <<i ) != 0 {
cv = guts .ChainingValue (guts .ParentNode (stack [i ], cv , &guts .IV , 0 ))
i ++
}
stack [i ] = cv
sc ++
}
var buf [guts .MaxSIMD * guts .ChunkSize ]byte
var buflen int
for len (p ) > 0 {
if buflen == len (buf ) {
pushSubtree (guts .ChainingValue (guts .CompressBuffer (&buf , buflen , &guts .IV , counter +(sc *guts .MaxSIMD ), 0 )))
buflen = 0
}
n := copy (buf [buflen :], p )
buflen += n
p = p [n :]
}
n := guts .CompressBuffer (&buf , buflen , &guts .IV , counter +(sc *guts .MaxSIMD ), 0 )
for i := bits .TrailingZeros64 (sc ); i < bits .Len64 (sc ); i ++ {
if sc &(1 <<i ) != 0 {
n = guts .ParentNode (stack [i ], guts .ChainingValue (n ), &guts .IV , 0 )
}
}
return n
}
func EncodedSize (dataLen int , group int , outboard bool ) int {
groupSize := guts .ChunkSize << group
size := 8
if dataLen > 0 {
chunks := (dataLen + groupSize - 1 ) / groupSize
cvs := 2 *chunks - 2
size += cvs * 32
}
if !outboard {
size += dataLen
}
return size
}
func Encode (dst io .WriterAt , data io .Reader , dataLen int64 , group int , outboard bool ) ([32 ]byte , error ) {
groupSize := uint64 (guts .ChunkSize << group )
buf := make ([]byte , groupSize )
var err error
read := func (p []byte ) []byte {
if err == nil {
_, err = io .ReadFull (data , p )
}
return p
}
write := func (p []byte , off uint64 ) {
if err == nil {
_, err = dst .WriteAt (p , int64 (off ))
}
}
var counter uint64
var rec func (bufLen uint64 , flags uint32 , off uint64 ) (uint64 , [8 ]uint32 )
rec = func (bufLen uint64 , flags uint32 , off uint64 ) (uint64 , [8 ]uint32 ) {
if err != nil {
return 0 , [8 ]uint32 {}
} else if bufLen <= groupSize {
g := read (buf [:bufLen ])
if !outboard {
write (g , off )
}
n := compressGroup (g , counter )
counter += bufLen / guts .ChunkSize
n .Flags |= flags
return 0 , guts .ChainingValue (n )
}
mid := uint64 (1 ) << (bits .Len64 (bufLen -1 ) - 1 )
lchildren , l := rec (mid , 0 , off +64 )
llen := lchildren * 32
if !outboard {
llen += (mid / groupSize ) * groupSize
}
rchildren , r := rec (bufLen -mid , 0 , off +64 +llen )
write (cvToBytes (&l )[:], off )
write (cvToBytes (&r )[:], off +32 )
return 2 + lchildren + rchildren , guts .ChainingValue (guts .ParentNode (l , r , &guts .IV , flags ))
}
binary .LittleEndian .PutUint64 (buf [:8 ], uint64 (dataLen ))
write (buf [:8 ], 0 )
_ , root := rec (uint64 (dataLen ), guts .FlagRoot , 8 )
return *cvToBytes (&root ), err
}
func Decode (dst io .Writer , data , outboard io .Reader , group int , root [32 ]byte ) (bool , error ) {
if outboard == nil {
outboard = data
}
groupSize := uint64 (guts .ChunkSize << group )
buf := make ([]byte , groupSize )
var err error
read := func (r io .Reader , p []byte ) []byte {
if err == nil {
_, err = io .ReadFull (r , p )
}
return p
}
write := func (w io .Writer , p []byte ) {
if err == nil {
_, err = w .Write (p )
}
}
readParent := func () (l , r [8 ]uint32 ) {
read (outboard , buf [:64 ])
return bytesToCV (buf [:32 ]), bytesToCV (buf [32 :])
}
var counter uint64
var rec func (cv [8 ]uint32 , bufLen uint64 , flags uint32 ) bool
rec = func (cv [8 ]uint32 , bufLen uint64 , flags uint32 ) bool {
if err != nil {
return false
} else if bufLen <= groupSize {
n := compressGroup (read (data , buf [:bufLen ]), counter )
counter += bufLen / guts .ChunkSize
n .Flags |= flags
valid := cv == guts .ChainingValue (n )
if valid {
write (dst , buf [:bufLen ])
}
return valid
}
l , r := readParent ()
n := guts .ParentNode (l , r , &guts .IV , flags )
mid := uint64 (1 ) << (bits .Len64 (bufLen -1 ) - 1 )
return guts .ChainingValue (n ) == cv && rec (l , mid , 0 ) && rec (r , bufLen -mid , 0 )
}
read (outboard , buf [:8 ])
dataLen := binary .LittleEndian .Uint64 (buf [:8 ])
ok := rec (bytesToCV (root [:]), dataLen , guts .FlagRoot )
return ok , err
}
type bufferAt struct {
buf []byte
}
func (b *bufferAt ) WriteAt (p []byte , off int64 ) (int , error ) {
if copy (b .buf [off :], p ) != len (p ) {
panic ("bad buffer size" )
}
return len (p ), nil
}
func EncodeBuf (data []byte , group int , outboard bool ) ([]byte , [32 ]byte ) {
buf := bufferAt {buf : make ([]byte , EncodedSize (len (data ), group , outboard ))}
root , _ := Encode (&buf , bytes .NewReader (data ), int64 (len (data )), group , outboard )
return buf .buf , root
}
func VerifyBuf (data , outboard []byte , group int , root [32 ]byte ) bool {
d , o := bytes .NewBuffer (data ), bytes .NewBuffer (outboard )
var or io .Reader = o
if outboard == nil {
or = nil
}
ok , _ := Decode (io .Discard , d , or , group , root )
return ok && d .Len () == 0 && o .Len () == 0
}
func ExtractSlice (dst io .Writer , data , outboard io .Reader , group int , offset uint64 , length uint64 ) error {
combinedEncoding := outboard == nil
if combinedEncoding {
outboard = data
}
groupSize := uint64 (guts .ChunkSize << group )
buf := make ([]byte , groupSize )
var err error
read := func (r io .Reader , n uint64 , copy bool ) {
if err == nil {
_, err = io .ReadFull (r , buf [:n ])
if err == nil && copy {
_, err = dst .Write (buf [:n ])
}
}
}
var rec func (pos , bufLen uint64 )
rec = func (pos , bufLen uint64 ) {
inSlice := pos < (offset +length ) && offset < (pos +bufLen )
if err != nil {
return
} else if bufLen <= groupSize {
if combinedEncoding || inSlice {
read (data , bufLen , inSlice )
}
return
}
read (outboard , 64 , inSlice )
mid := uint64 (1 ) << (bits .Len64 (bufLen -1 ) - 1 )
rec (pos , mid )
rec (pos +mid , bufLen -mid )
}
read (outboard , 8 , true )
dataLen := binary .LittleEndian .Uint64 (buf [:8 ])
if dataLen < offset +length {
return errors .New ("invalid slice length" )
}
rec (0 , dataLen )
return err
}
func DecodeSlice (dst io .Writer , data io .Reader , group int , offset , length uint64 , root [32 ]byte ) (bool , error ) {
groupSize := uint64 (guts .ChunkSize << group )
buf := make ([]byte , groupSize )
var err error
read := func (n uint64 ) []byte {
if err == nil {
_, err = io .ReadFull (data , buf [:n ])
}
return buf [:n ]
}
readParent := func () (l , r [8 ]uint32 ) {
read (64 )
return bytesToCV (buf [:32 ]), bytesToCV (buf [32 :])
}
write := func (p []byte ) {
if err == nil {
_, err = dst .Write (p )
}
}
var rec func (cv [8 ]uint32 , pos , bufLen uint64 , flags uint32 ) bool
rec = func (cv [8 ]uint32 , pos , bufLen uint64 , flags uint32 ) bool {
inSlice := pos < (offset +length ) && offset < (pos +bufLen )
if err != nil {
return false
} else if bufLen <= groupSize {
if !inSlice {
return true
}
n := compressGroup (read (bufLen ), pos /guts .ChunkSize )
n .Flags |= flags
valid := cv == guts .ChainingValue (n )
if valid {
p := buf [:bufLen ]
if pos +bufLen > offset +length {
p = p [:offset +length -pos ]
}
if pos < offset {
p = p [offset -pos :]
}
write (p )
}
return valid
}
if !inSlice {
return true
}
l , r := readParent ()
n := guts .ParentNode (l , r , &guts .IV , flags )
mid := uint64 (1 ) << (bits .Len64 (bufLen -1 ) - 1 )
return guts .ChainingValue (n ) == cv && rec (l , pos , mid , 0 ) && rec (r , pos +mid , bufLen -mid , 0 )
}
dataLen := binary .LittleEndian .Uint64 (read (8 ))
if dataLen < offset +length {
return false , errors .New ("invalid slice length" )
}
ok := rec (bytesToCV (root [:]), 0 , dataLen , guts .FlagRoot )
return ok , err
}
func VerifySlice (data []byte , group int , offset uint64 , length uint64 , root [32 ]byte ) ([]byte , bool ) {
d := bytes .NewBuffer (data )
var buf bytes .Buffer
if ok , _ := DecodeSlice (&buf , d , group , offset , length , root ); !ok || d .Len () > 0 {
return nil , false
}
return buf .Bytes (), true
}
func VerifyChunk (chunks , outboard []byte , group int , offset uint64 , root [32 ]byte ) bool {
cbuf := bytes .NewBuffer (chunks )
obuf := bytes .NewBuffer (outboard )
groupSize := uint64 (guts .ChunkSize << group )
length := uint64 (len (chunks ))
nodesWithin := func (bufLen uint64 ) int {
n := int (bufLen / groupSize )
if bufLen %groupSize == 0 {
n --
}
return n
}
var rec func (cv [8 ]uint32 , pos , bufLen uint64 , flags uint32 ) bool
rec = func (cv [8 ]uint32 , pos , bufLen uint64 , flags uint32 ) bool {
inSlice := pos < (offset +length ) && offset < (pos +bufLen )
if bufLen <= groupSize {
if !inSlice {
return true
}
n := compressGroup (cbuf .Next (int (groupSize )), pos /guts .ChunkSize )
n .Flags |= flags
return cv == guts .ChainingValue (n )
}
if !inSlice {
_ = obuf .Next (64 * nodesWithin (bufLen ))
return true
}
l , r := bytesToCV (obuf .Next (32 )), bytesToCV (obuf .Next (32 ))
n := guts .ParentNode (l , r , &guts .IV , flags )
mid := uint64 (1 ) << (bits .Len64 (bufLen -1 ) - 1 )
return guts .ChainingValue (n ) == cv && rec (l , pos , mid , 0 ) && rec (r , pos +mid , bufLen -mid , 0 )
}
if obuf .Len () < 8 {
return false
}
dataLen := binary .LittleEndian .Uint64 (obuf .Next (8 ))
if dataLen < offset +length || obuf .Len () != 64 *nodesWithin (dataLen ) {
return false
}
return rec (bytesToCV (root [:]), 0 , dataLen , guts .FlagRoot )
}
The pages are generated with Golds v0.8.2 . (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu .
PR and bug reports are welcome and can be submitted to the issue list .
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds .