
* Export changes to OSS. * First commit. * Cherry-pick. * Documentation. * Post review updates.
204 lines
5.1 KiB
Go
204 lines
5.1 KiB
Go
/*
|
|
* 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
|
|
}
|