forked from grpc/grpc-go
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Export changes to OSS. * First commit. * Cherry-pick. * Documentation. * Post review updates.
- Loading branch information
Showing
15 changed files
with
1,080 additions
and
1,023 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,203 @@ | ||
/* | ||
* 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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
/* | ||
* 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 | ||
|
||
import ( | ||
"encoding/binary" | ||
"reflect" | ||
"testing" | ||
) | ||
|
||
func TestMessageDecoder(t *testing.T) { | ||
for _, test := range []struct { | ||
numFrames int | ||
data []string | ||
}{ | ||
{1, []string{"abc"}}, // One message per frame. | ||
{1, []string{"abc", "def", "ghi"}}, // Multiple messages per frame. | ||
{3, []string{"a", "bcdef", "ghif"}}, // Multiple messages over multiple frames. | ||
} { | ||
var want []*RecvMsg | ||
for _, d := range test.data { | ||
want = append(want, &RecvMsg{Length: len(d), Overhead: 5}) | ||
want = append(want, &RecvMsg{Data: []byte(d)}) | ||
} | ||
var got []*RecvMsg | ||
dcdr := NewMessageDecoder(func(r *RecvMsg) { got = append(got, r) }) | ||
for _, fr := range createFrames(test.numFrames, test.data) { | ||
dcdr.Decode(fr, 0) | ||
} | ||
if !match(got, want) { | ||
t.Fatalf("got: %v, want: %v", got, want) | ||
} | ||
} | ||
} | ||
|
||
func match(got, want []*RecvMsg) bool { | ||
for i, v := range got { | ||
if !reflect.DeepEqual(v, want[i]) { | ||
return false | ||
} | ||
} | ||
return true | ||
} | ||
|
||
func createFrames(n int, msgs []string) [][]byte { | ||
var b []byte | ||
for _, m := range msgs { | ||
payload := []byte(m) | ||
hdr := make([]byte, 5) | ||
binary.BigEndian.PutUint32(hdr[1:], uint32(len(payload))) | ||
b = append(b, hdr...) | ||
b = append(b, payload...) | ||
} | ||
// break b into n parts. | ||
var result [][]byte | ||
batch := len(b) / n | ||
for len(b) != 0 { | ||
sz := batch | ||
if len(b) < sz { | ||
sz = len(b) | ||
} | ||
result = append(result, b[:sz]) | ||
b = b[sz:] | ||
} | ||
return result | ||
} |
Oops, something went wrong.