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