github.com/ergo-services/ergo@v1.999.224/etf/encode.go (about)

     1  package etf
     2  
     3  import (
     4  	"encoding/binary"
     5  	"fmt"
     6  	"math"
     7  	"math/big"
     8  	"reflect"
     9  
    10  	"github.com/ergo-services/ergo/lib"
    11  )
    12  
    13  var (
    14  	ErrStringTooLong = fmt.Errorf("Encoding error. String too long. Max allowed length is 65535")
    15  	ErrAtomTooLong   = fmt.Errorf("Encoding error. Atom too long. Max allowed UTF-8 chars is 255")
    16  
    17  	// internal types
    18  	goSlice            = byte(240)
    19  	goMap              = byte(241)
    20  	goStruct           = byte(242)
    21  	goSliceRegistered  = byte(243)
    22  	goMapRegistered    = byte(244)
    23  	goStructRegistered = byte(245)
    24  
    25  	marshalerType = reflect.TypeOf((*Marshaler)(nil)).Elem()
    26  )
    27  
    28  // EncodeOptions
    29  type EncodeOptions struct {
    30  	AtomCache         *AtomCacheOut
    31  	SenderAtomCache   map[Atom]CacheItem
    32  	EncodingAtomCache *EncodingAtomCache
    33  	AtomMapping       *AtomMapping
    34  
    35  	// FlagBigPidRef The node accepts a larger amount of data in pids
    36  	// and references (node container types version 4).
    37  	// In the pid case full 32-bit ID and Serial fields in NEW_PID_EXT
    38  	// and in the reference case up to 5 32-bit ID words are now
    39  	// accepted in NEWER_REFERENCE_EXT. Introduced in OTP 24.
    40  	FlagBigPidRef bool
    41  
    42  	// FlagBigCreation The node understands big node creation tags NEW_PID_EXT,
    43  	// NEWER_REFERENCE_EXT.
    44  	FlagBigCreation bool
    45  
    46  	NodeName string
    47  	PeerName string
    48  }
    49  
    50  // Encode
    51  func Encode(term Term, b *lib.Buffer, options EncodeOptions) (retErr error) {
    52  	if lib.CatchPanic() {
    53  		defer func() {
    54  			// We should catch any panic happened during encoding Golang types.
    55  			if r := recover(); r != nil {
    56  				retErr = fmt.Errorf("%v", r)
    57  			}
    58  		}()
    59  	}
    60  	var stack, child *stackElement
    61  
    62  	cacheEnabled := options.AtomCache != nil
    63  	cacheIndex := int16(0)
    64  	if cacheEnabled {
    65  		cacheIndex = int16(len(options.EncodingAtomCache.L))
    66  	}
    67  
    68  	// Atom cache: (if its enabled: options.AtomCache != nil)
    69  	// 1. check for an atom in options.WriterAtomCache (map)
    70  	// 2. if not found in WriterAtomCache call AtomCache.Append(atom),
    71  	//    encode it as a regular atom (ettAtom*)
    72  	// 3. if found
    73  	//    add options.EncodingAtomCache[i] = CacheItem, where i is just a counter
    74  	//    within this encoding process.
    75  
    76  	//    encode atom as ettCacheRef with value = i
    77  	for {
    78  
    79  		child = nil
    80  
    81  		if stack != nil {
    82  
    83  			if stack.i == stack.children {
    84  				if stack.parent == nil {
    85  					return nil
    86  				}
    87  				stack, stack.parent = stack.parent, nil
    88  				continue
    89  			}
    90  
    91  			switch stack.termType {
    92  			case ettList:
    93  				if stack.i == stack.children-1 {
    94  					// last item of list should be ettNil
    95  					term = nil
    96  					break
    97  				}
    98  				term = stack.term.(List)[stack.i]
    99  			case ettListImproper:
   100  				// improper list like [a|b] has no ettNil as a last item
   101  				term = stack.term.(ListImproper)[stack.i]
   102  
   103  			case ettSmallTuple:
   104  				term = stack.term.(Tuple)[stack.i]
   105  
   106  			case ettPid:
   107  				p := stack.term.(Pid)
   108  				if stack.i == 0 {
   109  					term = p.Node
   110  					break
   111  				}
   112  
   113  				buf := b.Extend(9)
   114  
   115  				// ID a 32-bit big endian unsigned integer.
   116  				// If FlagBigPidRef is not set, only 15 bits may be used
   117  				// and the rest must be 0.
   118  				if options.FlagBigPidRef {
   119  					binary.BigEndian.PutUint32(buf[:4], uint32(p.ID))
   120  				} else {
   121  					// 15 bits only 2**15 - 1 = 32767
   122  					binary.BigEndian.PutUint32(buf[:4], uint32(p.ID)&32767)
   123  				}
   124  
   125  				// Serial a 32-bit big endian unsigned integer.
   126  				// If distribution FlagBigPidRef is not set, only 13 bits may be used
   127  				// and the rest must be 0.
   128  				if options.FlagBigPidRef {
   129  					binary.BigEndian.PutUint32(buf[4:8], uint32(p.ID>>32))
   130  				} else {
   131  					// 13 bits only 2**13 - 1 = 8191
   132  					binary.BigEndian.PutUint32(buf[4:8], (uint32(p.ID>>15) & 8191))
   133  				}
   134  
   135  				// Same as NEW_PID_EXT except the Creation field is
   136  				// only one byte and only two bits are significant,
   137  				// the rest are to be 0.
   138  				buf[8] = byte(p.Creation) & 3
   139  
   140  				stack.i++
   141  				continue
   142  
   143  			case ettNewPid:
   144  				p := stack.term.(Pid)
   145  				if stack.i == 0 {
   146  					term = p.Node
   147  					break
   148  				}
   149  
   150  				buf := b.Extend(12)
   151  				// ID
   152  				if options.FlagBigPidRef {
   153  					binary.BigEndian.PutUint32(buf[:4], uint32(p.ID))
   154  				} else {
   155  					// 15 bits only 2**15 - 1 = 32767
   156  					binary.BigEndian.PutUint32(buf[:4], uint32(p.ID)&32767)
   157  				}
   158  				// Serial
   159  				if options.FlagBigPidRef {
   160  					binary.BigEndian.PutUint32(buf[4:8], uint32(p.ID>>32))
   161  				} else {
   162  					// 13 bits only 2**13 - 1 = 8191
   163  					binary.BigEndian.PutUint32(buf[4:8], (uint32(p.ID>>32))&8191)
   164  				}
   165  				// Creation
   166  				binary.BigEndian.PutUint32(buf[8:12], p.Creation)
   167  
   168  				stack.i++
   169  				continue
   170  
   171  			case ettNewRef:
   172  				r := stack.term.(Ref)
   173  				if stack.i == 0 {
   174  					term = stack.term.(Ref).Node
   175  					break
   176  				}
   177  
   178  				lenID := 3
   179  				buf := b.Extend(1 + lenID*4)
   180  				// Only one byte long and only two bits are significant, the rest must be 0.
   181  				buf[0] = byte(r.Creation & 3)
   182  				buf = buf[1:]
   183  				for i := 0; i < lenID; i++ {
   184  					// In the first word (4 bytes) of ID, only 18 bits
   185  					// are significant, the rest must be 0.
   186  					if i == 0 {
   187  						// 2**18 - 1 = 262143
   188  						binary.BigEndian.PutUint32(buf[:4], r.ID[i]&262143)
   189  					} else {
   190  						binary.BigEndian.PutUint32(buf[:4], r.ID[i])
   191  					}
   192  					buf = buf[4:]
   193  				}
   194  
   195  				stack.i++
   196  				continue
   197  
   198  			case ettNewerRef:
   199  				r := stack.term.(Ref)
   200  				if stack.i == 0 {
   201  					term = stack.term.(Ref).Node
   202  					break
   203  				}
   204  
   205  				// // FIXME Erlang 24 has a bug https://github.com/erlang/otp/issues/5097
   206  				// uncomment once they fix it
   207  				lenID := 3
   208  				//if options.FlagBigPidRef {
   209  				//	lenID = 5
   210  				//}
   211  				buf := b.Extend(4 + lenID*4)
   212  				binary.BigEndian.PutUint32(buf[0:4], r.Creation)
   213  				buf = buf[4:]
   214  				for i := 0; i < lenID; i++ {
   215  					binary.BigEndian.PutUint32(buf[:4], r.ID[i])
   216  					buf = buf[4:]
   217  				}
   218  
   219  				stack.i++
   220  				continue
   221  
   222  			case ettMap:
   223  				key := stack.tmp.(List)[stack.i/2]
   224  				if stack.i&0x01 == 0x01 { // a value
   225  					term = stack.term.(Map)[key]
   226  					break
   227  				}
   228  				term = key
   229  
   230  			case goMapRegistered:
   231  				if stack.i == 0 { // registered type name as a key
   232  					term = stack.tmp
   233  					break
   234  				}
   235  				if stack.i == 1 { // nil as a value for the key (registered type name)
   236  					term = nil
   237  					break
   238  				}
   239  				key := stack.term.([]reflect.Value)[(stack.i-2)/2]
   240  				if stack.i&0x01 == 0x01 { // a value
   241  					term = stack.reg.MapIndex(key).Interface()
   242  					break
   243  				}
   244  				term = key.Interface() // a key
   245  
   246  			case goMap:
   247  				key := stack.tmp.([]reflect.Value)[stack.i/2]
   248  				if stack.i&0x01 == 0x01 { // a value
   249  					term = stack.term.(func(reflect.Value) reflect.Value)(key).Interface()
   250  					break
   251  				}
   252  				term = key.Interface() // a key
   253  
   254  			case goSliceRegistered:
   255  				if stack.i == 0 {
   256  					term = stack.tmp
   257  					break
   258  				}
   259  				if stack.i == stack.children-1 {
   260  					// last item of list should be ettNil
   261  					term = nil
   262  					break
   263  				}
   264  				term = stack.term.(func(int) reflect.Value)(stack.i - 1).Interface()
   265  
   266  			case goSlice:
   267  				if stack.i == stack.children-1 {
   268  					// last item of list should be ettNil
   269  					term = nil
   270  					break
   271  				}
   272  				term = stack.term.(func(int) reflect.Value)(stack.i).Interface()
   273  
   274  			case goStructRegistered:
   275  				if stack.i == 0 {
   276  					// first item must be a sturct name (stored in stack.tmp).
   277  					term = stack.tmp
   278  					break
   279  				}
   280  				// field value
   281  				term = stack.term.(func(int) reflect.Value)(stack.i - 1).Interface()
   282  
   283  			case goStruct:
   284  				field := stack.tmp.(func(int) reflect.StructField)(stack.i / 2)
   285  				fieldName := field.Name
   286  
   287  				if tag := field.Tag.Get("etf"); tag != "" {
   288  					fieldName = tag
   289  				}
   290  
   291  				if stack.i&0x01 != 0x01 { // a key (field name)
   292  					term = Atom(fieldName)
   293  					break
   294  				}
   295  
   296  				// a value
   297  				fvalue := stack.term.(func(int) reflect.Value)(stack.i / 2)
   298  				if fvalue.CanInterface() == false {
   299  					return fmt.Errorf("struct has unexported field %q", fieldName)
   300  				}
   301  				term = fvalue.Interface()
   302  
   303  			default:
   304  
   305  				return errInternal
   306  			}
   307  
   308  			stack.i++
   309  		}
   310  
   311  	recasting:
   312  		switch t := term.(type) {
   313  		case bool:
   314  
   315  			if cacheEnabled && cacheIndex < 255 {
   316  				value := Atom("false")
   317  				if t {
   318  					value = Atom("true")
   319  				}
   320  
   321  				// looking for CacheItem
   322  				ci, found := options.SenderAtomCache[value]
   323  				if found {
   324  					i := options.EncodingAtomCache.Append(ci)
   325  					cacheIndex = int16(i + 1)
   326  					b.Append([]byte{ettCacheRef, byte(i)})
   327  					break
   328  				} else {
   329  					// add it to the cache and encode as usual Atom
   330  					options.AtomCache.Append(value)
   331  				}
   332  			}
   333  
   334  			if t {
   335  				b.Append([]byte{ettSmallAtom, 4, 't', 'r', 'u', 'e'})
   336  				break
   337  			}
   338  
   339  			b.Append([]byte{ettSmallAtom, 5, 'f', 'a', 'l', 's', 'e'})
   340  
   341  		// do not use reflect.ValueOf(t) because its too expensive
   342  		case uint8:
   343  			b.Append([]byte{ettSmallInteger, t})
   344  
   345  		case int8:
   346  			if t < 0 {
   347  				term = int32(t)
   348  				goto recasting
   349  			}
   350  
   351  			b.Append([]byte{ettSmallInteger, uint8(t)})
   352  			break
   353  
   354  		case uint16:
   355  			if t <= math.MaxUint8 {
   356  				b.Append([]byte{ettSmallInteger, byte(t)})
   357  				break
   358  			}
   359  			term = int32(t)
   360  			goto recasting
   361  
   362  		case int16:
   363  			if t >= 0 && t <= math.MaxUint8 {
   364  				b.Append([]byte{ettSmallInteger, byte(t)})
   365  				break
   366  			}
   367  
   368  			term = int32(t)
   369  			goto recasting
   370  
   371  		case uint32:
   372  			if t <= math.MaxUint8 {
   373  				b.Append([]byte{ettSmallInteger, byte(t)})
   374  				break
   375  			}
   376  
   377  			if t > math.MaxInt32 {
   378  				term = int64(t)
   379  				goto recasting
   380  			}
   381  
   382  			term = int32(t)
   383  			goto recasting
   384  
   385  		case int32:
   386  			if t >= 0 && t <= math.MaxUint8 {
   387  				b.Append([]byte{ettSmallInteger, byte(t)})
   388  				break
   389  			}
   390  
   391  			// 1 (ettInteger) + 4 (32bit integer)
   392  			buf := b.Extend(1 + 4)
   393  			buf[0] = ettInteger
   394  			binary.BigEndian.PutUint32(buf[1:5], uint32(t))
   395  
   396  		case uint:
   397  			if t <= math.MaxUint8 {
   398  				b.Append([]byte{ettSmallInteger, byte(t)})
   399  				break
   400  			}
   401  
   402  			if t > math.MaxInt32 {
   403  				term = int64(t)
   404  				goto recasting
   405  			}
   406  
   407  			term = int32(t)
   408  			goto recasting
   409  
   410  		case int:
   411  			if t >= 0 && t <= math.MaxUint8 {
   412  				b.Append([]byte{ettSmallInteger, byte(t)})
   413  				break
   414  			}
   415  
   416  			if t > math.MaxInt32 || t < math.MinInt32 {
   417  				term = int64(t)
   418  				goto recasting
   419  			}
   420  
   421  			term = int32(t)
   422  			goto recasting
   423  
   424  		case uint64:
   425  			if t <= math.MaxUint8 {
   426  				b.Append([]byte{ettSmallInteger, byte(t)})
   427  				break
   428  			}
   429  
   430  			if t <= math.MaxInt32 {
   431  				term = int32(t)
   432  				goto recasting
   433  			}
   434  
   435  			if t <= math.MaxInt64 {
   436  				term = int64(t)
   437  				goto recasting
   438  			}
   439  
   440  			buf := []byte{ettSmallBig, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0}
   441  			binary.LittleEndian.PutUint64(buf[3:], uint64(t))
   442  			b.Append(buf)
   443  
   444  		case int64:
   445  			if t >= 0 && t <= math.MaxUint8 {
   446  				b.Append([]byte{ettSmallInteger, byte(t)})
   447  				break
   448  			}
   449  
   450  			if t >= math.MinInt32 && t <= math.MaxInt32 {
   451  				term = int32(t)
   452  				goto recasting
   453  			}
   454  
   455  			if t == math.MinInt64 {
   456  				// corner case:
   457  				// if t = -9223372036854775808 (which is math.MinInt64)
   458  				// we can't just revert the sign because it overflows math.MaxInt64 value
   459  				buf := []byte{ettSmallBig, 8, 1, 0, 0, 0, 0, 0, 0, 0, 128}
   460  				b.Append(buf)
   461  				break
   462  			}
   463  
   464  			negative := byte(0)
   465  			if t < 0 {
   466  				negative = 1
   467  				t = -t
   468  			}
   469  
   470  			buf := []byte{ettSmallBig, 0, negative, 0, 0, 0, 0, 0, 0, 0, 0}
   471  			binary.LittleEndian.PutUint64(buf[3:], uint64(t))
   472  			switch {
   473  			case t < 4294967296:
   474  				buf[1] = 4
   475  				b.Append(buf[:7])
   476  
   477  			case t < 1099511627776:
   478  				buf[1] = 5
   479  				b.Append(buf[:8])
   480  
   481  			case t < 281474976710656:
   482  				buf[1] = 6
   483  				b.Append(buf[:9])
   484  
   485  			case t < 72057594037927936:
   486  				buf[1] = 7
   487  				b.Append(buf[:10])
   488  
   489  			default:
   490  				buf[1] = 8
   491  				b.Append(buf)
   492  			}
   493  
   494  		case big.Int:
   495  			bytes := t.Bytes()
   496  			negative := t.Sign() < 0
   497  			l := len(bytes)
   498  
   499  			for i := 0; i < l/2; i++ {
   500  				bytes[i], bytes[l-1-i] = bytes[l-1-i], bytes[i]
   501  			}
   502  
   503  			if l < 256 {
   504  				// 1 (ettSmallBig) + 1 (len) + 1 (sign) + bytes
   505  				buf := b.Extend(1 + 1 + 1 + l)
   506  				buf[0] = ettSmallBig
   507  				buf[1] = byte(l)
   508  
   509  				if negative {
   510  					buf[2] = 1
   511  				} else {
   512  					buf[2] = 0
   513  				}
   514  
   515  				copy(buf[3:], bytes)
   516  
   517  				break
   518  			}
   519  
   520  			// 1 (ettLargeBig) + 4 (len) + 1(sign) + bytes
   521  			buf := b.Extend(1 + 4 + 1 + l)
   522  			buf[0] = ettLargeBig
   523  			binary.BigEndian.PutUint32(buf[1:5], uint32(l))
   524  
   525  			if negative {
   526  				buf[5] = 1
   527  			} else {
   528  				buf[5] = 0
   529  			}
   530  
   531  			copy(buf[6:], bytes)
   532  
   533  		case string:
   534  			lenString := len(t)
   535  
   536  			if lenString > 65535 {
   537  				return ErrStringTooLong
   538  			}
   539  
   540  			// 1 (ettString) + 2 (len) + string
   541  			buf := b.Extend(1 + 2 + lenString)
   542  			buf[0] = ettString
   543  			binary.BigEndian.PutUint16(buf[1:3], uint16(lenString))
   544  			copy(buf[3:], t)
   545  
   546  		case Charlist:
   547  			term = []rune(t)
   548  			goto recasting
   549  
   550  		case String:
   551  			term = []byte(t)
   552  			goto recasting
   553  
   554  		case Atom:
   555  			// As from ERTS 9.0 (OTP 20), atoms may contain any Unicode
   556  			// characters and are always encoded using the UTF-8 external
   557  			// formats ATOM_UTF8_EXT or SMALL_ATOM_UTF8_EXT.
   558  
   559  			// replace atom value if we have mapped value for it
   560  			if options.AtomMapping != nil {
   561  				options.AtomMapping.MutexOut.RLock()
   562  				if mapped, ok := options.AtomMapping.Out[t]; ok {
   563  					t = mapped
   564  				}
   565  				options.AtomMapping.MutexOut.RUnlock()
   566  			}
   567  
   568  			// https://erlang.org/doc/apps/erts/erl_ext_dist.html#utf8_atoms
   569  			// The maximum number of allowed characters in an atom is 255.
   570  			// In the UTF-8 case, each character can need 4 bytes to be encoded.
   571  			if len([]rune(t)) > 255 {
   572  				return ErrAtomTooLong
   573  			}
   574  
   575  			if cacheEnabled && cacheIndex < 255 {
   576  				// looking for CacheItem
   577  				ci, found := options.SenderAtomCache[t]
   578  				if found {
   579  					i := options.EncodingAtomCache.Append(ci)
   580  					cacheIndex = int16(i + 1)
   581  					b.Append([]byte{ettCacheRef, byte(i)})
   582  					break
   583  				} else {
   584  					// add it to the cache and encode as usual Atom
   585  					options.AtomCache.Append(t)
   586  				}
   587  			}
   588  
   589  			lenAtom := len(t)
   590  			if lenAtom < 256 {
   591  				buf := b.Extend(1 + 1 + lenAtom)
   592  				buf[0] = ettSmallAtomUTF8
   593  				buf[1] = byte(lenAtom)
   594  				copy(buf[2:], t)
   595  				break
   596  			}
   597  
   598  			// 1 (ettAtomUTF8) + 2 (len) + atom
   599  			buf := b.Extend(1 + 2 + lenAtom)
   600  			buf[0] = ettAtomUTF8
   601  			binary.BigEndian.PutUint16(buf[1:3], uint16(lenAtom))
   602  			copy(buf[3:], t)
   603  
   604  		case float32:
   605  			term = float64(t)
   606  			goto recasting
   607  
   608  		case float64:
   609  			// 1 (ettNewFloat) + 8 (float)
   610  			buf := b.Extend(1 + 8)
   611  			buf[0] = ettNewFloat
   612  			bits := math.Float64bits(t)
   613  			binary.BigEndian.PutUint64(buf[1:9], uint64(bits))
   614  
   615  		case nil:
   616  			b.AppendByte(ettNil)
   617  
   618  		case Tuple:
   619  			lenTuple := len(t)
   620  			if lenTuple < 256 {
   621  				b.Append([]byte{ettSmallTuple, byte(lenTuple)})
   622  			} else {
   623  				buf := b.Extend(5)
   624  				buf[0] = ettLargeTuple
   625  				binary.BigEndian.PutUint32(buf[1:5], uint32(lenTuple))
   626  			}
   627  			child = &stackElement{
   628  				parent:   stack,
   629  				termType: ettSmallTuple, // doesn't matter what exact type for the further processing
   630  				term:     t,
   631  				children: lenTuple,
   632  			}
   633  
   634  		case Pid:
   635  			child = &stackElement{
   636  				parent:   stack,
   637  				term:     t,
   638  				children: 2,
   639  			}
   640  			if options.FlagBigCreation {
   641  				child.termType = ettNewPid
   642  				b.AppendByte(ettNewPid)
   643  			} else {
   644  				child.termType = ettPid
   645  				b.AppendByte(ettPid)
   646  			}
   647  
   648  		case Alias:
   649  			term = Ref(t)
   650  			goto recasting
   651  
   652  		case Ref:
   653  			buf := b.Extend(3)
   654  
   655  			child = &stackElement{
   656  				parent:   stack,
   657  				term:     t,
   658  				children: 2,
   659  			}
   660  			if options.FlagBigCreation {
   661  				buf[0] = ettNewerRef
   662  				child.termType = ettNewerRef
   663  
   664  			} else {
   665  				buf[0] = ettNewRef
   666  				child.termType = ettNewRef
   667  			}
   668  
   669  			// LEN a 16-bit big endian unsigned integer not larger
   670  			// than 5 when the FlagBigPidRef has been set; otherwise not larger than 3.
   671  
   672  			// FIXME Erlang 24 has a bug https://github.com/erlang/otp/issues/5097
   673  			// uncomment once they fix it
   674  			//if options.FlagBigPidRef {
   675  			//	binary.BigEndian.PutUint16(buf[1:3], 5)
   676  			//} else {
   677  			binary.BigEndian.PutUint16(buf[1:3], 3)
   678  			//}
   679  
   680  		case Map:
   681  			lenMap := len(t)
   682  			buf := b.Extend(5)
   683  			buf[0] = ettMap
   684  			binary.BigEndian.PutUint32(buf[1:], uint32(lenMap))
   685  
   686  			keys := make(List, 0, lenMap)
   687  			for key := range t {
   688  				keys = append(keys, key)
   689  			}
   690  
   691  			child = &stackElement{
   692  				parent:   stack,
   693  				termType: ettMap,
   694  				term:     t,
   695  				children: lenMap * 2,
   696  				tmp:      keys,
   697  			}
   698  
   699  		case ListImproper:
   700  			if len(t) == 0 {
   701  				b.AppendByte(ettNil)
   702  				continue
   703  			}
   704  			lenList := len(t) - 1
   705  			buf := b.Extend(5)
   706  			buf[0] = ettList
   707  			binary.BigEndian.PutUint32(buf[1:], uint32(lenList))
   708  			child = &stackElement{
   709  				parent:   stack,
   710  				termType: ettListImproper,
   711  				term:     t,
   712  				children: lenList + 1,
   713  			}
   714  
   715  		case List:
   716  			lenList := len(t)
   717  			if lenList == 0 {
   718  				b.AppendByte(ettNil)
   719  				continue
   720  			}
   721  			buf := b.Extend(5)
   722  			buf[0] = ettList
   723  			binary.BigEndian.PutUint32(buf[1:], uint32(lenList))
   724  			child = &stackElement{
   725  				parent:   stack,
   726  				termType: ettList,
   727  				term:     t,
   728  				children: lenList + 1,
   729  			}
   730  
   731  		case []byte:
   732  			lenBinary := len(t)
   733  			buf := b.Extend(1 + 4 + lenBinary)
   734  			buf[0] = ettBinary
   735  			binary.BigEndian.PutUint32(buf[1:5], uint32(lenBinary))
   736  			copy(buf[5:], t)
   737  
   738  		case Marshaler:
   739  			m, err := t.MarshalETF()
   740  			if err != nil {
   741  				return err
   742  			}
   743  
   744  			lenBinary := len(m)
   745  			buf := b.Extend(1 + 4 + lenBinary)
   746  			buf[0] = ettBinary
   747  			binary.BigEndian.PutUint32(buf[1:5], uint32(lenBinary))
   748  			copy(buf[5:], m)
   749  
   750  		default:
   751  			v := reflect.ValueOf(t)
   752  			vt := reflect.TypeOf(t)
   753  			vtAtomName := regTypeName(vt)
   754  			registered.RLock()
   755  			rtype, typeIsRegistered := registered.typesEnc[vtAtomName]
   756  			registered.RUnlock()
   757  
   758  			switch v.Kind() {
   759  			case reflect.Struct:
   760  				lenStruct := v.NumField()
   761  				if typeIsRegistered {
   762  					// registered type. encode as a tuple with vtAtomName as the first element
   763  					vtAtomName = rtype.name
   764  					if lenStruct+1 < 255 {
   765  						b.Append([]byte{ettSmallTuple, byte(lenStruct + 1)})
   766  					} else {
   767  						buf := b.Extend(5)
   768  						buf[0] = ettLargeTuple
   769  						binary.BigEndian.PutUint32(buf[1:], uint32(lenStruct+1))
   770  					}
   771  					child = &stackElement{
   772  						parent:   stack,
   773  						termType: goStructRegistered,
   774  						term:     v.Field,
   775  						children: lenStruct + 1,
   776  						tmp:      vtAtomName,
   777  					}
   778  					break
   779  				}
   780  
   781  				// will be encoded as a ettMap
   782  				buf := b.Extend(5)
   783  				buf[0] = ettMap
   784  				binary.BigEndian.PutUint32(buf[1:], uint32(lenStruct))
   785  
   786  				child = &stackElement{
   787  					parent:   stack,
   788  					termType: goStruct,
   789  					term:     v.Field,
   790  					children: lenStruct * 2,
   791  					tmp:      v.Type().Field,
   792  				}
   793  
   794  			case reflect.Array, reflect.Slice:
   795  				lenList := v.Len()
   796  
   797  				if typeIsRegistered {
   798  					vtAtomName = rtype.name
   799  					lenList++ // first element for the type name
   800  					buf := b.Extend(5)
   801  					buf[0] = ettList
   802  					binary.BigEndian.PutUint32(buf[1:], uint32(lenList))
   803  					child = &stackElement{
   804  						parent:   stack,
   805  						termType: goSliceRegistered,
   806  						term:     v.Index,
   807  						children: lenList + 1,
   808  						tmp:      vtAtomName,
   809  					}
   810  					break
   811  				}
   812  
   813  				if lenList == 0 {
   814  					b.AppendByte(ettNil)
   815  					continue
   816  				}
   817  
   818  				buf := b.Extend(5)
   819  				buf[0] = ettList
   820  				binary.BigEndian.PutUint32(buf[1:], uint32(lenList))
   821  				child = &stackElement{
   822  					parent:   stack,
   823  					termType: goSlice,
   824  					term:     v.Index,
   825  					children: lenList + 1,
   826  				}
   827  
   828  			case reflect.Map:
   829  				lenMap := v.Len()
   830  				if typeIsRegistered {
   831  					lenMap++
   832  					vtAtomName = rtype.name
   833  					buf := b.Extend(5)
   834  					buf[0] = ettMap
   835  					binary.BigEndian.PutUint32(buf[1:], uint32(lenMap))
   836  
   837  					child = &stackElement{
   838  						parent:   stack,
   839  						termType: goMapRegistered,
   840  						term:     v.MapKeys(),
   841  						children: lenMap * 2,
   842  						tmp:      vtAtomName,
   843  						reg:      &v,
   844  					}
   845  					break
   846  				}
   847  
   848  				buf := b.Extend(5)
   849  				buf[0] = ettMap
   850  				binary.BigEndian.PutUint32(buf[1:], uint32(lenMap))
   851  
   852  				child = &stackElement{
   853  					parent:   stack,
   854  					termType: goMap,
   855  					term:     v.MapIndex,
   856  					children: lenMap * 2,
   857  					tmp:      v.MapKeys(),
   858  				}
   859  
   860  			case reflect.Ptr:
   861  				// dereference value
   862  				if !v.IsNil() {
   863  					term = v.Elem().Interface()
   864  					goto recasting
   865  				}
   866  
   867  				b.AppendByte(ettNil)
   868  				if stack == nil {
   869  					break
   870  				}
   871  
   872  			default:
   873  				return fmt.Errorf("unsupported type %v with value %#v", v.Type(), v)
   874  			}
   875  		}
   876  
   877  		if stack == nil && child == nil {
   878  			return nil
   879  		}
   880  
   881  		if child != nil {
   882  			stack = child
   883  		}
   884  
   885  	}
   886  }