blob: 41f92986464264731dd8888b5962fd333cde3238 [file] [log] [blame]
package msgp
import (
"math"
)
// Locate returns a []byte pointing to the field
// in a messagepack map with the provided key. (The returned []byte
// points to a sub-slice of 'raw'; Locate does no allocations.) If the
// key doesn't exist in the map, a zero-length []byte will be returned.
func Locate(key string, raw []byte) []byte {
s, n := locate(raw, key)
return raw[s:n]
}
// Replace takes a key ("key") in a messagepack map ("raw")
// and replaces its value with the one provided and returns
// the new []byte. The returned []byte may point to the same
// memory as "raw". Replace makes no effort to evaluate the validity
// of the contents of 'val'. It may use up to the full capacity of 'raw.'
// Replace returns 'nil' if the field doesn't exist or if the object in 'raw'
// is not a map.
func Replace(key string, raw []byte, val []byte) []byte {
start, end := locate(raw, key)
if start == end {
return nil
}
return replace(raw, start, end, val, true)
}
// CopyReplace works similarly to Replace except that the returned
// byte slice does not point to the same memory as 'raw'. CopyReplace
// returns 'nil' if the field doesn't exist or 'raw' isn't a map.
func CopyReplace(key string, raw []byte, val []byte) []byte {
start, end := locate(raw, key)
if start == end {
return nil
}
return replace(raw, start, end, val, false)
}
// Remove removes a key-value pair from 'raw'. It returns
// 'raw' unchanged if the key didn't exist.
func Remove(key string, raw []byte) []byte {
start, end := locateKV(raw, key)
if start == end {
return raw
}
raw = raw[:start+copy(raw[start:], raw[end:])]
return resizeMap(raw, -1)
}
// HasKey returns whether the map in 'raw' has
// a field with key 'key'
func HasKey(key string, raw []byte) bool {
sz, bts, err := ReadMapHeaderBytes(raw)
if err != nil {
return false
}
var field []byte
for i := uint32(0); i < sz; i++ {
field, bts, err = ReadStringZC(bts)
if err != nil {
return false
}
if UnsafeString(field) == key {
return true
}
}
return false
}
func replace(raw []byte, start int, end int, val []byte, inplace bool) []byte {
ll := end - start // length of segment to replace
lv := len(val)
if inplace {
extra := lv - ll
// fastest case: we're doing
// a 1:1 replacement
if extra == 0 {
copy(raw[start:], val)
return raw
} else if extra < 0 {
// 'val' smaller than replaced value
// copy in place and shift back
x := copy(raw[start:], val)
y := copy(raw[start+x:], raw[end:])
return raw[:start+x+y]
} else if extra < cap(raw)-len(raw) {
// 'val' less than (cap-len) extra bytes
// copy in place and shift forward
raw = raw[0 : len(raw)+extra]
// shift end forward
copy(raw[end+extra:], raw[end:])
copy(raw[start:], val)
return raw
}
}
// we have to allocate new space
out := make([]byte, len(raw)+len(val)-ll)
x := copy(out, raw[:start])
y := copy(out[x:], val)
copy(out[x+y:], raw[end:])
return out
}
// locate does a naive O(n) search for the map key; returns start, end
// (returns 0,0 on error)
func locate(raw []byte, key string) (start int, end int) {
var (
sz uint32
bts []byte
field []byte
err error
)
sz, bts, err = ReadMapHeaderBytes(raw)
if err != nil {
return
}
// loop and locate field
for i := uint32(0); i < sz; i++ {
field, bts, err = ReadStringZC(bts)
if err != nil {
return 0, 0
}
if UnsafeString(field) == key {
// start location
l := len(raw)
start = l - len(bts)
bts, err = Skip(bts)
if err != nil {
return 0, 0
}
end = l - len(bts)
return
}
bts, err = Skip(bts)
if err != nil {
return 0, 0
}
}
return 0, 0
}
// locate key AND value
func locateKV(raw []byte, key string) (start int, end int) {
var (
sz uint32
bts []byte
field []byte
err error
)
sz, bts, err = ReadMapHeaderBytes(raw)
if err != nil {
return 0, 0
}
for i := uint32(0); i < sz; i++ {
tmp := len(bts)
field, bts, err = ReadStringZC(bts)
if err != nil {
return 0, 0
}
if UnsafeString(field) == key {
start = len(raw) - tmp
bts, err = Skip(bts)
if err != nil {
return 0, 0
}
end = len(raw) - len(bts)
return
}
bts, err = Skip(bts)
if err != nil {
return 0, 0
}
}
return 0, 0
}
// delta is delta on map size
func resizeMap(raw []byte, delta int64) []byte {
var sz int64
switch raw[0] {
case mmap16:
sz = int64(big.Uint16(raw[1:]))
if sz+delta <= math.MaxUint16 {
big.PutUint16(raw[1:], uint16(sz+delta))
return raw
}
if cap(raw)-len(raw) >= 2 {
raw = raw[0 : len(raw)+2]
copy(raw[5:], raw[3:])
big.PutUint32(raw[1:], uint32(sz+delta))
return raw
}
n := make([]byte, 0, len(raw)+5)
n = AppendMapHeader(n, uint32(sz+delta))
return append(n, raw[3:]...)
case mmap32:
sz = int64(big.Uint32(raw[1:]))
big.PutUint32(raw[1:], uint32(sz+delta))
return raw
default:
sz = int64(rfixmap(raw[0]))
if sz+delta < 16 {
raw[0] = wfixmap(uint8(sz + delta))
return raw
} else if sz+delta <= math.MaxUint16 {
if cap(raw)-len(raw) >= 2 {
raw = raw[0 : len(raw)+2]
copy(raw[3:], raw[1:])
raw[0] = mmap16
big.PutUint16(raw[1:], uint16(sz+delta))
return raw
}
n := make([]byte, 0, len(raw)+5)
n = AppendMapHeader(n, uint32(sz+delta))
return append(n, raw[1:]...)
}
if cap(raw)-len(raw) >= 4 {
raw = raw[0 : len(raw)+4]
copy(raw[5:], raw[1:])
raw[0] = mmap32
big.PutUint32(raw[1:], uint32(sz+delta))
return raw
}
n := make([]byte, 0, len(raw)+5)
n = AppendMapHeader(n, uint32(sz+delta))
return append(n, raw[1:]...)
}
}