blob: 888862d515f88292e04f769877f170572ccd904c [file]
// 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 (
"bytes"
"errors"
)
const (
// EntityDelimiter is the delimiter for entities in a variable-length array.
EntityDelimiter = '|'
// Escape is the escape character for entities in a variable-length array.
Escape = '\\'
)
// MarshalVarArray marshals a byte slice into a variable-length array format.
// It escapes delimiter and escape characters within the source slice.
func MarshalVarArray(dest, src []byte) []byte {
if bytes.IndexByte(src, EntityDelimiter) < 0 && bytes.IndexByte(src, Escape) < 0 {
dest = append(dest, src...)
dest = append(dest, EntityDelimiter)
return dest
}
for _, b := range src {
if b == EntityDelimiter || b == Escape {
dest = append(dest, Escape)
}
dest = append(dest, b)
}
dest = append(dest, EntityDelimiter)
return dest
}
// UnmarshalVarArray unmarshals a variable-length array from src starting at idx.
//
// WARNING: This function mutates src. Decoding is performed in-place by
// overwriting bytes in src[idx:next) to remove escape characters. The decoded
// value is the view src[idx:end] into the same backing array; copy it (for
// example, with bytes.Clone or append([]byte(nil), ...)) if you need to
// preserve the original encoded buffer or keep the decoded value independent of
// subsequent in-place decoding on the same buffer.
//
// It returns:
// - end: the index of the first byte after the decoded value (exclusive)
// - next: the index of the next element (the byte after the delimiter)
//
// The caller can iterate without creating subslices by tracking indices:
//
// for idx < len(src) {
// end, next, err := UnmarshalVarArray(src, idx)
// // use src[idx:end]
// idx = next
// }
func UnmarshalVarArray(src []byte, idx int) (int, int, error) {
if idx >= len(src) {
return 0, 0, errors.New("empty entity value")
}
if src[idx] == EntityDelimiter {
// Empty value; value is src[idx:idx], next starts after the delimiter.
return idx, idx + 1, nil
}
// Decode in-place: read index i, write index j.
writeIdx := idx
for readIdx := idx; readIdx < len(src); readIdx++ {
b := src[readIdx]
switch {
case b == Escape:
// Escape must be followed by at least one more byte.
if readIdx+1 >= len(src) {
return 0, 0, errors.New("invalid escape character")
}
readIdx++
src[writeIdx] = src[readIdx]
writeIdx++
case b == EntityDelimiter:
// Return end index of decoded value and index after delimiter.
return writeIdx, readIdx + 1, nil
default:
src[writeIdx] = b
writeIdx++
}
}
return 0, 0, errors.New("invalid variable array")
}