package codec import ( "fmt" "github.com/pingcap/errors" ) const ( encGroupSize = 8 encMarker = byte(0xFF) encPad = byte(0x0) ) var pads = make([]byte, encGroupSize) // EncodeBytes guarantees the encoded value is in ascending order for comparison, // encoding with the following rule: // [group1][marker1]...[groupN][markerN] // group is 8 bytes slice which is padding with 0. // marker is `0xFF - padding 0 count` // For example: // [] -> [0, 0, 0, 0, 0, 0, 0, 0, 247] // [1, 2, 3] -> [1, 2, 3, 0, 0, 0, 0, 0, 250] // [1, 2, 3, 0] -> [1, 2, 3, 0, 0, 0, 0, 0, 251] // [1, 2, 3, 4, 5, 6, 7, 8] -> [1, 2, 3, 4, 5, 6, 7, 8, 255, 0, 0, 0, 0, 0, 0, 0, 0, 247] // Refer: https://github.com/facebook/mysql-5.6/wiki/MyRocks-record-format#memcomparable-format func EncodeBytes(data []byte) []byte { // Allocate more space to avoid unnecessary slice growing. // Assume that the byte slice size is about `(len(data) / encGroupSize + 1) * (encGroupSize + 1)` bytes, // that is `(len(data) / 8 + 1) * 9` in our implement. dLen := len(data) result := make([]byte, 0, (dLen/encGroupSize+1)*(encGroupSize+1)+8) // make extra room for appending ts for idx := 0; idx <= dLen; idx += encGroupSize { remain := dLen - idx padCount := 0 if remain >= encGroupSize { result = append(result, data[idx:idx+encGroupSize]...) } else { padCount = encGroupSize - remain result = append(result, data[idx:]...) result = append(result, pads[:padCount]...) } marker := encMarker - byte(padCount) result = append(result, marker) } return result } // DecodeBytes decodes bytes which is encoded by EncodeBytes before, // returns the leftover bytes and decoded value if no error. func DecodeBytes(b []byte) ([]byte, []byte, error) { data := make([]byte, 0, len(b)) for { if len(b) < encGroupSize+1 { return nil, nil, fmt.Errorf("insufficient bytes to decode value: %d", len(b)) } groupBytes := b[:encGroupSize+1] group := groupBytes[:encGroupSize] marker := groupBytes[encGroupSize] padCount := encMarker - marker if padCount > encGroupSize { return nil, nil, errors.Errorf("invalid marker byte, group bytes %q", groupBytes) } realGroupSize := encGroupSize - padCount data = append(data, group[:realGroupSize]...) b = b[encGroupSize+1:] if padCount != 0 { // Check validity of padding bytes. for _, v := range group[realGroupSize:] { if v != encPad { return nil, nil, errors.Errorf("invalid padding byte, group bytes %q", groupBytes) } } break } } return b, data, nil }