From 30ca22ba58051d11c04326326ff75eeebfda6662 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torbjo=CC=88rn=20Einarsson?= Date: Wed, 20 Nov 2024 16:28:54 +0100 Subject: [PATCH] wip: tools take hex or base64 key --- CHANGELOG.md | 10 +++- cmd/mp4ff-decrypt/doc.go | 19 +++--- cmd/mp4ff-decrypt/main.go | 27 ++++----- cmd/mp4ff-decrypt/main_test.go | 40 ++++++++----- cmd/mp4ff-encrypt/doc.go | 36 +++++------ cmd/mp4ff-encrypt/main.go | 31 +++++----- mp4/crypto_test.go | 2 +- mp4/pssh.go | 26 -------- mp4/pssh_test.go | 6 +- mp4/uuid.go | 105 +++++++++++++++++++++++---------- mp4/uuid_test.go | 74 ++++++++++++++++++++++- 11 files changed, 240 insertions(+), 136 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4913a023..2b5017d6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] -- Nothing yet +### Changed + +- mp4.NewUUIDFromHex() changed to more general mp4.NewUUIDFromString() +- cmd/mp4ff-decrypt -key option instead of -k. Takes hex or base64 value +- cmd/mp4ff-encrypt -key and -kid options now take hex or bae64 values + +### Added + +- mp4.SetUUID() can take base64 string as well as hex-encoded. ## [0.47.0] - 2024-11-12 diff --git a/cmd/mp4ff-decrypt/doc.go b/cmd/mp4ff-decrypt/doc.go index 91a1b601..3eb5035a 100644 --- a/cmd/mp4ff-decrypt/doc.go +++ b/cmd/mp4ff-decrypt/doc.go @@ -2,17 +2,16 @@ mp4ff-decrypt decrypts a fragmented mp4 file encrypted with Common Encryption scheme cenc or cbcs. For a media segment, it needs an init segment with encryption information. - Usage of mp4ff-decrypt: +Usage of mp4ff-decrypt: +mp4ff-decrypt [options] infile outfile - mp4ff-decrypt [options] infile outfile +options: - options: - - -init string - Path to init file with encryption info (scheme, kid, pssh) - -k string - Required: key (hex) - -version - Get mp4ff version + -init string + Path to init file with encryption info (scheme, kid, pssh) + -key string + Required: key (32 hex or 24 base64 chars) + -version + Get mp4ff version */ package main diff --git a/cmd/mp4ff-decrypt/main.go b/cmd/mp4ff-decrypt/main.go index 49af9d8f..3c0ac901 100644 --- a/cmd/mp4ff-decrypt/main.go +++ b/cmd/mp4ff-decrypt/main.go @@ -1,7 +1,6 @@ package main import ( - "encoding/hex" "errors" "flag" "fmt" @@ -24,7 +23,7 @@ Usage of %s: type options struct { initFilePath string - hexKey string + keyStr string version bool } @@ -37,7 +36,7 @@ func parseOptions(fs *flag.FlagSet, args []string) (*options, error) { opts := options{} fs.StringVar(&opts.initFilePath, "init", "", "Path to init file with encryption info (scheme, kid, pssh)") - fs.StringVar(&opts.hexKey, "k", "", "Required: key (hex)") + fs.StringVar(&opts.keyStr, "key", "", "Required: key (32 hex or 24 base64 chars)") fs.BoolVar(&opts.version, "version", false, "Get mp4ff version") err := fs.Parse(args[1:]) return &opts, err @@ -74,8 +73,13 @@ func run(args []string) error { var inFilePath = fs.Arg(0) var outFilePath = fs.Arg(1) - if opts.hexKey == "" { - return fmt.Errorf("no hex key specified") + if opts.keyStr == "" { + return fmt.Errorf("no key specified") + } + + key, err := mp4.UnpackKey(opts.keyStr) + if err != nil { + return fmt.Errorf("unpacking key: %w", err) } ifh, err := os.Open(inFilePath) @@ -96,22 +100,15 @@ func run(args []string) error { } defer inith.Close() } - err = decryptFile(ifh, inith, ofh, opts.hexKey) + + err = decryptFile(ifh, inith, ofh, key) if err != nil { return fmt.Errorf("decryptFile: %w", err) } return nil } -func decryptFile(r, initR io.Reader, w io.Writer, hexKey string) error { - - if len(hexKey) != 32 { - return fmt.Errorf("hex key must have length 32 chars") - } - key, err := hex.DecodeString(hexKey) - if err != nil { - return err - } +func decryptFile(r, initR io.Reader, w io.Writer, key []byte) error { inMp4, err := mp4.DecodeFile(r) if err != nil { return err diff --git a/cmd/mp4ff-decrypt/main_test.go b/cmd/mp4ff-decrypt/main_test.go index ce18955f..90b8ef72 100644 --- a/cmd/mp4ff-decrypt/main_test.go +++ b/cmd/mp4ff-decrypt/main_test.go @@ -6,6 +6,8 @@ import ( "os" "path" "testing" + + "github.com/Eyevinn/mp4ff/mp4" ) func TestNonRunningOptionCases(t *testing.T) { @@ -24,12 +26,12 @@ func TestNonRunningOptionCases(t *testing.T) { {desc: "unknown args", args: []string{"mp4ff-decrypt", "-x"}, err: true}, {desc: "no outfile", args: []string{"mp4ff-decrypt", "infile.mp4"}, err: true}, {desc: "no key", args: []string{"mp4ff-decrypt", "infile.mp4", outFile}, err: true}, - {desc: "non-existing infile", args: []string{"mp4ff-decrypt", "-k", key, "infile.mp4", outFile}, err: true}, - {desc: "non-existing initfile", args: []string{"mp4ff-decrypt", "-init", "init.mp4", "-k", key, infile, outFile}, err: true}, - {desc: "bad infile", args: []string{"mp4ff-decrypt", "-k", key, "main.go", outFile}, err: true}, - {desc: "short key", args: []string{"mp4ff-decrypt", "-k", "ab", infile, outFile}, err: true}, - {desc: "bad key", args: []string{"mp4ff-decrypt", "-k", badKey, infile, outFile}, err: true}, - {desc: "non-encrypted file", args: []string{"mp4ff-decrypt", "-k", key, nonEncryptedFile, outFile}, err: false}, + {desc: "non-existing infile", args: []string{"mp4ff-decrypt", "-key", key, "infile.mp4", outFile}, err: true}, + {desc: "non-existing initfile", args: []string{"mp4ff-decrypt", "-init", "init.mp4", "-key", key, infile, outFile}, err: true}, + {desc: "bad infile", args: []string{"mp4ff-decrypt", "-key", key, "main.go", outFile}, err: true}, + {desc: "short key", args: []string{"mp4ff-decrypt", "-key", "ab", infile, outFile}, err: true}, + {desc: "bad key", args: []string{"mp4ff-decrypt", "-key", badKey, infile, outFile}, err: true}, + {desc: "non-encrypted file", args: []string{"mp4ff-decrypt", "-key", key, nonEncryptedFile, outFile}, err: false}, {desc: "version", args: []string{"mp4ff-decrypt", "-version"}, err: false}, {desc: "help", args: []string{"mp4ff-decrypt", "-h"}, err: false}, } @@ -52,38 +54,44 @@ func TestDecodeFiles(t *testing.T) { initFile string inFile string expectedOutFile string - hexKey string + keyHexOrBase64 string }{ { desc: "cenc", inFile: "../../mp4/testdata/prog_8s_enc_dashinit.mp4", expectedOutFile: "../../mp4/testdata/prog_8s_dec_dashinit.mp4", - hexKey: "63cb5f7184dd4b689a5c5ff11ee6a328", + keyHexOrBase64: "63cb5f7184dd4b689a5c5ff11ee6a328", + }, + { + desc: "cenc with base64 key", + inFile: "../../mp4/testdata/prog_8s_enc_dashinit.mp4", + expectedOutFile: "../../mp4/testdata/prog_8s_dec_dashinit.mp4", + keyHexOrBase64: "Y8tfcYTdS2iaXF/xHuajKA==", }, { desc: "cbcs", inFile: "../../mp4/testdata/cbcs.mp4", expectedOutFile: "../../mp4/testdata/cbcsdec.mp4", - hexKey: "22bdb0063805260307ee5045c0f3835a", + keyHexOrBase64: "22bdb0063805260307ee5045c0f3835a", }, { desc: "cbcs audio", inFile: "../../mp4/testdata/cbcs_audio.mp4", expectedOutFile: "../../mp4/testdata/cbcs_audiodec.mp4", - hexKey: "5ffd93861fa776e96cccd934898fc1c8", + keyHexOrBase64: "5ffd93861fa776e96cccd934898fc1c8", }, { desc: "PIFF audio", initFile: "testdata/PIFF/audio/init.mp4", inFile: "testdata/PIFF/audio/segment-1.0001.m4s", expectedOutFile: "testdata/PIFF/audio/segment-1.0001_dec.m4s", - hexKey: "602a9289bfb9b1995b75ac63f123fc86", + keyHexOrBase64: "602a9289bfb9b1995b75ac63f123fc86", }, { desc: "PIFF video", inFile: "testdata/PIFF/video/complseg-1.0001.mp4", expectedOutFile: "testdata/PIFF/video/complseg-1.0001_dec.mp4", - hexKey: "602a9289bfb9b1995b75ac63f123fc86", + keyHexOrBase64: "602a9289bfb9b1995b75ac63f123fc86", }, } tmpDir := t.TempDir() @@ -94,7 +102,7 @@ func TestDecodeFiles(t *testing.T) { if c.initFile != "" { args = append(args, "-init", c.initFile) } - args = append(args, "-k", c.hexKey, c.inFile, outFile) + args = append(args, "-key", c.keyHexOrBase64, c.inFile, outFile) err := run(args) if err != nil { t.Error(err) @@ -127,7 +135,11 @@ func BenchmarkDecodeCenc(b *testing.B) { for i := 0; i < b.N; i++ { inBuf := bytes.NewBuffer(raw) outBuf.Reset() - err = decryptFile(inBuf, nil, outBuf, hexKey) + key, err := mp4.UnpackKey(hexKey) + if err != nil { + b.Error(err) + } + err = decryptFile(inBuf, nil, outBuf, key) if err != nil { b.Error(err) } diff --git a/cmd/mp4ff-encrypt/doc.go b/cmd/mp4ff-encrypt/doc.go index 87e4b1b5..0741729a 100644 --- a/cmd/mp4ff-encrypt/doc.go +++ b/cmd/mp4ff-encrypt/doc.go @@ -1,27 +1,27 @@ /* mp4ff-encrypt encrypts a fragmented mp4 file using Common Encryption with cenc or cbcs scheme. A combined fragmented file with init segment and media segment(s) will be encrypted. -For a pure media segment, an init segment with encryption information is needed +For a pure media segment, an init segment with encryption information is needed. - Usage of mp4ff-encrypt: +Usage of mp4ff-encrypt: - mp4ff-encrypt [options] infile outfile +mp4ff-encrypt [options] infile outfile - options: +options: - -init string - Path to init file with encryption info (scheme, kid, pssh) - -iv string - Required: iv (16 or 32 hex chars) - -key string - Required: key (32 hex chars) - -kid string - key id (32 hex chars). Required if initFilePath empty - -pssh string - file with one or more pssh box(es) in binary format. Will be added at end of moov box - -scheme string - cenc or cbcs. Required if initFilePath empty (default "cenc") - -version - Get mp4ff version + -init string + Path to init file with encryption info (scheme, kid, pssh) + -iv string + Required: iv (16 or 32 hex chars) + -key string + Required: key (32 hex or 24 base64 chars) + -kid string + key id (32 hex or 24 base64 chars). Required if initFilePath empty + -pssh string + file with one or more pssh box(es) in binary format. Will be added at end of moov box + -scheme string + cenc or cbcs. Required if initFilePath empty (default "cenc") + -version + Get mp4ff version */ package main diff --git a/cmd/mp4ff-encrypt/main.go b/cmd/mp4ff-encrypt/main.go index 01578b6f..68e55ea1 100644 --- a/cmd/mp4ff-encrypt/main.go +++ b/cmd/mp4ff-encrypt/main.go @@ -25,8 +25,8 @@ Usage of %s: type options struct { initFile string - kidHex string - keyHex string + kidStr string + keyStr string ivHex string scheme string psshFile string @@ -43,8 +43,8 @@ func parseOptions(fs *flag.FlagSet, args []string) (*options, error) { opts := options{} fs.StringVar(&opts.initFile, "init", "", "Path to init file with encryption info (scheme, kid, pssh)") - fs.StringVar(&opts.kidHex, "kid", "", "key id (32 hex chars). Required if initFilePath empty") - fs.StringVar(&opts.keyHex, "key", "", "Required: key (32 hex chars)") + fs.StringVar(&opts.kidStr, "kid", "", "key id (32 hex or 24 base64 chars). Required if initFilePath empty") + fs.StringVar(&opts.keyStr, "key", "", "Required: key (32 hex or 24 base64 chars)") fs.StringVar(&opts.ivHex, "iv", "", "Required: iv (16 or 32 hex chars)") fs.StringVar(&opts.scheme, "scheme", "cenc", "cenc or cbcs. Required if initFilePath empty") fs.StringVar(&opts.psshFile, "pssh", "", "file with one or more pssh box(es) in binary format. Will be added at end of moov box") @@ -85,7 +85,7 @@ func run(args []string) error { var inFilePath = fs.Arg(0) var outFilePath = fs.Arg(1) - if opts.keyHex == "" || opts.ivHex == "" { + if opts.keyStr == "" || opts.ivHex == "" { fs.Usage() return fmt.Errorf("need both key and iv") } @@ -127,7 +127,7 @@ func run(args []string) error { } } - err = encryptFile(ifh, ofh, initSeg, opts.scheme, opts.kidHex, opts.keyHex, opts.ivHex, psshData) + err = encryptFile(ifh, ofh, initSeg, opts.scheme, opts.kidStr, opts.keyStr, opts.ivHex, psshData) if err != nil { return fmt.Errorf("encryptFile: %w", err) } @@ -135,7 +135,7 @@ func run(args []string) error { } func encryptFile(ifh io.Reader, ofh io.Writer, initSeg *mp4.InitSegment, - scheme, kidHex, keyHex, ivHex string, psshData []byte) error { + scheme, kidStr, keyStr, ivHex string, psshData []byte) error { if len(ivHex) != 32 && len(ivHex) != 16 { return fmt.Errorf("hex iv must have length 16 or 32 chars; %d", len(ivHex)) @@ -145,23 +145,22 @@ func encryptFile(ifh io.Reader, ofh io.Writer, initSeg *mp4.InitSegment, return fmt.Errorf("invalid iv %s", ivHex) } - if len(keyHex) != 32 { - return fmt.Errorf("hex key must have length 32 chars: %d", len(keyHex)) + if len(keyStr) != 32 { + return fmt.Errorf("hex key must have length 32 chars: %d", len(keyStr)) } - key, err := hex.DecodeString(keyHex) + key, err := mp4.UnpackKey(keyStr) if err != nil { - return fmt.Errorf("invalid key %s", keyHex) + return fmt.Errorf("invalid key %s, %w", keyStr, err) } var kidUUID mp4.UUID if initSeg == nil { - if len(kidHex) != 32 { - return fmt.Errorf("hex key id must have length 32 chars: %d", len(kidHex)) - } - kidUUID, err = mp4.NewUUIDFromHex(kidHex) + kid, err := mp4.UnpackKey(kidStr) if err != nil { - return fmt.Errorf("invalid kid %s", kidHex) + return fmt.Errorf("invalid key ID %s: %w", kidStr, err) } + kidHex := hex.EncodeToString(kid) + kidUUID, _ = mp4.NewUUIDFromString(kidHex) if scheme != "cenc" && scheme != "cbcs" { return fmt.Errorf("scheme must be cenc or cbcs: %s", scheme) } diff --git a/mp4/crypto_test.go b/mp4/crypto_test.go index a6a5e5b2..20381aae 100644 --- a/mp4/crypto_test.go +++ b/mp4/crypto_test.go @@ -105,7 +105,7 @@ func TestEncryptDecrypt(t *testing.T) { ivHex16 := "ffeeddccbbaa99887766554433221100" kidHex := "11112222333344445555666677778888" key, _ := hex.DecodeString(keyHex) - kidUUID, _ := NewUUIDFromHex(kidHex) + kidUUID, _ := NewUUIDFromString(kidHex) psshFile := "testdata/pssh.bin" psh, err := os.Open(psshFile) if err != nil { diff --git a/mp4/pssh.go b/mp4/pssh.go index 04dd46ac..8cffb5e4 100644 --- a/mp4/pssh.go +++ b/mp4/pssh.go @@ -19,32 +19,6 @@ const ( UUID_W3C_COMMON = "1077efec-c0b2-4d02-ace3-3c1e52e2fb4b" ) -// UUID - 16-byte KeyID or SystemID -type UUID []byte - -func (u UUID) String() string { - h := hex.EncodeToString(u) - if len(u) != 16 { - return h - } - return fmt.Sprintf("%s-%s-%s-%s-%s", h[0:8], h[8:12], h[12:16], h[16:20], h[20:32]) -} - -// NewUUIDFromHex creates a UUID from a hexadecimal string with 32 chars or 36 chars (with dashes) -func NewUUIDFromHex(h string) (UUID, error) { - if len(h) == 36 { - h = strings.ReplaceAll(h, "-", "") - } - if len(h) != 32 { - return nil, fmt.Errorf("hex has %d chars, not 32", len(h)) - } - s, err := hex.DecodeString(h) - if err != nil { - return nil, err - } - return UUID(s), nil -} - // ProtectionSystemName returns name of protection system if known. func ProtectionSystemName(systemID UUID) string { uStr := systemID.String() diff --git a/mp4/pssh_test.go b/mp4/pssh_test.go index 407fe8a7..f10cdd20 100644 --- a/mp4/pssh_test.go +++ b/mp4/pssh_test.go @@ -33,12 +33,12 @@ func TestPsshFromBase64(t *testing.T) { func TestEncodeDecodePSSH(t *testing.T) { hPR := strings.ReplaceAll(UUIDPlayReady, "-", "") - pr, err := NewUUIDFromHex(hPR) + pr, err := NewUUIDFromString(hPR) if err != nil { t.Fatal(err) } kid := "00112233445566778899aabbccddeeff" - ku, err := NewUUIDFromHex(kid) + ku, err := NewUUIDFromString(kid) if err != nil { t.Fatal(err) } @@ -71,7 +71,7 @@ func TestPsshUUIDs(t *testing.T) { } for _, c := range cases { - u, err := NewUUIDFromHex(c.hexUUIDs) + u, err := NewUUIDFromString(c.hexUUIDs) if err != nil { t.Fatal(err) } diff --git a/mp4/uuid.go b/mp4/uuid.go index 56b56121..4fa6c020 100644 --- a/mp4/uuid.go +++ b/mp4/uuid.go @@ -2,6 +2,7 @@ package mp4 import ( "bytes" + "encoding/base64" "encoding/hex" "fmt" "io" @@ -10,6 +11,27 @@ import ( "github.com/Eyevinn/mp4ff/bits" ) +// UUID - 16-byte KeyID or SystemID +type UUID []byte + +func (u UUID) String() string { + if len(u) != 16 { + return fmt.Sprintf("bad uuid %q", hex.EncodeToString(u)) + } + h := hex.EncodeToString(u[:]) + return fmt.Sprintf("%s-%s-%s-%s-%s", h[0:8], h[8:12], h[12:16], h[16:20], h[20:32]) +} + +// Equal compares with other UUID +func (u UUID) Equal(a UUID) bool { + return bytes.Equal(u[:], a[:]) +} + +// NewUUIDFromString creates a UUID from a hexadecimal, uuid-string or base64 string +func NewUUIDFromString(h string) (UUID, error) { + return createUUID(h) +} + const ( // The following UUIDs belong to Microsoft Smooth Streaming Protocol (MSS) @@ -29,34 +51,17 @@ const ( UUIDPiffSenc = "a2394f52-5a9b-4f14-a244-6c427c648df4" ) -// uuid - compact representation of UUID -type uuid [16]byte - -// String - UUID-formatted string -func (u uuid) String() string { - hexStr := hex.EncodeToString(u[:]) - return fmt.Sprintf("%s-%s-%s-%s-%s", hexStr[:8], hexStr[8:12], hexStr[12:16], hexStr[16:20], hexStr[20:]) -} - -// Equal - compare with other uuid -func (u uuid) Equal(a uuid) bool { - return bytes.Equal(u[:], a[:]) -} - -// createUUID - create uuid from string -func createUUID(u string) (uuid, error) { - var a uuid - stripped := strings.ReplaceAll(u, "-", "") - b, err := hex.DecodeString(stripped) - if err != nil || len(b) != 16 { - return a, fmt.Errorf("bad uuid string: %s", u) +// createUUID - create uuid from hex, uuid-formatted hex, or base64 string +func createUUID(u string) (UUID, error) { + b, err := UnpackKey(u) + if err != nil { + return nil, err } - _ = copy(a[:], b) - return a, nil + return UUID(b), nil } // mustCreateUUID - create uuid from string. Panic for bad string -func mustCreateUUID(u string) uuid { +func mustCreateUUID(u string) UUID { b, err := createUUID(u) if err != nil { panic(err.Error()) @@ -65,15 +70,15 @@ func mustCreateUUID(u string) uuid { } var ( - uuidTfxd uuid = mustCreateUUID(UUIDTfxd) - uuidTfrf uuid = mustCreateUUID(UUIDTfrf) - uuidPiffSenc uuid = mustCreateUUID(UUIDPiffSenc) + uuidTfxd UUID = mustCreateUUID(UUIDTfxd) + uuidTfrf UUID = mustCreateUUID(UUIDTfrf) + uuidPiffSenc UUID = mustCreateUUID(UUIDPiffSenc) ) // UUIDBox - Used as container for MSS boxes tfxd and tfrf // For unknown UUID, the data after the UUID is stored as UnknownPayload type UUIDBox struct { - uuid uuid + uuid UUID Tfxd *TfxdData Tfrf *TfrfData Senc *SencBox @@ -86,7 +91,8 @@ func (u *UUIDBox) UUID() string { return u.uuid.String() } -// UUID - Set UUID from string +// UUID - Set UUID from string corresponding to 16 bytes. +// The input should be a UUID-formatted hex string, plain hex or baset64 encoded. func (u *UUIDBox) SetUUID(uuid string) (err error) { u.uuid, err = createUUID(uuid) return err @@ -123,8 +129,10 @@ func DecodeUUIDBox(hdr BoxHeader, startPos uint64, r io.Reader) (Box, error) { // DecodeUUIDBoxSR - decode a UUID box including tfxd or tfrf func DecodeUUIDBoxSR(hdr BoxHeader, startPos uint64, sr bits.SliceReader) (Box, error) { - b := &UUIDBox{StartPos: startPos} - copy(b.uuid[:], sr.ReadBytes(16)) + b := &UUIDBox{ + StartPos: startPos, + uuid: sr.ReadBytes(16), + } switch b.UUID() { case UUIDTfxd: tfxd, err := decodeTfxd(sr) @@ -330,3 +338,38 @@ func (b *UUIDBox) Info(w io.Writer, specificBoxLevels, indent, indentStep string } return bd.err } + +// UnpackKey unpacks a hex or base64 encoded 16-byte key. +// The key can be in uuid formats with hyphens at positions 8, 13, 18, 23. +func UnpackKey(inKey string) (key []byte, err error) { + shorten := func(s string) string { + return fmt.Sprintf("%s...%s", s[:6], s[len(s)-6:]) + } + switch len(inKey) { + case 36: + if inKey[8] != '-' || inKey[13] != '-' || inKey[18] != '-' || inKey[23] != '-' { + return nil, fmt.Errorf("bad uuid format: %s", shorten(inKey)) + } + inKey = strings.ReplaceAll(inKey, "-", "") + if len(inKey) != 32 { + return nil, fmt.Errorf("bad uuid format: %s", shorten(inKey)) + } + key, err = hex.DecodeString(inKey) + if err != nil { + return nil, fmt.Errorf("bad uuid %s: %w", shorten(inKey), err) + } + case 32: + key, err = hex.DecodeString(inKey) + if err != nil { + return nil, fmt.Errorf("bad hex %s: %w", shorten(inKey), err) + } + case 24: + key, err = base64.StdEncoding.DecodeString(inKey) + if err != nil { + return nil, fmt.Errorf("bad base64 %s: %w", shorten(inKey), err) + } + default: + return nil, fmt.Errorf("cannot decode key %s", inKey) + } + return key, nil +} diff --git a/mp4/uuid_test.go b/mp4/uuid_test.go index 51c46817..823225c8 100644 --- a/mp4/uuid_test.go +++ b/mp4/uuid_test.go @@ -60,7 +60,7 @@ func TestUUIDVariants(t *testing.T) { func TestSetUUID(t *testing.T) { testCases := []struct { uuidStr string - expected uuid + expected UUID shouldFail bool }{ { @@ -109,3 +109,75 @@ func TestUUIDEncodeDecoder(t *testing.T) { } boxDiffAfterEncodeAndDecode(t, tfxd) } + +func TestUnpackKey(t *testing.T) { + cases := []struct { + desc string + keyStr string + expected []byte + expectedErr string + }{ + { + desc: "valid hex key", + keyStr: "00112233445566778899aabbccddeeff", + expected: []byte{0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, + 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff}, + expectedErr: "", + }, + { + desc: "invalid hex key", + keyStr: "0011223x445566778899aabbccddeeff", + expectedErr: "bad hex 001122...ddeeff: encoding/hex: invalid byte: U+0078 'x'", + }, + { + desc: "wrong length key", + keyStr: "00112233445566778899aab", + expectedErr: "cannot decode key 00112233445566778899aab", + }, + { + desc: "good uuid", + keyStr: "00112233-4455-6677-8899-aabbccddeeff", + expected: []byte{0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, + 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff}, + expectedErr: "", + }, + { + desc: "bad uuid, misplaced dashes", + keyStr: "00----112233445566778899aabbccddeeff", + expectedErr: "bad uuid format: 00----...ddeeff", + }, + { + desc: "bad uuid too many dashes", + keyStr: "00112233-4-55-6677-8899-aabbccddeeff", + expectedErr: "bad uuid format: 001122...ddeeff", + }, + { + desc: "bad hex in uuid", + keyStr: "0011223x-4455-6677-8899-aabbccddeeff", + expectedErr: "bad uuid 001122...ddeeff: encoding/hex: invalid byte: U+0078 'x'", + }, + { + desc: "valid base64 key", + keyStr: "ABEiM0RVZneImaq7zN3u/w=-", + expectedErr: "bad base64 ABEiM0...3u/w=-: illegal base64 data at input byte 22", + }, + } + for _, c := range cases { + t.Run(c.desc, func(t *testing.T) { + key, err := UnpackKey(c.keyStr) + if c.expectedErr != "" { + if err == nil { + t.Error("expected error but got nil") + } + if err.Error() != c.expectedErr { + t.Errorf("error %q not matching expected error %q", err, c.expectedErr) + } + return + } + if !bytes.Equal(key, c.expected) { + t.Errorf("got %x instead of %x", key, c.expected) + } + }) + } + +}