| // Copyright 2015 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| package binres |
| |
| import ( |
| "fmt" |
| "unicode/utf16" |
| ) |
| |
| const ( |
| SortedFlag uint32 = 1 << 0 |
| UTF8Flag = 1 << 8 |
| ) |
| |
| // PoolRef is the i'th string in a pool. |
| type PoolRef uint32 |
| |
| // Resolve returns the string entry of PoolRef in pl. |
| func (ref PoolRef) Resolve(pl *Pool) string { |
| return pl.strings[ref] |
| } |
| |
| // Pool is a container for string and style span collections. |
| // |
| // Pool has the following structure marshalled: |
| // chunkHeader |
| // uint32 number of strings in this pool |
| // uint32 number of style spans in pool |
| // uint32 SortedFlag, UTF8Flag |
| // uint32 index of string data from header |
| // uint32 index of style data from header |
| // []uint32 string indices starting at zero |
| // []uint16 or []uint8 concatenation of string entries |
| // |
| // UTF-16 entries are as follows: |
| // uint16 string length, exclusive |
| // uint16 [optional] low word if high bit of length set |
| // [n]byte data |
| // uint16 0x0000 terminator |
| // |
| // UTF-8 entries are as follows: |
| // uint8 character length, exclusive |
| // uint8 [optional] low word if high bit of character length set |
| // uint8 byte length, exclusive |
| // uint8 [optional] low word if high bit of byte length set |
| // [n]byte data |
| // uint8 0x00 terminator |
| type Pool struct { |
| chunkHeader |
| strings []string |
| styles []*Span |
| flags uint32 // SortedFlag, UTF8Flag |
| } |
| |
| // ref returns the PoolRef of s, inserting s if it doesn't exist. |
| func (pl *Pool) ref(s string) PoolRef { |
| for i, x := range pl.strings { |
| if s == x { |
| return PoolRef(i) |
| } |
| } |
| pl.strings = append(pl.strings, s) |
| return PoolRef(len(pl.strings) - 1) |
| } |
| |
| // RefByName returns the PoolRef of s, or error if not exists. |
| func (pl *Pool) RefByName(s string) (PoolRef, error) { |
| for i, x := range pl.strings { |
| if s == x { |
| return PoolRef(i), nil |
| } |
| } |
| return 0, fmt.Errorf("PoolRef by name %q does not exist", s) |
| } |
| |
| func (pl *Pool) IsSorted() bool { return pl.flags&SortedFlag == SortedFlag } |
| func (pl *Pool) IsUTF8() bool { return pl.flags&UTF8Flag == UTF8Flag } |
| |
| func (pl *Pool) UnmarshalBinary(bin []byte) error { |
| if err := (&pl.chunkHeader).UnmarshalBinary(bin); err != nil { |
| return err |
| } |
| if pl.typ != ResStringPool { |
| return fmt.Errorf("have type %s, want %s", pl.typ, ResStringPool) |
| } |
| |
| pl.strings = make([]string, btou32(bin[8:])) |
| pl.styles = make([]*Span, btou32(bin[12:])) |
| pl.flags = btou32(bin[16:]) |
| |
| offstrings := btou32(bin[20:]) |
| offstyle := btou32(bin[24:]) |
| hdrlen := 28 |
| |
| if pl.IsUTF8() { |
| for i := range pl.strings { |
| offset := int(offstrings + btou32(bin[hdrlen+i*4:])) |
| |
| // if leading bit set for nchars and nbytes, |
| // treat first byte as 7-bit high word and next as low word. |
| nchars := int(bin[offset]) |
| offset++ |
| if nchars&(1<<7) != 0 { |
| n0 := nchars ^ (1 << 7) // high word |
| n1 := int(bin[offset]) // low word |
| nchars = n0*(1<<8) + n1 |
| offset++ |
| } |
| |
| // TODO(d) At least one case in android.jar (api-10) resource table has only |
| // highbit set, making 7-bit highword zero. The reason for this is currently |
| // unknown but would make tests that unmarshal-marshal to match bytes impossible. |
| // The values noted were high-word: 0 (after highbit unset), low-word: 141 |
| // The size may be treated as an int8 triggering the use of two bytes to store size |
| // even though the implementation uses uint8. |
| nbytes := int(bin[offset]) |
| offset++ |
| if nbytes&(1<<7) != 0 { |
| n0 := nbytes ^ (1 << 7) // high word |
| n1 := int(bin[offset]) // low word |
| nbytes = n0*(1<<8) + n1 |
| offset++ |
| } |
| |
| data := bin[offset : offset+nbytes] |
| if x := uint8(bin[offset+nbytes]); x != 0 { |
| return fmt.Errorf("expected zero terminator, got 0x%02X for nchars=%v nbytes=%v data=%q", x, nchars, nbytes, data) |
| } |
| pl.strings[i] = string(data) |
| } |
| } else { |
| for i := range pl.strings { |
| offset := int(offstrings + btou32(bin[hdrlen+i*4:])) // read index of string |
| |
| // if leading bit set for nchars, treat first byte as 7-bit high word and next as low word. |
| nchars := int(btou16(bin[offset:])) |
| offset += 2 |
| if nchars&(1<<15) != 0 { // TODO(d) this is untested |
| n0 := nchars ^ (1 << 15) // high word |
| n1 := int(btou16(bin[offset:])) // low word |
| nchars = n0*(1<<16) + n1 |
| offset += 2 |
| } |
| |
| data := make([]uint16, nchars) |
| for i := range data { |
| data[i] = btou16(bin[offset+2*i:]) |
| } |
| |
| if x := btou16(bin[offset+nchars*2:]); x != 0 { |
| return fmt.Errorf("expected zero terminator, got 0x%04X for nchars=%v data=%q", x, nchars, data) |
| } |
| pl.strings[i] = string(utf16.Decode(data)) |
| } |
| } |
| |
| // TODO decode styles |
| _ = offstyle |
| |
| return nil |
| } |
| |
| func (pl *Pool) MarshalBinary() ([]byte, error) { |
| if pl.IsUTF8() { |
| return nil, fmt.Errorf("encode utf8 not supported") |
| } |
| |
| var ( |
| hdrlen = 28 |
| // indices of string indices |
| iis = make([]uint32, len(pl.strings)) |
| iislen = len(iis) * 4 |
| // utf16 encoded strings concatenated together |
| strs []uint16 |
| ) |
| for i, x := range pl.strings { |
| if len(x)>>16 > 0 { |
| panic(fmt.Errorf("string lengths over 1<<15 not yet supported, got len %d", len(x))) |
| } |
| p := utf16.Encode([]rune(x)) |
| if len(p) == 0 { |
| strs = append(strs, 0x0000, 0x0000) |
| } else { |
| strs = append(strs, uint16(len(p))) // string length (implicitly includes zero terminator to follow) |
| strs = append(strs, p...) |
| strs = append(strs, 0) // zero terminated |
| } |
| // indices start at zero |
| if i+1 != len(iis) { |
| iis[i+1] = uint32(len(strs) * 2) // utf16 byte index |
| } |
| } |
| |
| // check strings is 4-byte aligned, pad with zeros if not. |
| for x := (len(strs) * 2) % 4; x != 0; x -= 2 { |
| strs = append(strs, 0x0000) |
| } |
| |
| strslen := len(strs) * 2 |
| |
| hdr := chunkHeader{ |
| typ: ResStringPool, |
| headerByteSize: 28, |
| byteSize: uint32(28 + iislen + strslen), |
| } |
| |
| bin := make([]byte, hdr.byteSize) |
| |
| hdrbin, err := hdr.MarshalBinary() |
| if err != nil { |
| return nil, err |
| } |
| copy(bin, hdrbin) |
| |
| putu32(bin[8:], uint32(len(pl.strings))) |
| putu32(bin[12:], uint32(len(pl.styles))) |
| putu32(bin[16:], pl.flags) |
| putu32(bin[20:], uint32(hdrlen+iislen)) |
| putu32(bin[24:], 0) // index of styles start, is 0 when styles length is 0 |
| |
| buf := bin[28:] |
| for _, x := range iis { |
| putu32(buf, x) |
| buf = buf[4:] |
| } |
| |
| for _, x := range strs { |
| putu16(buf, x) |
| buf = buf[2:] |
| } |
| |
| if len(buf) != 0 { |
| panic(fmt.Errorf("failed to fill allocated buffer, %v bytes left over", len(buf))) |
| } |
| |
| return bin, nil |
| } |
| |
| type Span struct { |
| name PoolRef |
| |
| firstChar, lastChar uint32 |
| } |
| |
| func (spn *Span) UnmarshalBinary(bin []byte) error { |
| const end = 0xFFFFFFFF |
| spn.name = PoolRef(btou32(bin)) |
| if spn.name == end { |
| return nil |
| } |
| spn.firstChar = btou32(bin[4:]) |
| spn.lastChar = btou32(bin[8:]) |
| return nil |
| } |
| |
| // Map contains a uint32 slice mapping strings in the string |
| // pool back to resource identifiers. The i'th element of the slice |
| // is also the same i'th element of the string pool. |
| type Map struct { |
| chunkHeader |
| rs []TableRef |
| } |
| |
| func (m *Map) UnmarshalBinary(bin []byte) error { |
| (&m.chunkHeader).UnmarshalBinary(bin) |
| buf := bin[m.headerByteSize:m.byteSize] |
| m.rs = make([]TableRef, len(buf)/4) |
| for i := range m.rs { |
| m.rs[i] = TableRef(btou32(buf[i*4:])) |
| } |
| return nil |
| } |
| |
| func (m *Map) MarshalBinary() ([]byte, error) { |
| m.typ = ResXMLResourceMap |
| m.headerByteSize = 8 |
| m.byteSize = uint32(m.headerByteSize) + uint32(len(m.rs)*4) |
| bin := make([]byte, m.byteSize) |
| b, err := m.chunkHeader.MarshalBinary() |
| if err != nil { |
| return nil, err |
| } |
| copy(bin, b) |
| for i, r := range m.rs { |
| putu32(bin[8+i*4:], uint32(r)) |
| } |
| return bin, nil |
| } |