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  }