/* * Copyright 2018 gRPC authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ // Package msgdecoder contains the logic to deconstruct a gRPC-message. package msgdecoder import ( "encoding/binary" ) // RecvMsg is a message constructed from the incoming // bytes on the transport for a stream. // An instance of RecvMsg will contain only one of the // following: message header related fields, data slice // or error. type RecvMsg struct { // Following three are message header related // fields. // true if the message was compressed by the other // side. IsCompressed bool // Length of the message. Length int // Overhead is the length of message header(5 bytes) // plus padding. Overhead int // Data payload of the message. Data []byte // Err occurred while reading. // nil: received some data // io.EOF: stream is completed. data is nil. // other non-nil error: transport failure. data is nil. Err error Next *RecvMsg } // RecvMsgList is a linked-list of RecvMsg. type RecvMsgList struct { head *RecvMsg tail *RecvMsg } // IsEmpty returns true when l is empty. func (l *RecvMsgList) IsEmpty() bool { if l.tail == nil { return true } return false } // Enqueue adds r to l at the back. func (l *RecvMsgList) Enqueue(r *RecvMsg) { if l.IsEmpty() { l.head, l.tail = r, r return } t := l.tail l.tail = r t.Next = r } // Dequeue removes a RcvMsg from the end of l. func (l *RecvMsgList) Dequeue() *RecvMsg { if l.head == nil { // Note to developer: Instead of calling isEmpty() which // checks the same condition on l.tail, we check it directly // on l.head so that in non-nil cases, there aren't cache misses. return nil } r := l.head l.head = l.head.Next if l.head == nil { l.tail = nil } return r } // MessageDecoder decodes bytes from HTTP2 data frames // and constructs a gRPC message which is then put in a // buffer that application(RPCs) read from. // gRPC Messages: // First 5 bytes is the message header: // First byte: Payload format. // Next 4 bytes: Length of the message. // Rest of the bytes is the message payload. // // TODO(mmukhi): Write unit tests. type MessageDecoder struct { // current message being read by the transport. current *RecvMsg dataOfst int padding int // hdr stores the message header as it is beind received by the transport. hdr []byte hdrOfst int // Callback used to send decoded messages. dispatch func(*RecvMsg) } // NewMessageDecoder creates an instance of MessageDecoder. It takes a callback // which is called to dispatch finished headers and messages to the application. func NewMessageDecoder(dispatch func(*RecvMsg)) *MessageDecoder { return &MessageDecoder{ hdr: make([]byte, 5), dispatch: dispatch, } } // Decode consumes bytes from a HTTP2 data frame to create gRPC messages. func (m *MessageDecoder) Decode(b []byte, padding int) { m.padding += padding for len(b) > 0 { // Case 1: A complete message hdr was received earlier. if m.current != nil { n := copy(m.current.Data[m.dataOfst:], b) m.dataOfst += n b = b[n:] if m.dataOfst == len(m.current.Data) { // Message is complete. m.dispatch(m.current) m.current = nil m.dataOfst = 0 } continue } // Case 2a: No message header has been received yet. if m.hdrOfst == 0 { // case 2a.1: b has the whole header if len(b) >= 5 { m.parseHeader(b[:5]) b = b[5:] continue } // case 2a.2: b has partial header n := copy(m.hdr, b) m.hdrOfst = n b = b[n:] continue } // Case 2b: Partial message header was received earlier. n := copy(m.hdr[m.hdrOfst:], b) m.hdrOfst += n b = b[n:] if m.hdrOfst == 5 { // hdr is complete. m.hdrOfst = 0 m.parseHeader(m.hdr) } } } func (m *MessageDecoder) parseHeader(b []byte) { length := int(binary.BigEndian.Uint32(b[1:5])) hdr := &RecvMsg{ IsCompressed: int(b[0]) == 1, Length: length, Overhead: m.padding + 5, } m.padding = 0 // Dispatch the information retreived from message header so // that the RPC goroutine can send a proactive window update as we // wait for the rest of it. m.dispatch(hdr) if length == 0 { m.dispatch(&RecvMsg{}) return } m.current = &RecvMsg{ Data: getMem(length), } } func getMem(l int) []byte { // TODO(mmukhi): Reuse this memory. return make([]byte, l) } // CreateMessageHeader creates a gRPC-specific message header. func CreateMessageHeader(l int, isCompressed bool) []byte { // TODO(mmukhi): Investigate if this memory is worth // reusing. hdr := make([]byte, 5) if isCompressed { hdr[0] = byte(1) } binary.BigEndian.PutUint32(hdr[1:], uint32(l)) return hdr }