pool.go 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293
  1. // Copyright 2015 The Go Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style
  3. // license that can be found in the LICENSE file.
  4. package binres
  5. import (
  6. "fmt"
  7. "unicode/utf16"
  8. )
  9. const (
  10. SortedFlag uint32 = 1 << 0
  11. UTF8Flag = 1 << 8
  12. )
  13. // PoolRef is the i'th string in a pool.
  14. type PoolRef uint32
  15. // Resolve returns the string entry of PoolRef in pl.
  16. func (ref PoolRef) Resolve(pl *Pool) string {
  17. return pl.strings[ref]
  18. }
  19. // Pool is a container for string and style span collections.
  20. //
  21. // Pool has the following structure marshalled:
  22. //
  23. // chunkHeader
  24. // uint32 number of strings in this pool
  25. // uint32 number of style spans in pool
  26. // uint32 SortedFlag, UTF8Flag
  27. // uint32 index of string data from header
  28. // uint32 index of style data from header
  29. // []uint32 string indices starting at zero
  30. // []uint16 or []uint8 concatenation of string entries
  31. //
  32. // UTF-16 entries are as follows:
  33. //
  34. // uint16 string length, exclusive
  35. // uint16 [optional] low word if high bit of length set
  36. // [n]byte data
  37. // uint16 0x0000 terminator
  38. //
  39. // UTF-8 entries are as follows:
  40. //
  41. // uint8 character length, exclusive
  42. // uint8 [optional] low word if high bit of character length set
  43. // uint8 byte length, exclusive
  44. // uint8 [optional] low word if high bit of byte length set
  45. // [n]byte data
  46. // uint8 0x00 terminator
  47. type Pool struct {
  48. chunkHeader
  49. strings []string
  50. styles []*Span
  51. flags uint32 // SortedFlag, UTF8Flag
  52. }
  53. // ref returns the PoolRef of s, inserting s if it doesn't exist.
  54. func (pl *Pool) ref(s string) PoolRef {
  55. for i, x := range pl.strings {
  56. if s == x {
  57. return PoolRef(i)
  58. }
  59. }
  60. pl.strings = append(pl.strings, s)
  61. return PoolRef(len(pl.strings) - 1)
  62. }
  63. // RefByName returns the PoolRef of s, or error if not exists.
  64. func (pl *Pool) RefByName(s string) (PoolRef, error) {
  65. for i, x := range pl.strings {
  66. if s == x {
  67. return PoolRef(i), nil
  68. }
  69. }
  70. return 0, fmt.Errorf("PoolRef by name %q does not exist", s)
  71. }
  72. func (pl *Pool) IsSorted() bool { return pl.flags&SortedFlag == SortedFlag }
  73. func (pl *Pool) IsUTF8() bool { return pl.flags&UTF8Flag == UTF8Flag }
  74. func (pl *Pool) UnmarshalBinary(bin []byte) error {
  75. if err := (&pl.chunkHeader).UnmarshalBinary(bin); err != nil {
  76. return err
  77. }
  78. if pl.typ != ResStringPool {
  79. return fmt.Errorf("have type %s, want %s", pl.typ, ResStringPool)
  80. }
  81. pl.strings = make([]string, btou32(bin[8:]))
  82. pl.styles = make([]*Span, btou32(bin[12:]))
  83. pl.flags = btou32(bin[16:])
  84. offstrings := btou32(bin[20:])
  85. offstyle := btou32(bin[24:])
  86. hdrlen := 28
  87. if pl.IsUTF8() {
  88. for i := range pl.strings {
  89. offset := int(offstrings + btou32(bin[hdrlen+i*4:]))
  90. // if leading bit set for nchars and nbytes,
  91. // treat first byte as 7-bit high word and next as low word.
  92. nchars := int(bin[offset])
  93. offset++
  94. if nchars&(1<<7) != 0 {
  95. n0 := nchars ^ (1 << 7) // high word
  96. n1 := int(bin[offset]) // low word
  97. nchars = n0*(1<<8) + n1
  98. offset++
  99. }
  100. // TODO(d) At least one case in android.jar (api-10) resource table has only
  101. // highbit set, making 7-bit highword zero. The reason for this is currently
  102. // unknown but would make tests that unmarshal-marshal to match bytes impossible.
  103. // The values noted were high-word: 0 (after highbit unset), low-word: 141
  104. // The size may be treated as an int8 triggering the use of two bytes to store size
  105. // even though the implementation uses uint8.
  106. nbytes := int(bin[offset])
  107. offset++
  108. if nbytes&(1<<7) != 0 {
  109. n0 := nbytes ^ (1 << 7) // high word
  110. n1 := int(bin[offset]) // low word
  111. nbytes = n0*(1<<8) + n1
  112. offset++
  113. }
  114. data := bin[offset : offset+nbytes]
  115. if x := uint8(bin[offset+nbytes]); x != 0 {
  116. return fmt.Errorf("expected zero terminator, got 0x%02X for nchars=%v nbytes=%v data=%q", x, nchars, nbytes, data)
  117. }
  118. pl.strings[i] = string(data)
  119. }
  120. } else {
  121. for i := range pl.strings {
  122. offset := int(offstrings + btou32(bin[hdrlen+i*4:])) // read index of string
  123. // if leading bit set for nchars, treat first byte as 7-bit high word and next as low word.
  124. nchars := int(btou16(bin[offset:]))
  125. offset += 2
  126. if nchars&(1<<15) != 0 { // TODO(d) this is untested
  127. n0 := nchars ^ (1 << 15) // high word
  128. n1 := int(btou16(bin[offset:])) // low word
  129. nchars = n0*(1<<16) + n1
  130. offset += 2
  131. }
  132. data := make([]uint16, nchars)
  133. for i := range data {
  134. data[i] = btou16(bin[offset+2*i:])
  135. }
  136. if x := btou16(bin[offset+nchars*2:]); x != 0 {
  137. return fmt.Errorf("expected zero terminator, got 0x%04X for nchars=%v data=%q", x, nchars, data)
  138. }
  139. pl.strings[i] = string(utf16.Decode(data))
  140. }
  141. }
  142. // TODO decode styles
  143. _ = offstyle
  144. return nil
  145. }
  146. func (pl *Pool) MarshalBinary() ([]byte, error) {
  147. if pl.IsUTF8() {
  148. return nil, fmt.Errorf("encode utf8 not supported")
  149. }
  150. var (
  151. hdrlen = 28
  152. // indices of string indices
  153. iis = make([]uint32, len(pl.strings))
  154. iislen = len(iis) * 4
  155. // utf16 encoded strings concatenated together
  156. strs []uint16
  157. )
  158. for i, x := range pl.strings {
  159. if len(x)>>16 > 0 {
  160. panic(fmt.Errorf("string lengths over 1<<15 not yet supported, got len %d", len(x)))
  161. }
  162. p := utf16.Encode([]rune(x))
  163. if len(p) == 0 {
  164. strs = append(strs, 0x0000, 0x0000)
  165. } else {
  166. strs = append(strs, uint16(len(p))) // string length (implicitly includes zero terminator to follow)
  167. strs = append(strs, p...)
  168. strs = append(strs, 0) // zero terminated
  169. }
  170. // indices start at zero
  171. if i+1 != len(iis) {
  172. iis[i+1] = uint32(len(strs) * 2) // utf16 byte index
  173. }
  174. }
  175. // check strings is 4-byte aligned, pad with zeros if not.
  176. for x := (len(strs) * 2) % 4; x != 0; x -= 2 {
  177. strs = append(strs, 0x0000)
  178. }
  179. strslen := len(strs) * 2
  180. hdr := chunkHeader{
  181. typ: ResStringPool,
  182. headerByteSize: 28,
  183. byteSize: uint32(28 + iislen + strslen),
  184. }
  185. bin := make([]byte, hdr.byteSize)
  186. hdrbin, err := hdr.MarshalBinary()
  187. if err != nil {
  188. return nil, err
  189. }
  190. copy(bin, hdrbin)
  191. putu32(bin[8:], uint32(len(pl.strings)))
  192. putu32(bin[12:], uint32(len(pl.styles)))
  193. putu32(bin[16:], pl.flags)
  194. putu32(bin[20:], uint32(hdrlen+iislen))
  195. putu32(bin[24:], 0) // index of styles start, is 0 when styles length is 0
  196. buf := bin[28:]
  197. for _, x := range iis {
  198. putu32(buf, x)
  199. buf = buf[4:]
  200. }
  201. for _, x := range strs {
  202. putu16(buf, x)
  203. buf = buf[2:]
  204. }
  205. if len(buf) != 0 {
  206. panic(fmt.Errorf("failed to fill allocated buffer, %v bytes left over", len(buf)))
  207. }
  208. return bin, nil
  209. }
  210. type Span struct {
  211. name PoolRef
  212. firstChar, lastChar uint32
  213. }
  214. func (spn *Span) UnmarshalBinary(bin []byte) error {
  215. const end = 0xFFFFFFFF
  216. spn.name = PoolRef(btou32(bin))
  217. if spn.name == end {
  218. return nil
  219. }
  220. spn.firstChar = btou32(bin[4:])
  221. spn.lastChar = btou32(bin[8:])
  222. return nil
  223. }
  224. // Map contains a uint32 slice mapping strings in the string
  225. // pool back to resource identifiers. The i'th element of the slice
  226. // is also the same i'th element of the string pool.
  227. type Map struct {
  228. chunkHeader
  229. rs []TableRef
  230. }
  231. func (m *Map) UnmarshalBinary(bin []byte) error {
  232. (&m.chunkHeader).UnmarshalBinary(bin)
  233. buf := bin[m.headerByteSize:m.byteSize]
  234. m.rs = make([]TableRef, len(buf)/4)
  235. for i := range m.rs {
  236. m.rs[i] = TableRef(btou32(buf[i*4:]))
  237. }
  238. return nil
  239. }
  240. func (m *Map) MarshalBinary() ([]byte, error) {
  241. m.typ = ResXMLResourceMap
  242. m.headerByteSize = 8
  243. m.byteSize = uint32(m.headerByteSize) + uint32(len(m.rs)*4)
  244. bin := make([]byte, m.byteSize)
  245. b, err := m.chunkHeader.MarshalBinary()
  246. if err != nil {
  247. return nil, err
  248. }
  249. copy(bin, b)
  250. for i, r := range m.rs {
  251. putu32(bin[8+i*4:], uint32(r))
  252. }
  253. return bin, nil
  254. }