github.com/hamba/avro/v2@v2.22.1-0.20240518180522-aff3955acf7d/codec_fixed.go (about)

     1  package avro
     2  
     3  import (
     4  	"encoding/binary"
     5  	"fmt"
     6  	"math/big"
     7  	"reflect"
     8  	"unsafe"
     9  
    10  	"github.com/modern-go/reflect2"
    11  )
    12  
    13  func createDecoderOfFixed(schema Schema, typ reflect2.Type) ValDecoder {
    14  	fixed := schema.(*FixedSchema)
    15  	switch typ.Kind() {
    16  	case reflect.Array:
    17  		arrayType := typ.(reflect2.ArrayType)
    18  		if arrayType.Elem().Kind() != reflect.Uint8 || arrayType.Len() != fixed.Size() {
    19  			break
    20  		}
    21  		return &fixedCodec{arrayType: typ.(*reflect2.UnsafeArrayType)}
    22  
    23  	case reflect.Uint64:
    24  		fixed := schema.(*FixedSchema)
    25  		if fixed.Size() != 8 {
    26  			break
    27  		}
    28  
    29  		return &fixedUint64Codec{}
    30  
    31  	case reflect.Struct:
    32  		ls := fixed.Logical()
    33  		if ls == nil {
    34  			break
    35  		}
    36  		typ1 := typ.Type1()
    37  		switch {
    38  		case typ1.ConvertibleTo(durType) && ls.Type() == Duration:
    39  			return &fixedDurationCodec{}
    40  		case typ1.ConvertibleTo(ratType) && ls.Type() == Decimal:
    41  			dec := ls.(*DecimalLogicalSchema)
    42  			return &fixedDecimalCodec{prec: dec.Precision(), scale: dec.Scale(), size: fixed.Size()}
    43  		}
    44  	}
    45  
    46  	return &errorDecoder{
    47  		err: fmt.Errorf("avro: %s is unsupported for Avro %s, size=%d", typ.String(), schema.Type(), fixed.Size()),
    48  	}
    49  }
    50  
    51  func createEncoderOfFixed(schema Schema, typ reflect2.Type) ValEncoder {
    52  	fixed := schema.(*FixedSchema)
    53  	switch typ.Kind() {
    54  	case reflect.Array:
    55  		arrayType := typ.(reflect2.ArrayType)
    56  		fixed := schema.(*FixedSchema)
    57  		if arrayType.Elem().Kind() != reflect.Uint8 || arrayType.Len() != fixed.Size() {
    58  			break
    59  		}
    60  		return &fixedCodec{arrayType: typ.(*reflect2.UnsafeArrayType)}
    61  
    62  	case reflect.Uint64:
    63  		fixed := schema.(*FixedSchema)
    64  		if fixed.Size() != 8 {
    65  			break
    66  		}
    67  
    68  		return &fixedUint64Codec{}
    69  
    70  	case reflect.Ptr:
    71  		ptrType := typ.(*reflect2.UnsafePtrType)
    72  		elemType := ptrType.Elem()
    73  
    74  		ls := fixed.Logical()
    75  		tpy1 := elemType.Type1()
    76  		if elemType.Kind() != reflect.Struct || !tpy1.ConvertibleTo(ratType) || ls == nil ||
    77  			ls.Type() != Decimal {
    78  			break
    79  		}
    80  		dec := ls.(*DecimalLogicalSchema)
    81  		return &fixedDecimalCodec{prec: dec.Precision(), scale: dec.Scale(), size: fixed.Size()}
    82  
    83  	case reflect.Struct:
    84  		ls := fixed.Logical()
    85  		if ls == nil {
    86  			break
    87  		}
    88  		typ1 := typ.Type1()
    89  		if typ1.ConvertibleTo(durType) && ls.Type() == Duration {
    90  			return &fixedDurationCodec{}
    91  		}
    92  	}
    93  
    94  	return &errorEncoder{
    95  		err: fmt.Errorf("avro: %s is unsupported for Avro %s, size=%d", typ.String(), schema.Type(), fixed.Size()),
    96  	}
    97  }
    98  
    99  type fixedUint64Codec [8]byte
   100  
   101  func (c *fixedUint64Codec) Decode(ptr unsafe.Pointer, r *Reader) {
   102  	buffer := c[:]
   103  	r.Read(buffer)
   104  	*(*uint64)(ptr) = binary.BigEndian.Uint64(buffer)
   105  }
   106  
   107  func (c *fixedUint64Codec) Encode(ptr unsafe.Pointer, w *Writer) {
   108  	buffer := c[:]
   109  	binary.BigEndian.PutUint64(buffer, *(*uint64)(ptr))
   110  	_, _ = w.Write(buffer)
   111  }
   112  
   113  type fixedCodec struct {
   114  	arrayType *reflect2.UnsafeArrayType
   115  }
   116  
   117  func (c *fixedCodec) Decode(ptr unsafe.Pointer, r *Reader) {
   118  	for i := 0; i < c.arrayType.Len(); i++ {
   119  		c.arrayType.UnsafeSetIndex(ptr, i, reflect2.PtrOf(r.readByte()))
   120  	}
   121  }
   122  
   123  func (c *fixedCodec) Encode(ptr unsafe.Pointer, w *Writer) {
   124  	for i := 0; i < c.arrayType.Len(); i++ {
   125  		bytePtr := c.arrayType.UnsafeGetIndex(ptr, i)
   126  		w.writeByte(*((*byte)(bytePtr)))
   127  	}
   128  }
   129  
   130  type fixedDecimalCodec struct {
   131  	prec  int
   132  	scale int
   133  	size  int
   134  }
   135  
   136  func (c *fixedDecimalCodec) Decode(ptr unsafe.Pointer, r *Reader) {
   137  	b := make([]byte, c.size)
   138  	r.Read(b)
   139  	*((*big.Rat)(ptr)) = *ratFromBytes(b, c.scale)
   140  }
   141  
   142  func (c *fixedDecimalCodec) Encode(ptr unsafe.Pointer, w *Writer) {
   143  	r := *((**big.Rat)(ptr))
   144  	scale := new(big.Int).Exp(big.NewInt(10), big.NewInt(int64(c.scale)), nil)
   145  	i := (&big.Int{}).Mul(r.Num(), scale)
   146  	i = i.Div(i, r.Denom())
   147  
   148  	var b []byte
   149  	switch i.Sign() {
   150  	case 0:
   151  		b = make([]byte, c.size)
   152  
   153  	case 1:
   154  		b = i.Bytes()
   155  		if b[0]&0x80 > 0 {
   156  			b = append([]byte{0}, b...)
   157  		}
   158  		if len(b) < c.size {
   159  			padded := make([]byte, c.size)
   160  			copy(padded[c.size-len(b):], b)
   161  			b = padded
   162  		}
   163  
   164  	case -1:
   165  		b = i.Add(i, (&big.Int{}).Lsh(one, uint(c.size*8))).Bytes()
   166  	}
   167  
   168  	_, _ = w.Write(b)
   169  }
   170  
   171  type fixedDurationCodec struct{}
   172  
   173  func (*fixedDurationCodec) Decode(ptr unsafe.Pointer, r *Reader) {
   174  	b := make([]byte, 12)
   175  	r.Read(b)
   176  	var duration LogicalDuration
   177  	duration.Months = binary.LittleEndian.Uint32(b[0:4])
   178  	duration.Days = binary.LittleEndian.Uint32(b[4:8])
   179  	duration.Milliseconds = binary.LittleEndian.Uint32(b[8:12])
   180  	*((*LogicalDuration)(ptr)) = duration
   181  }
   182  
   183  func (*fixedDurationCodec) Encode(ptr unsafe.Pointer, w *Writer) {
   184  	duration := (*LogicalDuration)(ptr)
   185  	b := make([]byte, 4)
   186  	binary.LittleEndian.PutUint32(b, duration.Months)
   187  	_, _ = w.Write(b)
   188  	binary.LittleEndian.PutUint32(b, duration.Days)
   189  	_, _ = w.Write(b)
   190  	binary.LittleEndian.PutUint32(b, duration.Milliseconds)
   191  	_, _ = w.Write(b)
   192  }