// Copyright (c) 2015-2026 MinIO, Inc. // // This file is part of MinIO Object Storage stack // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . package cmd import ( "bytes" "encoding/binary" "errors" "hash/crc32" "io" "time" "github.com/tinylib/msgp/msgp" ) const ( fastOpenFrameVersion uint16 = 1 fastOpenFramePreludeLen = 15 fastOpenFrameMaxHeaderLen = 4 << 20 ) var ( fastOpenFrameMagic = [4]byte{'B', 'F', 'G', '1'} errFastOpenFrameBadMagic = errors.New("fastopen frame: bad magic") errFastOpenFrameBadVersion = errors.New("fastopen frame: bad version") errFastOpenFrameBadCRC = errors.New("fastopen frame: bad crc") errFastOpenFrameHeaderTooLarge = errors.New("fastopen frame: header too large") errFastOpenFrameBadPayload = errors.New("fastopen frame: bad payload") errFastOpenFrameBadBodyMode = errors.New("fastopen frame: bad body mode") errFastOpenFrameBadStatus = errors.New("fastopen frame: bad status") errFastOpenFrameBadBitrot = errors.New("fastopen frame: bad bitrot algorithm") ) // FastOpenPartFlags carries protocol flags for a FastOpenPart request. type FastOpenPartFlags uint32 // FastOpenPartRequest describes a storage-node FastOpen part request. type FastOpenPartRequest struct { Version uint16 VersionID string PartNumber int Offset int64 Length int64 Flags FastOpenPartFlags } // FastOpenFramePrelude is the fixed-size header at byte zero of every // successful FastOpenPart stream. type FastOpenFramePrelude struct { Magic [4]byte Version uint16 HeaderLen uint32 HeaderCRC32 uint32 BodyMode FastOpenBodyMode } // FastOpenBodyMode describes how the payload following a FastOpen frame is encoded. type FastOpenBodyMode uint8 const ( // FastOpenBodyShard streams the requested shard bytes from the local disk. FastOpenBodyShard FastOpenBodyMode = iota + 1 // FastOpenBodyInline carries the object bytes directly in the frame body. FastOpenBodyInline // FastOpenBodyMetadataOnly returns metadata without a payload body. FastOpenBodyMetadataOnly // FastOpenBodyTransitioned indicates that the object body is remote-tiered. FastOpenBodyTransitioned ) // FastOpenFrameStatus is the object lookup result encoded in a FastOpen frame. type FastOpenFrameStatus uint8 const ( // FastOpenStatusOK indicates a usable metadata frame. FastOpenStatusOK FastOpenFrameStatus = iota // FastOpenStatusDeleteMarker indicates that the selected version is a delete marker. FastOpenStatusDeleteMarker // FastOpenStatusNotFound indicates the object was not found. FastOpenStatusNotFound // FastOpenStatusVersionNotFound indicates the requested version was not found. FastOpenStatusVersionNotFound // FastOpenStatusUnsupported indicates the disk cannot serve the object through FastOpen. FastOpenStatusUnsupported ) // CoalescedMetadataFrame is the compact metadata envelope returned by FastOpenPart. type CoalescedMetadataFrame struct { Status FastOpenFrameStatus Meta FastOpenGETMeta BodyMode FastOpenBodyMode BodyLen int64 } // FastOpenGETMeta is the subset of FileInfo needed to reconstruct GET metadata. type FastOpenGETMeta struct { VersionID string IsLatest bool Legacy bool ModTimeUnixNano int64 Size int64 Metadata map[string]string Transition FastOpenTransitionMeta Part FastOpenPartMeta Erasure FastOpenErasureMeta Checksum []byte NumVersions int SuccessorModTimeNanos int64 } // FastOpenTransitionMeta carries remote-tier metadata for a FastOpen GET. type FastOpenTransitionMeta struct { Status string Object string Tier string VersionID string } // FastOpenPartMeta intentionally carries only fields needed by Phase 1 // full-object GET. Range, partNumber, and multipart body paths must stay out of // FastOpen while this compact shape omits per-part index/checksum details. type FastOpenPartMeta struct { Number int Size int64 ActualSize int64 } // FastOpenErasureMeta carries erasure layout data required to decode a shard body. type FastOpenErasureMeta struct { DataBlocks int ParityBlocks int BlockSize int64 Index int Distribution []int Bitrot FastOpenBitrotMeta } // FastOpenBitrotMeta carries per-part checksum metadata for shard verification. type FastOpenBitrotMeta struct { PartNumber int Algorithm uint8 Hash []byte } func encodeFastOpenFrame(frame CoalescedMetadataFrame) ([]byte, error) { payload, err := marshalCoalescedMetadataFrame(frame) if err != nil { return nil, err } if len(payload) > fastOpenFrameMaxHeaderLen { return nil, errFastOpenFrameHeaderTooLarge } prelude := FastOpenFramePrelude{ Magic: fastOpenFrameMagic, Version: fastOpenFrameVersion, HeaderLen: uint32(len(payload)), HeaderCRC32: crc32.ChecksumIEEE(payload), BodyMode: frame.BodyMode, } out := make([]byte, fastOpenFramePreludeLen+len(payload)) encodeFastOpenFramePrelude(out[:fastOpenFramePreludeLen], prelude) copy(out[fastOpenFramePreludeLen:], payload) return out, nil } func readFastOpenFrame(r io.Reader) (FastOpenFramePrelude, CoalescedMetadataFrame, error) { var prelude FastOpenFramePrelude var frame CoalescedMetadataFrame preludeBuf := make([]byte, fastOpenFramePreludeLen) if _, err := io.ReadFull(r, preludeBuf); err != nil { return prelude, frame, err } prelude, err := decodeFastOpenFramePrelude(preludeBuf) if err != nil { return prelude, frame, err } if prelude.HeaderLen > fastOpenFrameMaxHeaderLen { return prelude, frame, errFastOpenFrameHeaderTooLarge } payload := make([]byte, prelude.HeaderLen) if _, err = io.ReadFull(r, payload); err != nil { return prelude, frame, err } if got := crc32.ChecksumIEEE(payload); got != prelude.HeaderCRC32 { return prelude, frame, errFastOpenFrameBadCRC } frame, err = unmarshalCoalescedMetadataFrame(payload) if err != nil { return prelude, frame, err } if frame.BodyMode != prelude.BodyMode { return prelude, frame, errFastOpenFrameBadPayload } return prelude, frame, nil } func encodeFastOpenFramePrelude(dst []byte, p FastOpenFramePrelude) { copy(dst[:4], p.Magic[:]) binary.BigEndian.PutUint16(dst[4:6], p.Version) binary.BigEndian.PutUint32(dst[6:10], p.HeaderLen) binary.BigEndian.PutUint32(dst[10:14], p.HeaderCRC32) dst[14] = byte(p.BodyMode) } func decodeFastOpenFramePrelude(src []byte) (FastOpenFramePrelude, error) { var p FastOpenFramePrelude if len(src) < fastOpenFramePreludeLen { return p, io.ErrUnexpectedEOF } copy(p.Magic[:], src[:4]) if !bytes.Equal(p.Magic[:], fastOpenFrameMagic[:]) { return p, errFastOpenFrameBadMagic } p.Version = binary.BigEndian.Uint16(src[4:6]) if p.Version != fastOpenFrameVersion { return p, errFastOpenFrameBadVersion } p.HeaderLen = binary.BigEndian.Uint32(src[6:10]) p.HeaderCRC32 = binary.BigEndian.Uint32(src[10:14]) p.BodyMode = FastOpenBodyMode(src[14]) if !p.BodyMode.valid() { return p, errFastOpenFrameBadBodyMode } return p, nil } func (m FastOpenBodyMode) valid() bool { switch m { case FastOpenBodyShard, FastOpenBodyInline, FastOpenBodyMetadataOnly, FastOpenBodyTransitioned: return true default: return false } } func (s FastOpenFrameStatus) valid() bool { switch s { case FastOpenStatusOK, FastOpenStatusDeleteMarker, FastOpenStatusNotFound, FastOpenStatusVersionNotFound, FastOpenStatusUnsupported: return true default: return false } } func fileInfoToFastOpenGETMeta(fi FileInfo) (FastOpenGETMeta, error) { meta := FastOpenGETMeta{ VersionID: fi.VersionID, IsLatest: fi.IsLatest, Legacy: fi.XLV1, ModTimeUnixNano: fastOpenTimeUnixNano(fi.ModTime), Size: fi.Size, Metadata: cloneStringMap(fi.Metadata), Transition: FastOpenTransitionMeta{ Status: fi.TransitionStatus, Object: fi.TransitionedObjName, Tier: fi.TransitionTier, VersionID: fi.TransitionVersionID, }, Erasure: FastOpenErasureMeta{ DataBlocks: fi.Erasure.DataBlocks, ParityBlocks: fi.Erasure.ParityBlocks, BlockSize: fi.Erasure.BlockSize, Index: fi.Erasure.Index, Distribution: append([]int(nil), fi.Erasure.Distribution...), }, Checksum: append([]byte(nil), fi.Checksum...), NumVersions: fi.NumVersions, SuccessorModTimeNanos: fastOpenTimeUnixNano(fi.SuccessorModTime), } if len(fi.Parts) > 0 { meta.Part = FastOpenPartMeta{ Number: fi.Parts[0].Number, Size: fi.Parts[0].Size, ActualSize: fi.Parts[0].ActualSize, } } if meta.Part.Number != 0 { ckSum := fi.Erasure.GetChecksumInfo(meta.Part.Number) algo, err := fastOpenBitrotCode(ckSum.Algorithm) if err != nil { return meta, err } // Modern xl.meta v2 leaves Erasure.Checksums empty; GetChecksumInfo // returns the canonical streaming default with a zero PartNumber and nil // hash. That intentionally reconstructs to the same verifier setup. meta.Erasure.Bitrot = FastOpenBitrotMeta{ PartNumber: ckSum.PartNumber, Algorithm: algo, Hash: append([]byte(nil), ckSum.Hash...), } } return meta, nil } // fastOpenGETMetaToFileInfo converts OK and delete-marker frames only. Disk-local // not-found, version-not-found, and unsupported statuses must be routed through // FastOpen quorum/error handling before calling this helper. func fastOpenGETMetaToFileInfo(volume, object string, status FastOpenFrameStatus, meta FastOpenGETMeta) (FileInfo, error) { if !status.valid() { return FileInfo{}, errFastOpenFrameBadStatus } if status != FastOpenStatusOK && status != FastOpenStatusDeleteMarker { return FileInfo{}, errFastOpenFrameBadStatus } fi := FileInfo{ Volume: volume, Name: object, VersionID: meta.VersionID, IsLatest: meta.IsLatest, Deleted: status == FastOpenStatusDeleteMarker, TransitionStatus: meta.Transition.Status, TransitionedObjName: meta.Transition.Object, TransitionTier: meta.Transition.Tier, TransitionVersionID: meta.Transition.VersionID, XLV1: meta.Legacy, ModTime: fastOpenUnixNanoTime(meta.ModTimeUnixNano), Size: meta.Size, Metadata: cloneStringMap(meta.Metadata), ReplicationState: getInternalReplicationState(meta.Metadata), NumVersions: meta.NumVersions, SuccessorModTime: fastOpenUnixNanoTime(meta.SuccessorModTimeNanos), Checksum: append([]byte(nil), meta.Checksum...), } if meta.Part.Number != 0 { fi.Parts = []ObjectPartInfo{{ Number: meta.Part.Number, Size: meta.Part.Size, ActualSize: meta.Part.ActualSize, }} } if meta.Erasure.hasData() { fi.Erasure = ErasureInfo{ Algorithm: erasureAlgorithm, DataBlocks: meta.Erasure.DataBlocks, ParityBlocks: meta.Erasure.ParityBlocks, BlockSize: meta.Erasure.BlockSize, Index: meta.Erasure.Index, Distribution: append([]int(nil), meta.Erasure.Distribution...), } } if meta.Erasure.Bitrot.Algorithm != 0 { algo, err := fastOpenBitrotAlgorithm(meta.Erasure.Bitrot.Algorithm) if err != nil { return FileInfo{}, err } fi.Erasure.Checksums = []ChecksumInfo{{ PartNumber: meta.Erasure.Bitrot.PartNumber, Algorithm: algo, Hash: append([]byte(nil), meta.Erasure.Bitrot.Hash...), }} } if !fi.Deleted && meta.Part.Number != 0 && meta.Erasure.Bitrot.Algorithm == 0 { return FileInfo{}, errFastOpenFrameBadBitrot } return fi, nil } func (m FastOpenErasureMeta) hasData() bool { return m.DataBlocks != 0 || m.ParityBlocks != 0 || m.BlockSize != 0 || m.Index != 0 || len(m.Distribution) != 0 } func fastOpenTimeUnixNano(t time.Time) int64 { if t.IsZero() { return 0 } return t.UnixNano() } func fastOpenUnixNanoTime(n int64) time.Time { if n == 0 { return time.Time{} } return time.Unix(0, n).UTC() } func fastOpenBitrotCode(algo BitrotAlgorithm) (uint8, error) { switch algo { case SHA256: return 1, nil case HighwayHash256: return 2, nil case HighwayHash256S: return 3, nil case BLAKE2b512: return 4, nil default: return 0, errFastOpenFrameBadBitrot } } func fastOpenBitrotAlgorithm(code uint8) (BitrotAlgorithm, error) { switch code { case 1: return SHA256, nil case 2: return HighwayHash256, nil case 3: return HighwayHash256S, nil case 4: return BLAKE2b512, nil default: return 0, errFastOpenFrameBadBitrot } } func marshalCoalescedMetadataFrame(f CoalescedMetadataFrame) ([]byte, error) { if !f.Status.valid() { return nil, errFastOpenFrameBadStatus } if !f.BodyMode.valid() { return nil, errFastOpenFrameBadBodyMode } var b []byte b = msgp.AppendMapHeader(b, 4) b = msgp.AppendString(b, "s") b = msgp.AppendUint8(b, uint8(f.Status)) b = msgp.AppendString(b, "m") b = appendFastOpenGETMeta(b, f.Meta) b = msgp.AppendString(b, "bm") b = msgp.AppendUint8(b, uint8(f.BodyMode)) b = msgp.AppendString(b, "bl") b = msgp.AppendInt64(b, f.BodyLen) return b, nil } func unmarshalCoalescedMetadataFrame(b []byte) (CoalescedMetadataFrame, error) { var f CoalescedMetadataFrame n, b, err := msgp.ReadMapHeaderBytes(b) if err != nil { return f, err } for n > 0 { n-- var field []byte field, b, err = msgp.ReadMapKeyZC(b) if err != nil { return f, err } switch string(field) { case "s": var v uint8 v, b, err = msgp.ReadUint8Bytes(b) f.Status = FastOpenFrameStatus(v) case "m": f.Meta, b, err = readFastOpenGETMeta(b) case "bm": var v uint8 v, b, err = msgp.ReadUint8Bytes(b) f.BodyMode = FastOpenBodyMode(v) case "bl": f.BodyLen, b, err = msgp.ReadInt64Bytes(b) default: b, err = msgp.Skip(b) } if err != nil { return f, err } } if len(b) != 0 { return f, errFastOpenFrameBadPayload } if !f.Status.valid() { return f, errFastOpenFrameBadStatus } if !f.BodyMode.valid() { return f, errFastOpenFrameBadBodyMode } return f, nil } func appendFastOpenGETMeta(b []byte, m FastOpenGETMeta) []byte { b = msgp.AppendMapHeader(b, 12) b = msgp.AppendString(b, "vid") b = msgp.AppendString(b, m.VersionID) b = msgp.AppendString(b, "il") b = msgp.AppendBool(b, m.IsLatest) b = msgp.AppendString(b, "v1") b = msgp.AppendBool(b, m.Legacy) b = msgp.AppendString(b, "mt") b = msgp.AppendInt64(b, m.ModTimeUnixNano) b = msgp.AppendString(b, "sz") b = msgp.AppendInt64(b, m.Size) b = msgp.AppendString(b, "meta") b = appendStringMap(b, m.Metadata) b = msgp.AppendString(b, "tr") b = appendFastOpenTransitionMeta(b, m.Transition) b = msgp.AppendString(b, "part") b = appendFastOpenPartMeta(b, m.Part) b = msgp.AppendString(b, "ei") b = appendFastOpenErasureMeta(b, m.Erasure) b = msgp.AppendString(b, "cs") b = appendBytesOrNil(b, m.Checksum) b = msgp.AppendString(b, "nv") b = msgp.AppendInt(b, m.NumVersions) b = msgp.AppendString(b, "smt") b = msgp.AppendInt64(b, m.SuccessorModTimeNanos) return b } func readFastOpenGETMeta(b []byte) (FastOpenGETMeta, []byte, error) { var m FastOpenGETMeta n, b, err := msgp.ReadMapHeaderBytes(b) if err != nil { return m, b, err } for n > 0 { n-- var field []byte field, b, err = msgp.ReadMapKeyZC(b) if err != nil { return m, b, err } switch string(field) { case "vid": m.VersionID, b, err = msgp.ReadStringBytes(b) case "il": m.IsLatest, b, err = msgp.ReadBoolBytes(b) case "v1": m.Legacy, b, err = msgp.ReadBoolBytes(b) case "mt": m.ModTimeUnixNano, b, err = msgp.ReadInt64Bytes(b) case "sz": m.Size, b, err = msgp.ReadInt64Bytes(b) case "meta": m.Metadata, b, err = readStringMap(b) case "tr": m.Transition, b, err = readFastOpenTransitionMeta(b) case "part": m.Part, b, err = readFastOpenPartMeta(b) case "ei": m.Erasure, b, err = readFastOpenErasureMeta(b) case "cs": m.Checksum, b, err = readBytesOrNil(b) case "nv": m.NumVersions, b, err = msgp.ReadIntBytes(b) case "smt": m.SuccessorModTimeNanos, b, err = msgp.ReadInt64Bytes(b) default: b, err = msgp.Skip(b) } if err != nil { return m, b, err } } return m, b, nil } func appendFastOpenTransitionMeta(b []byte, m FastOpenTransitionMeta) []byte { b = msgp.AppendMapHeader(b, 4) b = msgp.AppendString(b, "s") b = msgp.AppendString(b, m.Status) b = msgp.AppendString(b, "o") b = msgp.AppendString(b, m.Object) b = msgp.AppendString(b, "t") b = msgp.AppendString(b, m.Tier) b = msgp.AppendString(b, "v") b = msgp.AppendString(b, m.VersionID) return b } func readFastOpenTransitionMeta(b []byte) (FastOpenTransitionMeta, []byte, error) { var m FastOpenTransitionMeta n, b, err := msgp.ReadMapHeaderBytes(b) if err != nil { return m, b, err } for n > 0 { n-- var field []byte field, b, err = msgp.ReadMapKeyZC(b) if err != nil { return m, b, err } switch string(field) { case "s": m.Status, b, err = msgp.ReadStringBytes(b) case "o": m.Object, b, err = msgp.ReadStringBytes(b) case "t": m.Tier, b, err = msgp.ReadStringBytes(b) case "v": m.VersionID, b, err = msgp.ReadStringBytes(b) default: b, err = msgp.Skip(b) } if err != nil { return m, b, err } } return m, b, nil } func appendFastOpenPartMeta(b []byte, m FastOpenPartMeta) []byte { b = msgp.AppendMapHeader(b, 3) b = msgp.AppendString(b, "n") b = msgp.AppendInt(b, m.Number) b = msgp.AppendString(b, "s") b = msgp.AppendInt64(b, m.Size) b = msgp.AppendString(b, "as") b = msgp.AppendInt64(b, m.ActualSize) return b } func readFastOpenPartMeta(b []byte) (FastOpenPartMeta, []byte, error) { var m FastOpenPartMeta n, b, err := msgp.ReadMapHeaderBytes(b) if err != nil { return m, b, err } for n > 0 { n-- var field []byte field, b, err = msgp.ReadMapKeyZC(b) if err != nil { return m, b, err } switch string(field) { case "n": m.Number, b, err = msgp.ReadIntBytes(b) case "s": m.Size, b, err = msgp.ReadInt64Bytes(b) case "as": m.ActualSize, b, err = msgp.ReadInt64Bytes(b) default: b, err = msgp.Skip(b) } if err != nil { return m, b, err } } return m, b, nil } func appendFastOpenErasureMeta(b []byte, m FastOpenErasureMeta) []byte { b = msgp.AppendMapHeader(b, 6) b = msgp.AppendString(b, "d") b = msgp.AppendInt(b, m.DataBlocks) b = msgp.AppendString(b, "p") b = msgp.AppendInt(b, m.ParityBlocks) b = msgp.AppendString(b, "bs") b = msgp.AppendInt64(b, m.BlockSize) b = msgp.AppendString(b, "i") b = msgp.AppendInt(b, m.Index) b = msgp.AppendString(b, "dist") b = appendIntSlice(b, m.Distribution) b = msgp.AppendString(b, "br") b = appendFastOpenBitrotMeta(b, m.Bitrot) return b } func readFastOpenErasureMeta(b []byte) (FastOpenErasureMeta, []byte, error) { var m FastOpenErasureMeta n, b, err := msgp.ReadMapHeaderBytes(b) if err != nil { return m, b, err } for n > 0 { n-- var field []byte field, b, err = msgp.ReadMapKeyZC(b) if err != nil { return m, b, err } switch string(field) { case "d": m.DataBlocks, b, err = msgp.ReadIntBytes(b) case "p": m.ParityBlocks, b, err = msgp.ReadIntBytes(b) case "bs": m.BlockSize, b, err = msgp.ReadInt64Bytes(b) case "i": m.Index, b, err = msgp.ReadIntBytes(b) case "dist": m.Distribution, b, err = readIntSlice(b) case "br": m.Bitrot, b, err = readFastOpenBitrotMeta(b) default: b, err = msgp.Skip(b) } if err != nil { return m, b, err } } return m, b, nil } func appendFastOpenBitrotMeta(b []byte, m FastOpenBitrotMeta) []byte { b = msgp.AppendMapHeader(b, 3) b = msgp.AppendString(b, "pn") b = msgp.AppendInt(b, m.PartNumber) b = msgp.AppendString(b, "a") b = msgp.AppendUint8(b, m.Algorithm) b = msgp.AppendString(b, "h") b = appendBytesOrNil(b, m.Hash) return b } func readFastOpenBitrotMeta(b []byte) (FastOpenBitrotMeta, []byte, error) { var m FastOpenBitrotMeta n, b, err := msgp.ReadMapHeaderBytes(b) if err != nil { return m, b, err } for n > 0 { n-- var field []byte field, b, err = msgp.ReadMapKeyZC(b) if err != nil { return m, b, err } switch string(field) { case "pn": m.PartNumber, b, err = msgp.ReadIntBytes(b) case "a": m.Algorithm, b, err = msgp.ReadUint8Bytes(b) case "h": m.Hash, b, err = readBytesOrNil(b) default: b, err = msgp.Skip(b) } if err != nil { return m, b, err } } return m, b, nil } func appendStringMap(b []byte, m map[string]string) []byte { if m == nil { return msgp.AppendNil(b) } b = msgp.AppendMapHeader(b, uint32(len(m))) for k, v := range m { b = msgp.AppendString(b, k) b = msgp.AppendString(b, v) } return b } func readStringMap(b []byte) (map[string]string, []byte, error) { if msgp.IsNil(b) { var err error b, err = msgp.ReadNilBytes(b) return nil, b, err } n, b, err := msgp.ReadMapHeaderBytes(b) if err != nil { return nil, b, err } if n > uint32(len(b)/2) { return nil, b, errFastOpenFrameBadPayload } m := make(map[string]string, n) for n > 0 { n-- var k, v string k, b, err = msgp.ReadStringBytes(b) if err != nil { return nil, b, err } v, b, err = msgp.ReadStringBytes(b) if err != nil { return nil, b, err } m[k] = v } return m, b, nil } func appendIntSlice(b []byte, ints []int) []byte { if ints == nil { return msgp.AppendNil(b) } b = msgp.AppendArrayHeader(b, uint32(len(ints))) for _, v := range ints { b = msgp.AppendInt(b, v) } return b } func readIntSlice(b []byte) ([]int, []byte, error) { if msgp.IsNil(b) { var err error b, err = msgp.ReadNilBytes(b) return nil, b, err } n, b, err := msgp.ReadArrayHeaderBytes(b) if err != nil { return nil, b, err } if n > uint32(len(b)) { return nil, b, errFastOpenFrameBadPayload } ints := make([]int, n) for i := range ints { ints[i], b, err = msgp.ReadIntBytes(b) if err != nil { return nil, b, err } } return ints, b, nil } func appendBytesOrNil(b, v []byte) []byte { if v == nil { return msgp.AppendNil(b) } return msgp.AppendBytes(b, v) } func readBytesOrNil(b []byte) ([]byte, []byte, error) { if msgp.IsNil(b) { var err error b, err = msgp.ReadNilBytes(b) return nil, b, err } v, b, err := msgp.ReadBytesZC(b) if err != nil { return nil, b, err } return append([]byte(nil), v...), b, nil } func cloneStringMap(in map[string]string) map[string]string { if in == nil { return nil } out := make(map[string]string, len(in)) for k, v := range in { out[k] = v } return out }