Repository: Eyevinn/mp4ff
Pull request #408 (2025-01-25 14:16:56)

Basic VP9 support
Support for parsing and writing vp08 and vp09 sample entry boxes and the corresponding vppC configuration box.

Based on https://www.webmproject.org/vp9/mp4/

Solves #406.


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 39bb48d..6e79f9d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -17,6 +17,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 
 - mp4.SetUUID() can take base64 string as well as hex-encoded.
 - Support for weird dac3 box with initial 4 zero bytes (Issue #395)
+- Lots of fuzzying tests and changes to avoid panic on bad input data
+- Support for VP8 and VP9 video codecs (vp08 and vp09 boxes)
+- Support for VP Codec Configuration Box (vpcC)
 
 ### Fixed
 
diff --git a/mp4/box.go b/mp4/box.go
index 3535b9a..182f4ee 100644
--- a/mp4/box.go
+++ b/mp4/box.go
@@ -19,20 +19,24 @@ var decoders map[string]BoxDecoder
 
 func init() {
 	decoders = map[string]BoxDecoder{
+		"\xa9ART": DecodeGenericContainerBox,
+		"\xa9nam": DecodeGenericContainerBox,
+		"\xa9too": DecodeGenericContainerBox,
+		"\xa9cpy": DecodeGenericContainerBox,
 		"ac-3":    DecodeAudioSampleEntry,
 		"alou":    DecodeAlou,
 		"av01":    DecodeVisualSampleEntry,
+		"av1C":    DecodeAv1C,
 		"avc1":    DecodeVisualSampleEntry,
 		"avc3":    DecodeVisualSampleEntry,
 		"avcC":    DecodeAvcC,
-		"av1C":    DecodeAv1C,
 		"btrt":    DecodeBtrt,
 		"cdat":    DecodeCdat,
 		"cdsc":    DecodeTrefType,
 		"clap":    DecodeClap,
-		"cslg":    DecodeCslg,
 		"co64":    DecodeCo64,
 		"colr":    DecodeColr,
+		"cslg":    DecodeCslg,
 		"ctim":    DecodeCtim,
 		"ctts":    DecodeCtts,
 		"dac3":    DecodeDac3,
@@ -43,15 +47,15 @@ func init() {
 		"dpnd":    DecodeTrefType,
 		"dref":    DecodeDref,
 		"ec-3":    DecodeAudioSampleEntry,
-		"elng":    DecodeElng,
-		"esds":    DecodeEsds,
 		"edts":    DecodeEdts,
+		"elng":    DecodeElng,
 		"elst":    DecodeElst,
 		"emeb":    DecodeEmeb,
 		"emib":    DecodeEmib,
+		"emsg":    DecodeEmsg,
 		"enca":    DecodeAudioSampleEntry,
 		"encv":    DecodeVisualSampleEntry,
-		"emsg":    DecodeEmsg,
+		"esds":    DecodeEsds,
 		"evte":    DecodeEvte,
 		"font":    DecodeTrefType,
 		"free":    DecodeFree,
@@ -61,8 +65,8 @@ func init() {
 		"hev1":    DecodeVisualSampleEntry,
 		"hind":    DecodeTrefType,
 		"hint":    DecodeTrefType,
-		"hvcC":    DecodeHvcC,
 		"hvc1":    DecodeVisualSampleEntry,
+		"hvcC":    DecodeHvcC,
 		"iden":    DecodeIden,
 		"ilst":    DecodeIlst,
 		"iods":    DecodeUnknown,
@@ -82,10 +86,10 @@ func init() {
 		"minf":    DecodeMinf,
 		"moof":    DecodeMoof,
 		"moov":    DecodeMoov,
+		"mp4a":    DecodeAudioSampleEntry,
 		"mpod":    DecodeTrefType,
 		"mvex":    DecodeMvex,
 		"mvhd":    DecodeMvhd,
-		"mp4a":    DecodeAudioSampleEntry,
 		"nmhd":    DecodeNmhd,
 		"pasp":    DecodePasp,
 		"payl":    DecodePayl,
@@ -105,9 +109,9 @@ func init() {
 		"skip":    DecodeFree,
 		"smhd":    DecodeSmhd,
 		"ssix":    DecodeSsix,
-		"sthd":    DecodeSthd,
 		"stbl":    DecodeStbl,
 		"stco":    DecodeStco,
+		"sthd":    DecodeSthd,
 		"stpp":    DecodeStpp,
 		"stsc":    DecodeStsc,
 		"stsd":    DecodeStsd,
@@ -137,6 +141,9 @@ func init() {
 		"vdep":    DecodeTrefType,
 		"vlab":    DecodeVlab,
 		"vmhd":    DecodeVmhd,
+		"vp08":    DecodeVisualSampleEntry,
+		"vp09":    DecodeVisualSampleEntry,
+		"vpcC":    DecodeVppC,
 		"vplx":    DecodeTrefType,
 		"vsid":    DecodeVsid,
 		"vtta":    DecodeVtta,
@@ -144,10 +151,6 @@ func init() {
 		"vttC":    DecodeVttC,
 		"vtte":    DecodeVtte,
 		"wvtt":    DecodeWvtt,
-		"\xa9cpy": DecodeGenericContainerBox,
-		"\xa9nam": DecodeGenericContainerBox,
-		"\xa9too": DecodeGenericContainerBox,
-		"\xa9ART": DecodeGenericContainerBox,
 	}
 }
 
diff --git a/mp4/boxsr.go b/mp4/boxsr.go
index b779adb..deb69bb 100644
--- a/mp4/boxsr.go
+++ b/mp4/boxsr.go
@@ -10,20 +10,24 @@ var decodersSR map[string]BoxDecoderSR
 
 func init() {
 	decodersSR = map[string]BoxDecoderSR{
+		"\xa9ART": DecodeGenericContainerBoxSR,
+		"\xa9cpy": DecodeGenericContainerBoxSR,
+		"\xa9nam": DecodeGenericContainerBoxSR,
+		"\xa9too": DecodeGenericContainerBoxSR,
 		"ac-3":    DecodeAudioSampleEntrySR,
+		"alou":    DecodeAlouBoxSR,
 		"av01":    DecodeVisualSampleEntrySR,
+		"av1C":    DecodeAv1CSR,
 		"avc1":    DecodeVisualSampleEntrySR,
 		"avc3":    DecodeVisualSampleEntrySR,
-		"alou":    DecodeAlouBoxSR,
 		"avcC":    DecodeAvcCSR,
-		"av1C":    DecodeAv1CSR,
 		"btrt":    DecodeBtrtSR,
 		"cdat":    DecodeCdatSR,
 		"cdsc":    DecodeTrefTypeSR,
 		"clap":    DecodeClapSR,
-		"cslg":    DecodeCslgSR,
 		"co64":    DecodeCo64SR,
 		"colr":    DecodeColrSR,
+		"cslg":    DecodeCslgSR,
 		"ctim":    DecodeCtimSR,
 		"ctts":    DecodeCttsSR,
 		"dac3":    DecodeDac3SR,
@@ -34,15 +38,15 @@ func init() {
 		"dpnd":    DecodeTrefTypeSR,
 		"dref":    DecodeDrefSR,
 		"ec-3":    DecodeAudioSampleEntrySR,
+		"edts":    DecodeEdtsSR,
 		"elng":    DecodeElngSR,
+		"elst":    DecodeElstSR,
 		"emeb":    DecodeEmebSR,
 		"emib":    DecodeEmibSR,
-		"esds":    DecodeEsdsSR,
-		"edts":    DecodeEdtsSR,
-		"elst":    DecodeElstSR,
+		"emsg":    DecodeEmsgSR,
 		"enca":    DecodeAudioSampleEntrySR,
 		"encv":    DecodeVisualSampleEntrySR,
-		"emsg":    DecodeEmsgSR,
+		"esds":    DecodeEsdsSR,
 		"evte":    DecodeEvteSR,
 		"font":    DecodeTrefTypeSR,
 		"free":    DecodeFreeSR,
@@ -52,8 +56,8 @@ func init() {
 		"hev1":    DecodeVisualSampleEntrySR,
 		"hind":    DecodeTrefTypeSR,
 		"hint":    DecodeTrefTypeSR,
-		"hvcC":    DecodeHvcCSR,
 		"hvc1":    DecodeVisualSampleEntrySR,
+		"hvcC":    DecodeHvcCSR,
 		"iden":    DecodeIdenSR,
 		"ilst":    DecodeIlstSR,
 		"iods":    DecodeUnknownSR,
@@ -73,10 +77,10 @@ func init() {
 		"minf":    DecodeMinfSR,
 		"moof":    DecodeMoofSR,
 		"moov":    DecodeMoovSR,
+		"mp4a":    DecodeAudioSampleEntrySR,
 		"mpod":    DecodeTrefTypeSR,
 		"mvex":    DecodeMvexSR,
 		"mvhd":    DecodeMvhdSR,
-		"mp4a":    DecodeAudioSampleEntrySR,
 		"nmhd":    DecodeNmhdSR,
 		"pasp":    DecodePaspSR,
 		"payl":    DecodePaylSR,
@@ -96,9 +100,9 @@ func init() {
 		"skip":    DecodeFreeSR,
 		"smhd":    DecodeSmhdSR,
 		"ssix":    DecodeSsixSR,
-		"sthd":    DecodeSthdSR,
 		"stbl":    DecodeStblSR,
 		"stco":    DecodeStcoSR,
+		"sthd":    DecodeSthdSR,
 		"stpp":    DecodeStppSR,
 		"stsc":    DecodeStscSR,
 		"stsd":    DecodeStsdSR,
@@ -128,6 +132,9 @@ func init() {
 		"vdep":    DecodeTrefTypeSR,
 		"vlab":    DecodeVlabSR,
 		"vmhd":    DecodeVmhdSR,
+		"vp08":    DecodeVisualSampleEntrySR,
+		"vp09":    DecodeVisualSampleEntrySR,
+		"vpcC":    DecodeVppCSR,
 		"vplx":    DecodeTrefTypeSR,
 		"vsid":    DecodeVsidSR,
 		"vtta":    DecodeVttaSR,
@@ -135,10 +142,6 @@ func init() {
 		"vttC":    DecodeVttCSR,
 		"vtte":    DecodeVtteSR,
 		"wvtt":    DecodeWvttSR,
-		"\xa9cpy": DecodeGenericContainerBoxSR,
-		"\xa9nam": DecodeGenericContainerBoxSR,
-		"\xa9too": DecodeGenericContainerBoxSR,
-		"\xa9ART": DecodeGenericContainerBoxSR,
 	}
 }
 
diff --git a/mp4/stsd.go b/mp4/stsd.go
index 573991a..7324a8b 100644
--- a/mp4/stsd.go
+++ b/mp4/stsd.go
@@ -24,6 +24,8 @@ type StsdBox struct {
 	Av01 *VisualSampleEntryBox
 	// Encv is a pointer to a box with name encv
 	Encv *VisualSampleEntryBox
+	// VpXX is a pointer to a box with name vp08 or vp09 (VP8 or VP9 video)
+	VpXX *VisualSampleEntryBox
 	// Mp4a is a pointer to a box with name mp4a
 	Mp4a *AudioSampleEntryBox
 	// AC3 is a pointer to a box with name ac-3
@@ -57,6 +59,8 @@ func (s *StsdBox) AddChild(box Box) {
 		s.Encv = box.(*VisualSampleEntryBox)
 	case "av01":
 		s.Av01 = box.(*VisualSampleEntryBox)
+	case "vp08", "vp09":
+		s.VpXX = box.(*VisualSampleEntryBox)
 	case "mp4a":
 		s.Mp4a = box.(*AudioSampleEntryBox)
 	case "ac-3":
diff --git a/mp4/visualsampleentry.go b/mp4/visualsampleentry.go
index a562bd7..5e1ed9d 100644
--- a/mp4/visualsampleentry.go
+++ b/mp4/visualsampleentry.go
@@ -21,6 +21,7 @@ type VisualSampleEntryBox struct {
 	AvcC               *AvcCBox
 	HvcC               *HvcCBox
 	Av1C               *Av1CBox
+	VppC               *VppCBox
 	Btrt               *BtrtBox
 	Clap               *ClapBox
 	Pasp               *PaspBox
@@ -63,6 +64,8 @@ func (b *VisualSampleEntryBox) AddChild(child Box) {
 		b.HvcC = box
 	case *Av1CBox:
 		b.Av1C = box
+	case *VppCBox:
+		b.VppC = box
 	case *BtrtBox:
 		b.Btrt = box
 	case *ClapBox:
@@ -72,7 +75,6 @@ func (b *VisualSampleEntryBox) AddChild(child Box) {
 	case *SinfBox:
 		b.Sinf = box
 	}
-
 	b.Children = append(b.Children, child)
 }
 
@@ -112,7 +114,7 @@ func DecodeVisualSampleEntrySR(hdr BoxHeader, startPos uint64, sr bits.SliceRead
 	}
 	b.CompressorName = sr.ReadFixedLengthString(int(compressorNameLength))
 	sr.SkipBytes(int(31 - compressorNameLength))
-	sr.ReadUint16() // depth == 0x0018
+	sr.SkipBytes(2) // Skip depth
 	sr.ReadUint16() // pre_defined == -1
 
 	// Now there may be clap and pasp boxes
@@ -179,7 +181,7 @@ func (b *VisualSampleEntryBox) Encode(w io.Writer) error {
 	sw.WriteUint8(compressorNameLength)
 	sw.WriteString(b.CompressorName, false)
 	sw.WriteZeroBytes(int(31 - compressorNameLength))
-	sw.WriteUint16(0x0018) // depth == 0x0018
+	sw.WriteUint16(0x0018) // depth
 	sw.WriteUint16(0xffff) // pre_defined == -1  //86 bytes
 
 	_, err = w.Write(buf[:sw.Offset()]) // Only write  written bytes
@@ -218,7 +220,7 @@ func (b *VisualSampleEntryBox) EncodeSW(sw bits.SliceWriter) error {
 	sw.WriteUint8(compressorNameLength)
 	sw.WriteString(b.CompressorName, false)
 	sw.WriteZeroBytes(int(31 - compressorNameLength))
-	sw.WriteUint16(0x0018) // depth == 0x0018
+	sw.WriteUint16(0x0018) // depth
 	sw.WriteUint16(0xffff) // pre_defined == -1  //86 bytes
 
 	// Next output child boxes in order
diff --git a/mp4/vppc.go b/mp4/vppc.go
new file mode 100644
index 0000000..f1782ea
--- /dev/null
+++ b/mp4/vppc.go
@@ -0,0 +1,141 @@
+package mp4
+
+import (
+	"fmt"
+	"io"
+
+	"github.com/Eyevinn/mp4ff/bits"
+)
+
+// VppCBox - VP Codec Configuration Box (vpcC)
+// The VPCodecConfigurationBox contains decoder configuration information
+// formatted according to the VP codec configuration syntax.
+//
+// [WebM VP Codec Configuration]: https://www.webmproject.org/vp9/mp4/
+type VppCBox struct {
+	Version                 byte
+	Flags                   uint32
+	Profile                 byte
+	Level                   byte
+	BitDepth                byte
+	ChromaSubsampling       byte
+	VideoFullRangeFlag      byte
+	ColourPrimaries         byte
+	TransferCharacteristics byte
+	MatrixCoefficients      byte
+	CodecInitData           []byte
+}
+
+// DecodeVppC - box-specific decode
+func DecodeVppC(hdr BoxHeader, startPos uint64, r io.Reader) (Box, error) {
+	if hdr.Size < 20 {
+		return nil, fmt.Errorf("box too short < 20 bytes")
+	}
+	data, err := readBoxBody(r, hdr)
+	if err != nil {
+		return nil, err
+	}
+	sr := bits.NewFixedSliceReader(data)
+	return DecodeVppCSR(hdr, startPos, sr)
+}
+
+// DecodeVppCSR - box-specific decode
+func DecodeVppCSR(hdr BoxHeader, startPos uint64, sr bits.SliceReader) (Box, error) {
+	if hdr.Size < 20 {
+		return nil, fmt.Errorf("box too short < 20 bytes")
+	}
+	b := VppCBox{}
+	versionAndFlags := sr.ReadUint32()
+	b.Version = byte(versionAndFlags >> 24)
+	b.Flags = versionAndFlags & flagsMask
+
+	if b.Version != 1 {
+		return nil, fmt.Errorf("version %d not supported", b.Version)
+	}
+
+	b.Profile = sr.ReadUint8()
+	b.Level = sr.ReadUint8()
+
+	// Read bit depth and chroma subsampling packed in one byte
+	packedByte := sr.ReadUint8()
+	b.BitDepth = (packedByte >> 4) & 0x0F          // top 4 bits
+	b.ChromaSubsampling = (packedByte >> 1) & 0x07 // next 3 bits
+	b.VideoFullRangeFlag = packedByte & 0x01       // last bit
+
+	b.ColourPrimaries = sr.ReadUint8()
+	b.TransferCharacteristics = sr.ReadUint8()
+	b.MatrixCoefficients = sr.ReadUint8()
+
+	codecInitSize := sr.ReadUint16()
+	if hdr.Size != b.expectedSize(codecInitSize) {
+		return nil, fmt.Errorf("incorrect box size")
+	}
+	b.CodecInitData = sr.ReadBytes(int(codecInitSize))
+
+	return &b, sr.AccError()
+}
+
+// Type - box type
+func (b *VppCBox) Type() string {
+	return "vpcC"
+}
+
+// Size - calculated size of box
+func (b *VppCBox) Size() uint64 {
+	return uint64(boxHeaderSize + 4 + 6 + 2 + len(b.CodecInitData))
+}
+
+func (b *VppCBox) expectedSize(codecInitSize uint16) uint64 {
+	return uint64(boxHeaderSize+4+6+2) + uint64(codecInitSize)
+}
+
+// Encode - write box to w
+func (b *VppCBox) Encode(w io.Writer) error {
+	sw := bits.NewFixedSliceWriter(int(b.Size()))
+	err := b.EncodeSW(sw)
+	if err != nil {
+		return err
+	}
+	_, err = w.Write(sw.Bytes())
+	return err
+}
+
+// EncodeSW - box-specific encode to slicewriter
+func (b *VppCBox) EncodeSW(sw bits.SliceWriter) error {
+	err := EncodeHeaderSW(b, sw)
+	if err != nil {
+		return err
+	}
+	versionAndFlags := (uint32(b.Version) << 24) + b.Flags
+	sw.WriteUint32(versionAndFlags)
+	sw.WriteUint8(b.Profile)
+	sw.WriteUint8(b.Level)
+
+	// Pack bit depth, chroma subsampling and video full range flag into one byte
+	packedByte := (b.BitDepth << 4) | (b.ChromaSubsampling << 1) | b.VideoFullRangeFlag
+	sw.WriteUint8(packedByte)
+
+	sw.WriteUint8(b.ColourPrimaries)
+	sw.WriteUint8(b.TransferCharacteristics)
+	sw.WriteUint8(b.MatrixCoefficients)
+
+	sw.WriteUint16(uint16(len(b.CodecInitData)))
+	sw.WriteBytes(b.CodecInitData)
+
+	return sw.AccError()
+}
+
+// Info - write box info to w
+func (b *VppCBox) Info(w io.Writer, specificBoxLevels, indent, indentStep string) error {
+	bd := newInfoDumper(w, indent, b, -1, 0)
+	bd.write(" - profile: %d", b.Profile)
+	bd.write(" - level: %d", b.Level)
+	bd.write(" - bitDepth: %d", b.BitDepth)
+	bd.write(" - chromaSubsampling: %d", b.ChromaSubsampling)
+	bd.write(" - videoFullRangeFlag: %d", b.VideoFullRangeFlag)
+	bd.write(" - colourPrimaries: %d", b.ColourPrimaries)
+	bd.write(" - transferCharacteristics: %d", b.TransferCharacteristics)
+	bd.write(" - matrixCoefficients: %d", b.MatrixCoefficients)
+	bd.write(" - codecInitSize: %d", len(b.CodecInitData))
+	return bd.err
+}


Test patch:
diff --git a/mp4/helpers_test.go b/mp4/helpers_test.go
index d2b8329..14a2881 100644
--- a/mp4/helpers_test.go
+++ b/mp4/helpers_test.go
@@ -2,6 +2,7 @@ package mp4
 
 import (
 	"bytes"
+	"encoding/binary"
 	"flag"
 	"os"
 	"testing"
@@ -128,6 +129,26 @@ func assertError(t *testing.T, err error, msg string) {
 	}
 }
 
+func changeBoxSizeAndAssertError(t *testing.T, data []byte, pos uint64, newSize uint32, errMsg string) {
+	t.Helper()
+	raw := make([]byte, len(data))
+	copy(raw, data)
+	binary.BigEndian.PutUint32(raw[pos:pos+4], newSize)
+	assertBoxDecodeError(t, raw, pos, errMsg)
+}
+
+func assertBoxDecodeError(t *testing.T, data []byte, pos uint64, errMsg string) {
+	t.Helper()
+	_, err := DecodeBox(pos, bytes.NewBuffer(data))
+	if err == nil || err.Error() != errMsg {
+		t.Errorf("DecodeBox: Expected error msg: %q", errMsg)
+	}
+	_, err = DecodeBoxSR(pos, bits.NewFixedSliceReader(data))
+	if err == nil || err.Error() != errMsg {
+		t.Errorf("DecodeBoxSR: Expected error msg: %q", errMsg)
+	}
+}
+
 // writeGolden - write golden file that to be used for later tests
 func writeGolden(t *testing.T, goldenAssetPath string, data []byte) error {
 	t.Helper()
diff --git a/mp4/stsd_test.go b/mp4/stsd_test.go
index fc408a8..3cbd800 100644
--- a/mp4/stsd_test.go
+++ b/mp4/stsd_test.go
@@ -2,6 +2,7 @@ package mp4
 
 import (
 	"bytes"
+	"encoding/hex"
 	"testing"
 
 	"github.com/Eyevinn/mp4ff/aac"
@@ -72,3 +73,20 @@ func TestStsdEncodeDecode(t *testing.T) {
 		t.Errorf("Expected nil, got %v", btrt)
 	}
 }
+
+func TestStsdVP9(t *testing.T) {
+	hexData := "" +
+		"000000a87374736400000000000000010000009876703039000000000000" +
+		"000100000000000000000000000000000000050002d00048000000480000" +
+		"000000000001184c61766336312e31392e313030206c69627670782d7670" +
+		"39000000000000000018ffff000000147670634301000000001f80020202" +
+		"00000000000a6669656c0100000000107061737000000001000000010000" +
+		"001462747274000000000010152200101522"
+
+	binData, err := hex.DecodeString(hexData)
+	if err != nil {
+		t.Error(err)
+	}
+
+	cmpAfterDecodeEncodeBox(t, binData)
+}
diff --git a/mp4/vppc_test.go b/mp4/vppc_test.go
new file mode 100644
index 0000000..3bf609b
--- /dev/null
+++ b/mp4/vppc_test.go
@@ -0,0 +1,52 @@
+package mp4
+
+import (
+	"bytes"
+	"testing"
+)
+
+func TestVppC(t *testing.T) {
+	// Create a sample VppC box
+	vppc := &VppCBox{
+		Version:                 1,
+		Flags:                   0,
+		Profile:                 1,
+		Level:                   10,
+		BitDepth:                10,
+		ChromaSubsampling:       1,
+		VideoFullRangeFlag:      1,
+		ColourPrimaries:         1,
+		TransferCharacteristics: 1,
+		MatrixCoefficients:      1,
+		CodecInitData:           []byte{0x01, 0x02, 0x03, 0x04},
+	}
+
+	boxDiffAfterEncodeAndDecode(t, vppc)
+
+	buf := bytes.Buffer{}
+	err := vppc.Encode(&buf)
+	if err != nil {
+		t.Error(err)
+	}
+
+	data := buf.Bytes()
+
+	// Check that there is an error for Version != 0 (after 8-byte header)
+	raw := make([]byte, len(data))
+	copy(raw, data)
+	raw[8] = 0
+	_, err = DecodeBox(0, bytes.NewBuffer(raw))
+	errMsg := "decode vpcC pos 0: version 0 not supported"
+	if err == nil || err.Error() != errMsg {
+		t.Errorf("Expected error msg: %q", errMsg)
+	}
+
+	// Check that there is an error if the CodecInitSize is not compatible withthe box size.
+	copy(raw, data)
+	raw[19] = 2
+	assertBoxDecodeError(t, raw, 0, "decode vpcC pos 0: incorrect box size")
+
+	// Check that there is an error if the box is too short
+	tooShortSize := uint32(9)
+	changeBoxSizeAndAssertError(t, data, 0, tooShortSize, "decode vpcC pos 0: box too short < 20 bytes")
+}