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  }