pkg/unpack/unpack.go (96 lines of code) (raw):
// Copyright 2022 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package unpack
import (
"encoding/binary"
"errors"
"fmt"
"strconv"
"github.com/googlecloudplatform/pi-delivery/pkg/ycd"
)
var ErrUnknownRadix error = errors.New("Unpack: unknown radix")
var ErrBufferTooSmall error = errors.New("Unpack: destination buffer is too small")
var ErrInvalidWord error = errors.New("Unpack: invalid word")
const (
zeros = "0000000000000000000"
WordSize = ycd.WordSize
)
func copyWithZero(dst []byte, s string, nz int) int {
return copy(dst, zeros[:nz]) + copy(dst[nz:], s)
}
// UnpackBlock reads packed digits from packed and writes unpacked strings to unpacked.
func UnpackBlock(unpacked, packed []byte, radix, pre int) (int, error) {
if len(packed) == 0 || len(unpacked) == 0 {
return 0, nil
}
dpw := ycd.DigitsPerWord(radix)
unpackedLen := UnpackedLen(int64(len(packed)-1), radix) - int64(pre)
if int64(len(unpacked)) < unpackedLen {
return 0, fmt.Errorf("%w: required = %v bytes, actual buffer = %v bytes",
ErrBufferTooSmall, unpackedLen, len(unpacked))
}
// Unpack the first word with pre.
// Copy dpw-pre bytes.
s := strconv.FormatUint(binary.LittleEndian.Uint64(packed), radix)
nz := dpw - len(s)
if nz < 0 {
return 0, fmt.Errorf("%w: word = %16x, unpacked = %s",
ErrInvalidWord, packed[:WordSize], s)
}
nzNeeded := nz - pre
if nzNeeded < 0 {
nzNeeded = 0
}
n := copy(unpacked, zeros[:nzNeeded])
if n < dpw-pre && n < len(unpacked) {
if nz < pre {
n += copy(unpacked[n:], s[pre-nz:dpw-pre-n+(pre-nz)])
} else {
n += copy(unpacked[n:], s[:dpw-pre-n])
}
}
if len(packed) == WordSize {
return n, nil
}
// Process until the second last word.
for i := WordSize; i < len(packed)-WordSize; i += WordSize {
s := strconv.FormatUint(binary.LittleEndian.Uint64(packed[i:]), radix)
nz := dpw - len(s)
if nz < 0 {
return n, fmt.Errorf("%w: word = %16x, unpacked = %s", ErrInvalidWord,
packed[i:i+WordSize], s)
}
n += copyWithZero(unpacked[n:], s, nz)
}
// Process the last word with post.
s = strconv.FormatUint(binary.LittleEndian.Uint64(packed[len(packed)-WordSize:]), radix)
nz = dpw - len(s)
if nz < 0 {
return n, fmt.Errorf("%w: word = %16x, unpacked = %s", ErrInvalidWord,
packed[len(packed)-WordSize:], s)
}
n += copy(unpacked[n:], zeros[:nz])
if n < len(unpacked) {
n += copy(unpacked[n:], s)
}
return n, nil
}
// UnpackedLen returns a number of bytes to store
// an unpacked sequence for n bytes of packed bytes.
func UnpackedLen(n int64, radix int) int64 {
return n / WordSize * int64(ycd.DigitsPerWord(radix))
}
// ToPackedOffsets calculates packed byte offsets for digits [off, off+len) where dpw is digits per word.
// There are overlapping words between block (file) boundaries if a block size is not aligned with words.
// This function takes that into account and returns increased values.
//
// Returned values:
// - start: starting byte offset of the first word containing off
// - n: number of bytes needed to [off, off+len)
// - pre: number of extra digits between the word aligment and off
// - post: number of extra digits between the word alignment and off+len-1
// n, pre, post will be 0 if len is 0.
func ToPackedOffsets(off, blockSize, len int64, dpw int) (start, n int64, pre, post int) {
if dpw <= 0 {
panic("ToPackedOffsets: zero or negative dpw")
}
padding := int64(dpw) - blockSize%int64(dpw)
if padding == int64(dpw) {
padding = 0
}
len += padding * ((off+len)/blockSize - off/blockSize)
off += padding * (off / blockSize)
start = off / int64(dpw)
pre = int(off - start*int64(dpw))
if len == 0 {
n = 0
post = 0
} else {
n = (len + int64(pre) + int64(dpw) - 1) / int64(dpw)
post = int(n*int64(dpw) - (len + int64(pre)))
}
start *= WordSize
n *= WordSize
return
}