From efcb4f1446ccb408b81771966a5a7032b393683c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torbj=C3=B6rn=20Einarson?= Date: Sat, 6 Jul 2024 13:44:52 +0200 Subject: [PATCH] fix: elng should be full box The elng box was errononously not reading/writing full box headers. This is now changed, but one can still read the old errononous type. --- CHANGELOG.md | 1 + mp4/container.go | 12 ++++++++-- mp4/elng.go | 61 +++++++++++++++++++++++++++++++++++++++--------- mp4/elng_test.go | 46 ++++++++++++++++++++++++++++++++++++ 4 files changed, 107 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0bf3777b..e3c41b5d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed - Overflow in calculating sample decode time +- elng box errononously did not include full box headers ## [0.45.0] - 2024-06-06 diff --git a/mp4/container.go b/mp4/container.go index ce82d5dd..d52e8725 100644 --- a/mp4/container.go +++ b/mp4/container.go @@ -111,7 +111,11 @@ func DecodeContainerChildren(hdr BoxHeader, startPos, endPos uint64, r io.Reader if pos == endPos { return children, nil } else if pos > endPos { - return nil, fmt.Errorf("non-matching children box sizes") + msg := "" + for _, c := range children { + msg += fmt.Sprintf("%s:%d ", c.Type(), c.Size()) + } + return nil, fmt.Errorf("non-matching children box sizes, parentSize=%d, %s", endPos-startPos, msg) } } } @@ -123,7 +127,11 @@ func DecodeContainerChildrenSR(hdr BoxHeader, startPos, endPos uint64, sr bits.S initPos := sr.GetPos() for { if pos > endPos { - return nil, fmt.Errorf("non matching children box sizes") + msg := "" + for _, c := range children { + msg += fmt.Sprintf("%s:%d ", c.Type(), c.Size()) + } + return nil, fmt.Errorf("non-matching children box sizes, parentSize=%d, %s", endPos-startPos, msg) } if pos == endPos { break diff --git a/mp4/elng.go b/mp4/elng.go index 5663dcda..a2fcb8c2 100644 --- a/mp4/elng.go +++ b/mp4/elng.go @@ -1,19 +1,36 @@ package mp4 import ( + "fmt" "io" "github.com/Eyevinn/mp4ff/bits" ) // ElngBox - Extended Language Box +// Defined in ISO/IEC 14496-12 Section 8.4.6 +// It should be a full box, but was erronously implemented +// as a normal box. For backwards compatibility, the +// erronous box without full header can still be decoded. +// The method MissingFullBoxBytes() returns true if that is the case. type ElngBox struct { - Language string + missingFullBox bool + Version byte + Flags uint32 + Language string +} + +// MissingFullBoxBytes indicates that the box is errornously not including the 4 full box header bytes +func (b *ElngBox) MissingFullBoxBytes() bool { + return b.missingFullBox } // CreateElng - Create an Extended Language Box func CreateElng(language string) *ElngBox { - return &ElngBox{Language: language} + return &ElngBox{ + Version: 0, + Flags: 0, + Language: language} } // DecodeElng - box-specific decode @@ -22,18 +39,27 @@ func DecodeElng(hdr BoxHeader, startPos uint64, r io.Reader) (Box, error) { if err != nil { return nil, err } - b := &ElngBox{ - Language: string(data[:len(data)-1]), - } - return b, nil + sr := bits.NewFixedSliceReader(data) + return DecodeElngSR(hdr, startPos, sr) } // DecodeElngSR - box-specific decode func DecodeElngSR(hdr BoxHeader, startPos uint64, sr bits.SliceReader) (Box, error) { - b := &ElngBox{ - Language: string(sr.ReadZeroTerminatedString(hdr.payloadLen())), + b := ElngBox{} + plLen := hdr.payloadLen() + if plLen < 7 { // Less than 4 byte flag and version + 2 letters + 0 termination + b.missingFullBox = true + b.Language = string(sr.ReadZeroTerminatedString(plLen)) + return &b, nil + } + versionAndFlags := sr.ReadUint32() + if versionAndFlags != 0 { + return nil, fmt.Errorf("version and flags are not zero") } - return b, sr.AccError() + b.Version = byte(versionAndFlags >> 24) + b.Flags = versionAndFlags & 0xffffff + b.Language = string(sr.ReadZeroTerminatedString(plLen - 4)) + return &b, sr.AccError() } // Type - box type @@ -43,7 +69,16 @@ func (b *ElngBox) Type() string { // Size - calculated size of box func (b *ElngBox) Size() uint64 { - return uint64(boxHeaderSize + len(b.Language) + 1) + size := uint64(boxHeaderSize + 4 + len(b.Language) + 1) + if b.missingFullBox { + size -= 4 + } + return size +} + +// FixMissingFullBoxBytes adds missing bytes version and flags bytes. +func (b *ElngBox) FixMissingFullBoxBytes() { + b.missingFullBox = false } // Encode - write box to w @@ -63,13 +98,17 @@ func (b *ElngBox) EncodeSW(sw bits.SliceWriter) error { if err != nil { return err } + if !b.missingFullBox { + versionAndFlags := uint32(b.Version)<<24 | b.Flags + sw.WriteUint32(versionAndFlags) + } sw.WriteString(b.Language, true) return sw.AccError() } // Info - write box-specific information func (b *ElngBox) Info(w io.Writer, specificBoxLevels, indent, indentStep string) error { - bd := newInfoDumper(w, indent, b, -1, 0) + bd := newInfoDumper(w, indent, b, int(b.Version), b.Flags) bd.write(" - language: %s", b.Language) return bd.err } diff --git a/mp4/elng_test.go b/mp4/elng_test.go index 7b588169..e2a679c1 100644 --- a/mp4/elng_test.go +++ b/mp4/elng_test.go @@ -1,6 +1,7 @@ package mp4 import ( + "bytes" "testing" ) @@ -9,3 +10,48 @@ func TestDecodeElng(t *testing.T) { elng := &ElngBox{Language: "en-US"} boxDiffAfterEncodeAndDecode(t, elng) } + +// TestElngWithoutFullBox tests erronous case where full box headers are not present. +func TestElngWithoutFullBox(t *testing.T) { + data := []byte("\x00\x00\x00\x0belngdk\x00") + bufIn := bytes.NewBuffer(data) + box, err := DecodeBox(0, bufIn) + if err != nil { + t.Errorf("could not decode elng") + } + elng := box.(*ElngBox) + if !elng.MissingFullBoxBytes() { + t.Errorf("missing full box not set") + } + bufOut := bytes.Buffer{} + err = elng.Encode(&bufOut) + if err != nil { + t.Errorf("error encoding elng") + } + if !bytes.Equal(bufOut.Bytes(), data) { + t.Errorf("encoded elng differs from input") + } +} + +func TestFixElngMissingFullBoxBytes(t *testing.T) { + dataIn := []byte("\x00\x00\x00\x0belngdk\x00") + dataOut := []byte("\x00\x00\x00\x0felng\x00\x00\x00\x00dk\x00") + bufIn := bytes.NewBuffer(dataIn) + box, err := DecodeBox(0, bufIn) + if err != nil { + t.Errorf("could not decode elng") + } + elng := box.(*ElngBox) + if !elng.MissingFullBoxBytes() { + t.Errorf("missing full box not set") + } + outBuf := bytes.Buffer{} + elng.FixMissingFullBoxBytes() + err = elng.Encode(&outBuf) + if err != nil { + t.Errorf("error encoding elng") + } + if !bytes.Equal(outBuf.Bytes(), dataOut) { + t.Errorf("encoded elng differs from input") + } +}