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