| // Licensed to Apache Software Foundation (ASF) under one or more contributor |
| // license agreements. See the NOTICE file distributed with |
| // this work for additional information regarding copyright |
| // ownership. Apache Software Foundation (ASF) licenses this file to you 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 encoding |
| |
| import ( |
| "fmt" |
| |
| "github.com/apache/skywalking-banyandb/pkg/bytes" |
| "github.com/apache/skywalking-banyandb/pkg/compress/zstd" |
| ) |
| |
| // EncodeBytes encodes a string into dst. |
| func EncodeBytes(dst, b []byte) []byte { |
| dst = VarUint64ToBytes(dst, uint64(len(b))) |
| dst = append(dst, b...) |
| return dst |
| } |
| |
| // DecodeBytes decodes a string from src. |
| func DecodeBytes(src []byte) ([]byte, []byte, error) { |
| var n uint64 |
| src, n = BytesToVarUint64(src) |
| if uint64(len(src)) < n { |
| return nil, nil, fmt.Errorf("src is too short for reading string with size %d; len(src)=%d", n, len(src)) |
| } |
| return src[n:], src[:n], nil |
| } |
| |
| // EncodeBytesBlock encodes a block of strings into dst. |
| func EncodeBytesBlock(dst []byte, a [][]byte) []byte { |
| u64s := GenerateUint64List(len(a)) |
| aLens := u64s.L[:0] |
| for _, s := range a { |
| if s == nil { |
| aLens = append(aLens, 0) |
| } else { |
| // Offset by 1: "" (len 0) becomes 1, "a" (len 1) becomes 2, etc. |
| aLens = append(aLens, uint64(len(s))+1) |
| } |
| } |
| u64s.L = aLens |
| dst = EncodeUint64Block(dst, u64s.L) |
| ReleaseUint64List(u64s) |
| |
| bb := bbPool.Generate() |
| b := bb.Buf |
| for _, s := range a { |
| b = append(b, s...) |
| } |
| bb.Buf = b |
| dst = compressBlock(dst, bb.Buf) |
| bbPool.Release(bb) |
| |
| return dst |
| } |
| |
| // BytesBlockDecoder decodes a block of strings from src. |
| // It reuses the underlying buffer for storing the decoded strings. |
| type BytesBlockDecoder struct { |
| data []byte |
| } |
| |
| // Reset resets the decoder. |
| func (bbd *BytesBlockDecoder) Reset() { |
| bbd.data = bbd.data[:0] |
| } |
| |
| // Decode decodes a block of strings from src. |
| func (bbd *BytesBlockDecoder) Decode(dst [][]byte, src []byte, itemsCount uint64) ([][]byte, error) { |
| u64List := GenerateUint64List(0) |
| defer ReleaseUint64List(u64List) |
| |
| var tail []byte |
| var err error |
| u64List.L, tail, err = DecodeUint64Block(u64List.L[:0], src, itemsCount) |
| if err != nil { |
| return dst, fmt.Errorf("cannot decode string lengths: %w", err) |
| } |
| aLens := u64List.L |
| src = tail |
| |
| dataLen := len(bbd.data) |
| bbd.data, tail, err = decompressBlock(bbd.data, src) |
| if err != nil { |
| return dst, fmt.Errorf("cannot decode bytes block with strings: %w", err) |
| } |
| if len(tail) > 0 { |
| return dst, fmt.Errorf("unexpected non-empty tail after reading bytes block with strings; len(tail)=%d", len(tail)) |
| } |
| |
| data := bbd.data[dataLen:] |
| for _, sLen := range aLens { |
| if sLen == 0 { |
| dst = append(dst, nil) |
| continue |
| } |
| // Reverse the offset |
| actualLen := sLen - 1 |
| if uint64(len(data)) < actualLen { |
| return dst, fmt.Errorf("cannot decode a string with the length %d bytes from %d bytes", actualLen, len(data)) |
| } |
| if actualLen == 0 { |
| // Explicitly create non-nil empty slice for empty strings |
| dst = append(dst, []byte{}) |
| } else { |
| dst = append(dst, data[:actualLen]) |
| } |
| data = data[actualLen:] |
| } |
| |
| return dst, nil |
| } |
| |
| // DecodeWithTail decodes a block of strings from src and returns the remaining tail. |
| func (bbd *BytesBlockDecoder) DecodeWithTail(dst [][]byte, src []byte, itemsCount uint64) ([][]byte, []byte, error) { |
| u64List := GenerateUint64List(0) |
| defer ReleaseUint64List(u64List) |
| |
| var tail []byte |
| var err error |
| u64List.L, tail, err = DecodeUint64Block(u64List.L[:0], src, itemsCount) |
| if err != nil { |
| return dst, nil, fmt.Errorf("cannot decode string lengths: %w", err) |
| } |
| aLens := u64List.L |
| src = tail |
| |
| dataLen := len(bbd.data) |
| bbd.data, tail, err = decompressBlock(bbd.data, src) |
| if err != nil { |
| return dst, tail, fmt.Errorf("cannot decode bytes block with strings: %w", err) |
| } |
| |
| data := bbd.data[dataLen:] |
| for _, sLen := range aLens { |
| if sLen == 0 { |
| dst = append(dst, nil) |
| continue |
| } |
| // Reverse the offset |
| actualLen := sLen - 1 |
| if uint64(len(data)) < actualLen { |
| return dst, tail, fmt.Errorf("cannot decode a string with the length %d bytes from %d bytes", actualLen, len(data)) |
| } |
| if actualLen == 0 { |
| // Explicitly create non-nil empty slice for empty strings |
| dst = append(dst, []byte{}) |
| } else { |
| dst = append(dst, data[:actualLen]) |
| } |
| data = data[actualLen:] |
| } |
| |
| return dst, tail, nil |
| } |
| |
| // EncodeUint64Block encodes a block of uint64 values into dst. |
| func EncodeUint64Block(dst []byte, a []uint64) []byte { |
| bb := bbPool.Generate() |
| bb.Buf = encodeUint64List(bb.Buf[:0], a) |
| dst = compressBlock(dst, bb.Buf) |
| bbPool.Release(bb) |
| return dst |
| } |
| |
| // DecodeUint64Block decodes a block of uint64 values from src. |
| func DecodeUint64Block(dst []uint64, src []byte, itemsCount uint64) ([]uint64, []byte, error) { |
| bb := bbPool.Generate() |
| defer bbPool.Release(bb) |
| |
| var err error |
| bb.Buf, src, err = decompressBlock(bb.Buf[:0], src) |
| if err != nil { |
| return dst, src, fmt.Errorf("cannot decode bytes block: %w", err) |
| } |
| |
| dst, err = decodeUint64List(dst, bb.Buf, itemsCount) |
| if err != nil { |
| return dst, src, fmt.Errorf("cannot decode %d uint64 items from bytes block of length %d bytes: %w", itemsCount, len(bb.Buf), err) |
| } |
| return dst, src, nil |
| } |
| |
| const ( |
| uintBlockType8 = 0 |
| uintBlockType16 = 1 |
| uintBlockType32 = 2 |
| uintBlockType64 = 3 |
| ) |
| |
| func encodeUint64List(dst []byte, a []uint64) []byte { |
| nMax := uint64(0) |
| for _, n := range a { |
| if n > nMax { |
| nMax = n |
| } |
| } |
| switch { |
| case nMax < (1 << 8): |
| dst = append(dst, uintBlockType8) |
| for _, n := range a { |
| dst = append(dst, byte(n)) |
| } |
| case nMax < (1 << 16): |
| dst = append(dst, uintBlockType16) |
| for _, n := range a { |
| dst = Uint16ToBytes(dst, uint16(n)) |
| } |
| case nMax < (1 << 32): |
| dst = append(dst, uintBlockType32) |
| for _, n := range a { |
| dst = Uint32ToBytes(dst, uint32(n)) |
| } |
| default: |
| dst = append(dst, uintBlockType64) |
| for _, n := range a { |
| dst = Uint64ToBytes(dst, n) |
| } |
| } |
| return dst |
| } |
| |
| func decodeUint64List(dst []uint64, src []byte, itemsCount uint64) ([]uint64, error) { |
| if len(src) < 1 { |
| return dst, fmt.Errorf("cannot decode uint64 block type from empty src") |
| } |
| blockType := src[0] |
| src = src[1:] |
| |
| switch blockType { |
| case uintBlockType8: |
| if uint64(len(src)) != itemsCount { |
| return dst, fmt.Errorf("unexpected block length for %d items; got %d bytes; want %d bytes", itemsCount, len(src), itemsCount) |
| } |
| for _, v := range src { |
| dst = append(dst, uint64(v)) |
| } |
| case uintBlockType16: |
| if uint64(len(src)) != 2*itemsCount { |
| return dst, fmt.Errorf("unexpected block length for %d items; got %d bytes; want %d bytes", itemsCount, len(src), 2*itemsCount) |
| } |
| for len(src) > 0 { |
| v := BytesToUint16(src) |
| src = src[2:] |
| dst = append(dst, uint64(v)) |
| } |
| case uintBlockType32: |
| if uint64(len(src)) != 4*itemsCount { |
| return dst, fmt.Errorf("unexpected block length for %d items; got %d bytes; want %d bytes", itemsCount, len(src), 4*itemsCount) |
| } |
| for len(src) > 0 { |
| v := BytesToUint32(src) |
| src = src[4:] |
| dst = append(dst, uint64(v)) |
| } |
| case uintBlockType64: |
| if uint64(len(src)) != 8*itemsCount { |
| return dst, fmt.Errorf("unexpected block length for %d items; got %d bytes; want %d bytes", itemsCount, len(src), 8*itemsCount) |
| } |
| for len(src) > 0 { |
| v := BytesToUint64(src) |
| src = src[8:] |
| dst = append(dst, v) |
| } |
| default: |
| return dst, fmt.Errorf("unexpected uint64 block type: %d; want 0, 1, 2 or 3", blockType) |
| } |
| return dst, nil |
| } |
| |
| const ( |
| compressTypePlain = 0 |
| compressTypeZSTD = 1 |
| ) |
| |
| func compressBlock(dst, src []byte) []byte { |
| if len(src) < 128 { |
| dst = append(dst, compressTypePlain, byte(len(src))) |
| return append(dst, src...) |
| } |
| |
| dst = append(dst, compressTypeZSTD) |
| bb := bbPool.Generate() |
| bb.Buf = zstd.Compress(bb.Buf[:0], src, 1) |
| dst = VarUint64ToBytes(dst, uint64(len(bb.Buf))) |
| dst = append(dst, bb.Buf...) |
| bbPool.Release(bb) |
| return dst |
| } |
| |
| func decompressBlock(dst, src []byte) ([]byte, []byte, error) { |
| if len(src) < 1 { |
| return dst, src, fmt.Errorf("cannot decode block type from empty src") |
| } |
| blockType := src[0] |
| src = src[1:] |
| switch blockType { |
| case compressTypePlain: |
| if len(src) < 1 { |
| return dst, src, fmt.Errorf("cannot decode plain block size from empty src") |
| } |
| blockLen := int(src[0]) |
| src = src[1:] |
| if len(src) < blockLen { |
| return dst, src, fmt.Errorf("cannot read plain block with the size %d bytes from %b bytes", blockLen, len(src)) |
| } |
| |
| dst = append(dst, src[:blockLen]...) |
| src = src[blockLen:] |
| return dst, src, nil |
| case compressTypeZSTD: |
| tail, blockLen := BytesToVarUint64(src) |
| src = tail |
| if uint64(len(src)) < blockLen { |
| return dst, src, fmt.Errorf("cannot read compressed block with the size %d bytes from %d bytes", blockLen, len(src)) |
| } |
| compressedBlock := src[:blockLen] |
| src = src[blockLen:] |
| |
| // Decompress the block |
| var err error |
| bb := bbPool.Generate() |
| bb.Buf, err = zstd.Decompress(bb.Buf[:0], compressedBlock) |
| if err != nil { |
| return dst, src, fmt.Errorf("cannot decompress block: %w", err) |
| } |
| |
| // Copy the decompressed block to dst. |
| dst = append(dst, bb.Buf...) |
| bbPool.Release(bb) |
| return dst, src, nil |
| default: |
| return dst, src, fmt.Errorf("unexpected block type: %d; supported types: 0, 1", blockType) |
| } |
| } |
| |
| var bbPool = bytes.NewBufferPool("encoding.bytesBlock") |