github.com/c-darwin/mobile@v0.0.0-20160313183840-ff625c46f7c9/cmd/gomobile/binary_xml.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 main 6 7 import ( 8 "encoding/xml" 9 "fmt" 10 "io" 11 "sort" 12 "strconv" 13 "strings" 14 "unicode/utf16" 15 ) 16 17 // binaryXML converts XML into Android's undocumented binary XML format. 18 // 19 // The best source of information on this format seems to be the source code 20 // in AOSP frameworks-base. Android "resource" types seem to describe the 21 // encoded bytes, in particular: 22 // 23 // ResChunk_header 24 // ResStringPool_header 25 // ResXMLTree_node 26 // 27 // These are defined in: 28 // 29 // https://android.googlesource.com/platform/frameworks/base/+/master/include/androidfw/ResourceTypes.h 30 // 31 // The rough format of the file is a resource chunk containing a sequence of 32 // chunks. Each chunk is made up of a header and a body. The header begins with 33 // the contents of the ResChunk_header struct, which includes the size of both 34 // the header and the body. 35 // 36 // Both the header and body are 4-byte aligned. 37 // 38 // Values are encoded as little-endian. 39 // 40 // The android source code for encoding is done in the aapt tool. Its source 41 // code lives in AOSP: 42 // 43 // https://android.googlesource.com/platform/frameworks/base.git/+/master/tools/aapt 44 // 45 // A sample layout: 46 // 47 // File Header (ResChunk_header, type XML) 48 // Chunk: String Pool (type STRING_POOL) 49 // Sequence of strings, each with the format: 50 // uint16 length 51 // uint16 extended_length -- only if top bit set on length 52 // UTF-16LE string 53 // two zero bytes 54 // Resource Map 55 // The [i]th 4-byte entry in the resource map corresponds with 56 // the [i]th string from the string pool. The 4-bytes are a 57 // Resource ID constant defined: 58 // http://developer.android.com/reference/android/R.attr.html 59 // This appears to be a way to map strings onto enum values. 60 // Chunk: Namespace Start (ResXMLTree_node; ResXMLTree_namespaceExt) 61 // Chunk: Element Start 62 // ResXMLTree_node 63 // ResXMLTree_attrExt 64 // ResXMLTree_attribute (repeated attributeCount times) 65 // Chunk: Element End 66 // (ResXMLTree_node; ResXMLTree_endElementExt) 67 // ... 68 // Chunk: Namespace End 69 func binaryXML(r io.Reader) ([]byte, error) { 70 lr := &lineReader{r: r} 71 d := xml.NewDecoder(lr) 72 73 pool := new(binStringPool) 74 depth := 0 75 elements := []chunk{} 76 namespaceEnds := make(map[int][]binEndNamespace) 77 78 for { 79 line := lr.line(d.InputOffset()) 80 tok, err := d.Token() 81 if err != nil { 82 if err == io.EOF { 83 break 84 } 85 return nil, err 86 } 87 switch tok := tok.(type) { 88 case xml.StartElement: 89 // Intercept namespace definitions. 90 var attr []*binAttr 91 for _, a := range tok.Attr { 92 if a.Name.Space == "xmlns" { 93 elements = append(elements, binStartNamespace{ 94 line: line, 95 prefix: pool.get(a.Name.Local), 96 url: pool.get(a.Value), 97 }) 98 namespaceEnds[depth] = append([]binEndNamespace{{ 99 line: line, 100 prefix: pool.get(a.Name.Local), 101 url: pool.get(a.Value), 102 }}, namespaceEnds[depth]...) 103 continue 104 } 105 ba, err := pool.getAttr(a) 106 if err != nil { 107 return nil, fmt.Errorf("%d: %s: %v", line, a.Name.Local, err) 108 } 109 attr = append(attr, ba) 110 } 111 112 depth++ 113 elements = append(elements, &binStartElement{ 114 line: line, 115 ns: pool.getNS(tok.Name.Space), 116 name: pool.get(tok.Name.Local), 117 attr: attr, 118 }) 119 case xml.EndElement: 120 elements = append(elements, &binEndElement{ 121 line: line, 122 ns: pool.getNS(tok.Name.Space), 123 name: pool.get(tok.Name.Local), 124 }) 125 depth-- 126 if nsEnds := namespaceEnds[depth]; len(nsEnds) > 0 { 127 delete(namespaceEnds, depth) 128 for _, nsEnd := range nsEnds { 129 elements = append(elements, nsEnd) 130 } 131 } 132 case xml.CharData: 133 // The aapt tool appears to "compact" leading and 134 // trailing whitepsace. See XMLNode::removeWhitespace in 135 // https://android.googlesource.com/platform/frameworks/base.git/+/master/tools/aapt/XMLNode.cpp 136 if len(tok) == 0 { 137 continue 138 } 139 start, end := 0, len(tok) 140 for start < len(tok) && isSpace(tok[start]) { 141 start++ 142 } 143 for end > start && isSpace(tok[end-1]) { 144 end-- 145 } 146 if start == end { 147 continue // all whitespace, skip it 148 } 149 150 // Preserve one character of whitespace. 151 if start > 0 { 152 start-- 153 } 154 if end < len(tok) { 155 end++ 156 } 157 158 elements = append(elements, &binCharData{ 159 line: line, 160 data: pool.get(string(tok[start:end])), 161 }) 162 case xml.Comment: 163 // Ignored by Anroid Binary XML format. 164 case xml.ProcInst: 165 // Ignored by Anroid Binary XML format? 166 case xml.Directive: 167 // Ignored by Anroid Binary XML format. 168 default: 169 return nil, fmt.Errorf("apk: unexpected token: %v (%T)", tok, tok) 170 } 171 } 172 173 sortPool(pool) 174 for _, e := range elements { 175 if e, ok := e.(*binStartElement); ok { 176 sortAttr(e, pool) 177 } 178 } 179 180 resMap := &binResMap{pool} 181 182 size := 8 + pool.size() + resMap.size() 183 for _, e := range elements { 184 size += e.size() 185 } 186 187 b := make([]byte, 0, size) 188 b = appendHeader(b, headerXML, size) 189 b = pool.append(b) 190 b = resMap.append(b) 191 for _, e := range elements { 192 b = e.append(b) 193 } 194 195 return b, nil 196 } 197 198 func isSpace(b byte) bool { 199 switch b { 200 case '\t', '\n', '\v', '\f', '\r', ' ', 0x85, 0xA0: 201 return true 202 } 203 return false 204 } 205 206 type headerType uint16 207 208 const ( 209 headerXML headerType = 0x0003 210 headerStringPool = 0x0001 211 headerResourceMap = 0x0180 212 headerStartNamespace = 0x0100 213 headerEndNamespace = 0x0101 214 headerStartElement = 0x0102 215 headerEndElement = 0x0103 216 headerCharData = 0x0104 217 ) 218 219 func appendU16(b []byte, v uint16) []byte { 220 return append(b, byte(v), byte(v>>8)) 221 } 222 223 func appendU32(b []byte, v uint32) []byte { 224 return append(b, byte(v), byte(v>>8), byte(v>>16), byte(v>>24)) 225 } 226 227 func appendHeader(b []byte, typ headerType, size int) []byte { 228 b = appendU16(b, uint16(typ)) 229 b = appendU16(b, 8) 230 b = appendU16(b, uint16(size)) 231 b = appendU16(b, 0) 232 return b 233 } 234 235 // Attributes of the form android:key are mapped to resource IDs, which are 236 // embedded into the Binary XML format. 237 // 238 // http://developer.android.com/reference/android/R.attr.html 239 var resourceCodes = map[string]uint32{ 240 "versionCode": 0x0101021b, 241 "versionName": 0x0101021c, 242 "minSdkVersion": 0x0101020c, 243 "windowFullscreen": 0x0101020d, 244 "label": 0x01010001, 245 "hasCode": 0x0101000c, 246 "debuggable": 0x0101000f, 247 "name": 0x01010003, 248 "configChanges": 0x0101001f, 249 "value": 0x01010024, 250 } 251 252 // http://developer.android.com/reference/android/R.attr.html#configChanges 253 var configChanges = map[string]uint32{ 254 "mcc": 0x0001, 255 "mnc": 0x0002, 256 "locale": 0x0004, 257 "touchscreen": 0x0008, 258 "keyboard": 0x0010, 259 "keyboardHidden": 0x0020, 260 "navigation": 0x0040, 261 "orientation": 0x0080, 262 "screenLayout": 0x0100, 263 "uiMode": 0x0200, 264 "screenSize": 0x0400, 265 "smallestScreenSize": 0x0800, 266 "layoutDirection": 0x2000, 267 "fontScale": 0x40000000, 268 } 269 270 type lineReader struct { 271 off int64 272 lines []int64 273 r io.Reader 274 } 275 276 func (r *lineReader) Read(p []byte) (n int, err error) { 277 n, err = r.r.Read(p) 278 for i := 0; i < n; i++ { 279 if p[i] == '\n' { 280 r.lines = append(r.lines, r.off+int64(i)) 281 } 282 } 283 r.off += int64(n) 284 return n, err 285 } 286 287 func (r *lineReader) line(pos int64) int { 288 return sort.Search(len(r.lines), func(i int) bool { 289 return pos < r.lines[i] 290 }) + 1 291 } 292 293 type bstring struct { 294 ind uint32 295 str string 296 enc []byte // 2-byte length, utf16le, 2-byte zero 297 } 298 299 type chunk interface { 300 size() int 301 append([]byte) []byte 302 } 303 304 type binResMap struct { 305 pool *binStringPool 306 } 307 308 func (p *binResMap) append(b []byte) []byte { 309 b = appendHeader(b, headerResourceMap, p.size()) 310 for _, bstr := range p.pool.s { 311 c, ok := resourceCodes[bstr.str] 312 if !ok { 313 break 314 } 315 b = appendU32(b, c) 316 } 317 return b 318 } 319 320 func (p *binResMap) size() int { 321 count := 0 322 for _, bstr := range p.pool.s { 323 if _, ok := resourceCodes[bstr.str]; !ok { 324 break 325 } 326 count++ 327 } 328 return 8 + 4*count 329 } 330 331 type binStringPool struct { 332 s []*bstring 333 m map[string]*bstring 334 } 335 336 func (p *binStringPool) get(str string) *bstring { 337 if p.m == nil { 338 p.m = make(map[string]*bstring) 339 } 340 res := p.m[str] 341 if res != nil { 342 return res 343 } 344 res = &bstring{ 345 ind: uint32(len(p.s)), 346 str: str, 347 } 348 p.s = append(p.s, res) 349 p.m[str] = res 350 351 if len(str)>>16 > 0 { 352 panic(fmt.Sprintf("string lengths over 1<<15 not yet supported, got len %d for string that starts %q", len(str), str[:100])) 353 } 354 strUTF16 := utf16.Encode([]rune(str)) 355 res.enc = appendU16(nil, uint16(len(strUTF16))) 356 for _, w := range strUTF16 { 357 res.enc = appendU16(res.enc, w) 358 } 359 res.enc = appendU16(res.enc, 0) 360 return res 361 } 362 363 func (p *binStringPool) getNS(ns string) *bstring { 364 if ns == "" { 365 // Register empty string for inclusion in output (like aapt), 366 // but do not reference it from namespace elements. 367 p.get("") 368 return nil 369 } 370 return p.get(ns) 371 } 372 373 func (p *binStringPool) getAttr(attr xml.Attr) (*binAttr, error) { 374 a := &binAttr{ 375 ns: p.getNS(attr.Name.Space), 376 name: p.get(attr.Name.Local), 377 } 378 if attr.Name.Space != "http://schemas.android.com/apk/res/android" { 379 a.data = p.get(attr.Value) 380 return a, nil 381 } 382 383 // Some android attributes have interesting values. 384 switch attr.Name.Local { 385 case "versionCode", "minSdkVersion": 386 v, err := strconv.Atoi(attr.Value) 387 if err != nil { 388 return nil, err 389 } 390 a.data = int(v) 391 case "hasCode", "debuggable": 392 v, err := strconv.ParseBool(attr.Value) 393 if err != nil { 394 return nil, err 395 } 396 a.data = v 397 case "configChanges": 398 v := uint32(0) 399 for _, c := range strings.Split(attr.Value, "|") { 400 v |= configChanges[c] 401 } 402 a.data = v 403 default: 404 a.data = p.get(attr.Value) 405 } 406 return a, nil 407 } 408 409 const stringPoolPreamble = 0 + 410 8 + // chunk header 411 4 + // string count 412 4 + // style count 413 4 + // flags 414 4 + // strings start 415 4 + // styles start 416 0 417 418 func (p *binStringPool) unpaddedSize() int { 419 strLens := 0 420 for _, s := range p.s { 421 strLens += len(s.enc) 422 } 423 return stringPoolPreamble + 4*len(p.s) + strLens 424 } 425 426 func (p *binStringPool) size() int { 427 size := p.unpaddedSize() 428 size += size % 0x04 429 return size 430 } 431 432 // overloaded for testing. 433 var ( 434 sortPool = func(p *binStringPool) { 435 sort.Sort(p) 436 437 // Move resourceCodes to the front. 438 s := make([]*bstring, 0) 439 m := make(map[string]*bstring) 440 for str := range resourceCodes { 441 bstr := p.m[str] 442 if bstr == nil { 443 continue 444 } 445 bstr.ind = uint32(len(s)) 446 s = append(s, bstr) 447 m[str] = bstr 448 delete(p.m, str) 449 } 450 for _, bstr := range p.m { 451 bstr.ind = uint32(len(s)) 452 s = append(s, bstr) 453 } 454 p.s = s 455 p.m = m 456 } 457 sortAttr = func(e *binStartElement, p *binStringPool) {} 458 ) 459 460 func (b *binStringPool) Len() int { return len(b.s) } 461 func (b *binStringPool) Less(i, j int) bool { return b.s[i].str < b.s[j].str } 462 func (b *binStringPool) Swap(i, j int) { 463 b.s[i], b.s[j] = b.s[j], b.s[i] 464 b.s[i].ind, b.s[j].ind = b.s[j].ind, b.s[i].ind 465 } 466 467 func (p *binStringPool) append(b []byte) []byte { 468 stringsStart := uint32(stringPoolPreamble + 4*len(p.s)) 469 b = appendU16(b, uint16(headerStringPool)) 470 b = appendU16(b, 0x1c) // chunk header size 471 b = appendU16(b, uint16(p.size())) 472 b = appendU16(b, 0) 473 b = appendU32(b, uint32(len(p.s))) 474 b = appendU32(b, 0) // style count 475 b = appendU32(b, 0) // flags 476 b = appendU32(b, stringsStart) 477 b = appendU32(b, 0) // styles start 478 479 off := 0 480 for _, bstr := range p.s { 481 b = appendU32(b, uint32(off)) 482 off += len(bstr.enc) 483 } 484 for _, bstr := range p.s { 485 b = append(b, bstr.enc...) 486 } 487 488 for i := p.unpaddedSize() % 0x04; i > 0; i-- { 489 b = append(b, 0) 490 } 491 return b 492 } 493 494 type binStartElement struct { 495 line int 496 ns *bstring 497 name *bstring 498 attr []*binAttr 499 } 500 501 func (e *binStartElement) size() int { 502 return 8 + // chunk header 503 4 + // line number 504 4 + // comment 505 4 + // ns 506 4 + // name 507 2 + 2 + 2 + // attribute start, size, count 508 2 + 2 + 2 + // id/class/style index 509 len(e.attr)*(4+4+4+4+4) 510 } 511 512 func (e *binStartElement) append(b []byte) []byte { 513 b = appendU16(b, uint16(headerStartElement)) 514 b = appendU16(b, 0x10) // chunk header size 515 b = appendU16(b, uint16(e.size())) 516 b = appendU16(b, 0) 517 b = appendU32(b, uint32(e.line)) 518 b = appendU32(b, 0xffffffff) // comment 519 if e.ns == nil { 520 b = appendU32(b, 0xffffffff) 521 } else { 522 b = appendU32(b, e.ns.ind) 523 } 524 b = appendU32(b, e.name.ind) 525 b = appendU16(b, 0x14) // attribute start 526 b = appendU16(b, 0x14) // attribute size 527 b = appendU16(b, uint16(len(e.attr))) 528 b = appendU16(b, 0) // ID index (none) 529 b = appendU16(b, 0) // class index (none) 530 b = appendU16(b, 0) // style index (none) 531 for _, a := range e.attr { 532 b = a.append(b) 533 } 534 return b 535 } 536 537 type binAttr struct { 538 ns *bstring 539 name *bstring 540 data interface{} // either int (INT_DEC) or *bstring (STRING) 541 } 542 543 func (a *binAttr) append(b []byte) []byte { 544 if a.ns != nil { 545 b = appendU32(b, a.ns.ind) 546 } else { 547 b = appendU32(b, 0xffffffff) 548 } 549 b = appendU32(b, a.name.ind) 550 switch v := a.data.(type) { 551 case int: 552 b = appendU32(b, 0xffffffff) // raw value 553 b = appendU16(b, 8) // size 554 b = append(b, 0) // unused padding 555 b = append(b, 0x10) // INT_DEC 556 b = appendU32(b, uint32(v)) 557 case bool: 558 b = appendU32(b, 0xffffffff) // raw value 559 b = appendU16(b, 8) // size 560 b = append(b, 0) // unused padding 561 b = append(b, 0x12) // INT_BOOLEAN 562 if v { 563 b = appendU32(b, 0xffffffff) 564 } else { 565 b = appendU32(b, 0) 566 } 567 case uint32: 568 b = appendU32(b, 0xffffffff) // raw value 569 b = appendU16(b, 8) // size 570 b = append(b, 0) // unused padding 571 b = append(b, 0x11) // INT_HEX 572 b = appendU32(b, uint32(v)) 573 case *bstring: 574 b = appendU32(b, v.ind) // raw value 575 b = appendU16(b, 8) // size 576 b = append(b, 0) // unused padding 577 b = append(b, 0x03) // STRING 578 b = appendU32(b, v.ind) 579 default: 580 panic(fmt.Sprintf("unexpected attr type: %T (%v)", v, v)) 581 } 582 return b 583 } 584 585 type binEndElement struct { 586 line int 587 ns *bstring 588 name *bstring 589 attr []*binAttr 590 } 591 592 func (*binEndElement) size() int { 593 return 8 + // chunk header 594 4 + // line number 595 4 + // comment 596 4 + // ns 597 4 // name 598 } 599 600 func (e *binEndElement) append(b []byte) []byte { 601 b = appendU16(b, uint16(headerEndElement)) 602 b = appendU16(b, 0x10) // chunk header size 603 b = appendU16(b, uint16(e.size())) 604 b = appendU16(b, 0) 605 b = appendU32(b, uint32(e.line)) 606 b = appendU32(b, 0xffffffff) // comment 607 if e.ns == nil { 608 b = appendU32(b, 0xffffffff) 609 } else { 610 b = appendU32(b, e.ns.ind) 611 } 612 b = appendU32(b, e.name.ind) 613 return b 614 } 615 616 type binStartNamespace struct { 617 line int 618 prefix *bstring 619 url *bstring 620 } 621 622 func (binStartNamespace) size() int { 623 return 8 + // chunk header 624 4 + // line number 625 4 + // comment 626 4 + // prefix 627 4 // url 628 } 629 630 func (e binStartNamespace) append(b []byte) []byte { 631 b = appendU16(b, uint16(headerStartNamespace)) 632 b = appendU16(b, 0x10) // chunk header size 633 b = appendU16(b, uint16(e.size())) 634 b = appendU16(b, 0) 635 b = appendU32(b, uint32(e.line)) 636 b = appendU32(b, 0xffffffff) // comment 637 b = appendU32(b, e.prefix.ind) 638 b = appendU32(b, e.url.ind) 639 return b 640 } 641 642 type binEndNamespace struct { 643 line int 644 prefix *bstring 645 url *bstring 646 } 647 648 func (binEndNamespace) size() int { 649 return 8 + // chunk header 650 4 + // line number 651 4 + // comment 652 4 + // prefix 653 4 // url 654 } 655 656 func (e binEndNamespace) append(b []byte) []byte { 657 b = appendU16(b, uint16(headerEndNamespace)) 658 b = appendU16(b, 0x10) // chunk header size 659 b = appendU16(b, uint16(e.size())) 660 b = appendU16(b, 0) 661 b = appendU32(b, uint32(e.line)) 662 b = appendU32(b, 0xffffffff) // comment 663 b = appendU32(b, e.prefix.ind) 664 b = appendU32(b, e.url.ind) 665 return b 666 } 667 668 type binCharData struct { 669 line int 670 data *bstring 671 } 672 673 func (*binCharData) size() int { 674 return 8 + // chunk header 675 4 + // line number 676 4 + // comment 677 4 + // data 678 8 // junk 679 } 680 681 func (e *binCharData) append(b []byte) []byte { 682 b = appendU16(b, uint16(headerCharData)) 683 b = appendU16(b, 0x10) // chunk header size 684 b = appendU16(b, 0x1c) // size 685 b = appendU16(b, 0) 686 b = appendU32(b, uint32(e.line)) 687 b = appendU32(b, 0xffffffff) // comment 688 b = appendU32(b, e.data.ind) 689 b = appendU16(b, 0x08) 690 b = appendU16(b, 0) 691 b = appendU16(b, 0) 692 b = appendU16(b, 0) 693 return b 694 }