encode.go (3668B)
1 package eventstream 2 3 import ( 4 "bytes" 5 "encoding/binary" 6 "encoding/hex" 7 "encoding/json" 8 "fmt" 9 "hash" 10 "hash/crc32" 11 "io" 12 13 "github.com/aws/smithy-go/logging" 14 ) 15 16 // EncoderOptions is the configuration options for Encoder. 17 type EncoderOptions struct { 18 Logger logging.Logger 19 LogMessages bool 20 } 21 22 // Encoder provides EventStream message encoding. 23 type Encoder struct { 24 options EncoderOptions 25 26 headersBuf *bytes.Buffer 27 messageBuf *bytes.Buffer 28 } 29 30 // NewEncoder initializes and returns an Encoder to encode Event Stream 31 // messages. 32 func NewEncoder(optFns ...func(*EncoderOptions)) *Encoder { 33 o := EncoderOptions{} 34 35 for _, fn := range optFns { 36 fn(&o) 37 } 38 39 return &Encoder{ 40 options: o, 41 headersBuf: bytes.NewBuffer(nil), 42 messageBuf: bytes.NewBuffer(nil), 43 } 44 } 45 46 // Encode encodes a single EventStream message to the io.Writer the Encoder 47 // was created with. An error is returned if writing the message fails. 48 func (e *Encoder) Encode(w io.Writer, msg Message) (err error) { 49 e.headersBuf.Reset() 50 e.messageBuf.Reset() 51 52 var writer io.Writer = e.messageBuf 53 if e.options.Logger != nil && e.options.LogMessages { 54 encodeMsgBuf := bytes.NewBuffer(nil) 55 writer = io.MultiWriter(writer, encodeMsgBuf) 56 defer func() { 57 logMessageEncode(e.options.Logger, encodeMsgBuf, msg, err) 58 }() 59 } 60 61 if err = EncodeHeaders(e.headersBuf, msg.Headers); err != nil { 62 return err 63 } 64 65 crc := crc32.New(crc32IEEETable) 66 hashWriter := io.MultiWriter(writer, crc) 67 68 headersLen := uint32(e.headersBuf.Len()) 69 payloadLen := uint32(len(msg.Payload)) 70 71 if err = encodePrelude(hashWriter, crc, headersLen, payloadLen); err != nil { 72 return err 73 } 74 75 if headersLen > 0 { 76 if _, err = io.Copy(hashWriter, e.headersBuf); err != nil { 77 return err 78 } 79 } 80 81 if payloadLen > 0 { 82 if _, err = hashWriter.Write(msg.Payload); err != nil { 83 return err 84 } 85 } 86 87 msgCRC := crc.Sum32() 88 if err := binary.Write(writer, binary.BigEndian, msgCRC); err != nil { 89 return err 90 } 91 92 _, err = io.Copy(w, e.messageBuf) 93 94 return err 95 } 96 97 func logMessageEncode(logger logging.Logger, msgBuf *bytes.Buffer, msg Message, encodeErr error) { 98 w := bytes.NewBuffer(nil) 99 defer func() { logger.Logf(logging.Debug, w.String()) }() 100 101 fmt.Fprintf(w, "Message to encode:\n") 102 encoder := json.NewEncoder(w) 103 if err := encoder.Encode(msg); err != nil { 104 fmt.Fprintf(w, "Failed to get encoded message, %v\n", err) 105 } 106 107 if encodeErr != nil { 108 fmt.Fprintf(w, "Encode error: %v\n", encodeErr) 109 return 110 } 111 112 fmt.Fprintf(w, "Raw message:\n%s\n", hex.Dump(msgBuf.Bytes())) 113 } 114 115 func encodePrelude(w io.Writer, crc hash.Hash32, headersLen, payloadLen uint32) error { 116 p := messagePrelude{ 117 Length: minMsgLen + headersLen + payloadLen, 118 HeadersLen: headersLen, 119 } 120 if err := p.ValidateLens(); err != nil { 121 return err 122 } 123 124 err := binaryWriteFields(w, binary.BigEndian, 125 p.Length, 126 p.HeadersLen, 127 ) 128 if err != nil { 129 return err 130 } 131 132 p.PreludeCRC = crc.Sum32() 133 err = binary.Write(w, binary.BigEndian, p.PreludeCRC) 134 if err != nil { 135 return err 136 } 137 138 return nil 139 } 140 141 // EncodeHeaders writes the header values to the writer encoded in the event 142 // stream format. Returns an error if a header fails to encode. 143 func EncodeHeaders(w io.Writer, headers Headers) error { 144 for _, h := range headers { 145 hn := headerName{ 146 Len: uint8(len(h.Name)), 147 } 148 copy(hn.Name[:hn.Len], h.Name) 149 if err := hn.encode(w); err != nil { 150 return err 151 } 152 153 if err := h.Value.encode(w); err != nil { 154 return err 155 } 156 } 157 158 return nil 159 } 160 161 func binaryWriteFields(w io.Writer, order binary.ByteOrder, vs ...any) error { 162 for _, v := range vs { 163 if err := binary.Write(w, order, v); err != nil { 164 return err 165 } 166 } 167 return nil 168 }