diff --git a/util/conversion.go b/util/conversion.go index acd67d0..4903248 100644 --- a/util/conversion.go +++ b/util/conversion.go @@ -1,6 +1,7 @@ package util import ( + "encoding/binary" "strings" euni "golang.org/x/text/encoding/unicode" @@ -19,6 +20,13 @@ func BytesToInt(gBytes []byte) uint32 { return integer } +// Simply converts given uint32 into synch unsafe bytes +func IntToBytes(gInt uint32) []byte { + buff := make([]byte, 4) + binary.BigEndian.PutUint32(buff, gInt) + return buff +} + // Decodes given integer bytes into integer, ignores the first bit // of every given byte in binary form func BytesToIntSynchsafe(gBytes []byte) uint32 { diff --git a/util/conversion_test.go b/util/conversion_test.go index aa2e9a6..d793397 100644 --- a/util/conversion_test.go +++ b/util/conversion_test.go @@ -1,6 +1,8 @@ package util import ( + "fmt" + "strconv" "testing" ) @@ -48,3 +50,19 @@ func TestIntToBytesSynchsafe(t *testing.T) { } } } + +func TestIntToBytes(t *testing.T) { + var testInt uint32 = 124567 + testIntBits := fmt.Sprintf("%032b", testInt) + + gotBytes := IntToBytes(testInt) + + i := 0 + for _, gotByte := range gotBytes { + correctByte, _ := strconv.ParseUint(testIntBits[i:i+8], 2, 8) + if gotByte != byte(correctByte) { + t.Errorf("IntToBytes failed: expected byte to be %d; got %d", correctByte, gotByte) + } + i += 8 + } +} diff --git a/v2/header.go b/v2/header.go index 469b7c1..5cb0e69 100644 --- a/v2/header.go +++ b/v2/header.go @@ -10,7 +10,7 @@ import ( // Main header`s flags type HeaderFlags struct { - Unsynchronisated bool + Unsynchronised bool Compressed bool HasExtendedHeader bool Experimental bool @@ -19,7 +19,7 @@ type HeaderFlags struct { // ID3v2.x`s main header structure type Header struct { - Identifier string + // Identifier string Flags HeaderFlags Version string Size uint32 @@ -197,7 +197,6 @@ func readHeader(rs io.ReadSeeker) (Header, error) { if !bytes.Equal([]byte(HEADERIDENTIFIER), identifier) { return Header{}, ErrDoesNotUseID3v2 } - header.Identifier = string(identifier) // version majorVersion := int(hBytes[3]) @@ -221,9 +220,9 @@ func readHeader(rs io.ReadSeeker) (Header, error) { switch header.Version { case V2_2: if util.GetBit(flags, 1) { - header.Flags.Unsynchronisated = true + header.Flags.Unsynchronised = true } else { - header.Flags.Unsynchronisated = false + header.Flags.Unsynchronised = false } if util.GetBit(flags, 2) { header.Flags.Compressed = true @@ -232,9 +231,9 @@ func readHeader(rs io.ReadSeeker) (Header, error) { } case V2_3: if util.GetBit(flags, 1) { - header.Flags.Unsynchronisated = true + header.Flags.Unsynchronised = true } else { - header.Flags.Unsynchronisated = false + header.Flags.Unsynchronised = false } if util.GetBit(flags, 2) { header.Flags.HasExtendedHeader = true @@ -251,9 +250,9 @@ func readHeader(rs io.ReadSeeker) (Header, error) { case V2_4: if util.GetBit(flags, 1) { - header.Flags.Unsynchronisated = true + header.Flags.Unsynchronised = true } else { - header.Flags.Unsynchronisated = false + header.Flags.Unsynchronised = false } if util.GetBit(flags, 2) { header.Flags.HasExtendedHeader = true @@ -288,3 +287,146 @@ func readHeader(rs io.ReadSeeker) (Header, error) { return header, nil } + +// Converts given HeaderFlags struct into ready-to-write byte +// containing flags +func headerFlagsToByte(hf HeaderFlags, version string) byte { + var flagsByte byte = 0 + switch version { + case V2_2: + if hf.Unsynchronised { + flagsByte = util.SetBit(flagsByte, 8) + } + if hf.Compressed { + flagsByte = util.SetBit(flagsByte, 7) + } + + case V2_3: + if hf.Unsynchronised { + flagsByte = util.SetBit(flagsByte, 8) + } + if hf.HasExtendedHeader { + flagsByte = util.SetBit(flagsByte, 7) + } + if hf.Experimental { + flagsByte = util.SetBit(flagsByte, 6) + } + + case V2_4: + if hf.Unsynchronised { + flagsByte = util.SetBit(flagsByte, 8) + } + if hf.HasExtendedHeader { + flagsByte = util.SetBit(flagsByte, 7) + } + if hf.Experimental { + flagsByte = util.SetBit(flagsByte, 6) + } + if hf.FooterPresent { + flagsByte = util.SetBit(flagsByte, 5) + } + + } + return flagsByte +} + +// Converts given header into ready-to-write bytes +func (h *Header) toBytes() []byte { + buff := new(bytes.Buffer) + + // id + buff.Write([]byte(HEADERIDENTIFIER)) + + // version + version := []byte{0, 0} + switch h.Version { + case V2_2: + version = []byte{2, 0} + case V2_3: + version = []byte{3, 0} + case V2_4: + version = []byte{4, 0} + } + buff.Write(version) + + // flags + flagByte := headerFlagsToByte(h.Flags, h.Version) + buff.WriteByte(flagByte) + + // size + tagSize := util.IntToBytesSynchsafe(h.Size) + buff.Write(tagSize) + + // extended header + if !h.Flags.HasExtendedHeader { + return buff.Bytes() + } + + // double check for possible errors + if h.Version == V2_2 { + return buff.Bytes() + } + + // size + extSize := util.IntToBytes(h.ExtendedHeader.Size) + buff.Write(extSize) + + // flags and other version specific fields + switch h.Version { + case V2_3: + // flags + flagBytes := []byte{0, 0} + if h.ExtendedHeader.Flags.CRCpresent { + flagBytes[0] = util.SetBit(flagBytes[0], 8) + } + buff.Write(flagBytes) + + // crc data + if h.ExtendedHeader.Flags.CRCpresent { + buff.Write(h.ExtendedHeader.CRCdata) + } + + // padding size + paddingSize := util.IntToBytes(h.ExtendedHeader.PaddingSize) + buff.Write(paddingSize) + + case V2_4: + numberOfFlagBytes := byte(1) + buff.WriteByte(numberOfFlagBytes) + + extFlags := byte(0) + if h.ExtendedHeader.Flags.UpdateTag { + extFlags = util.SetBit(extFlags, 7) + } + if h.ExtendedHeader.Flags.CRCpresent { + extFlags = util.SetBit(extFlags, 6) + } + if h.ExtendedHeader.Flags.HasRestrictions { + extFlags = util.SetBit(extFlags, 5) + } + + buff.WriteByte(extFlags) + + // writing data, provided by flags + if h.ExtendedHeader.Flags.UpdateTag { + // data len + buff.WriteByte(0) + } + + if h.ExtendedHeader.Flags.CRCpresent { + // data len + buff.WriteByte(5) + // data + buff.Write(h.ExtendedHeader.CRCdata) + } + + if h.ExtendedHeader.Flags.HasRestrictions { + // data len + buff.WriteByte(1) + // data + buff.WriteByte(h.ExtendedHeader.Flags.Restrictions) + } + } + + return buff.Bytes() +} diff --git a/v2/header_test.go b/v2/header_test.go index dc6da18..a18966a 100644 --- a/v2/header_test.go +++ b/v2/header_test.go @@ -4,6 +4,8 @@ import ( "os" "path/filepath" "testing" + + "github.com/Unbewohnte/id3ed/util" ) var TESTDATAPATH string = filepath.Join("..", "testData") @@ -19,19 +21,58 @@ func TestReadHeader(t *testing.T) { t.Errorf("GetHeader failed: %s", err) } - if header.Identifier != "ID3" { - t.Errorf("GetHeader failed: expected identifier %s; got %s", "ID3", header.Identifier) - } - if header.Flags.HasExtendedHeader != false { t.Errorf("GetHeader failed: expected flag %v; got %v", false, header.Flags.HasExtendedHeader) } - if header.Flags.Unsynchronisated != false { - t.Errorf("GetHeader failed: expected flag %v; got %v", false, header.Flags.Unsynchronisated) + if header.Flags.Unsynchronised != false { + t.Errorf("GetHeader failed: expected flag %v; got %v", false, header.Flags.Unsynchronised) } if header.Size != 1138 { t.Errorf("GetHeader failed: expected size %v; got %v", 1138, header.Size) } } + +func TestHeaderFlagsToByte(t *testing.T) { + hf := HeaderFlags{ + Experimental: true, + FooterPresent: true, + } + var correctFlagsByte byte = 0 + correctFlagsByte = util.SetBit(correctFlagsByte, 5) + correctFlagsByte = util.SetBit(correctFlagsByte, 6) + + gotByte := headerFlagsToByte(hf, V2_4) + + if gotByte != correctFlagsByte { + t.Errorf("headerFlagsToByte failed: expected to get %d; got %d", correctFlagsByte, gotByte) + } +} + +func TestHeaderToBytes(t *testing.T) { + testHeader := Header{ + Version: V2_4, + Flags: HeaderFlags{}, // all false + Size: 12345, + ExtendedHeader: ExtendedHeader{}, + } + + hBytes := testHeader.toBytes() + + // t.Errorf("%v", hBytes) + // 73 68 51 4 0 0 0 0 96 57 + // 73 68 51 - identifier + // 4 0 - version + // 0 - flags + // 0 0 96 57 - size + + if string(hBytes[0:3]) != HEADERIDENTIFIER { + t.Errorf("expected to get %s, got %s", HEADERIDENTIFIER, string(hBytes[0:3])) + } + + if util.BytesToIntSynchsafe(hBytes[6:10]) != testHeader.Size { + t.Errorf("toBytes failed: expected size to be %d; got %d", + testHeader.Size, util.BytesToIntSynchsafe(hBytes[7:10])) + } +}