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