forked from pneymrl2f/nightingale
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
210 lines
5.8 KiB
210 lines
5.8 KiB
// Copyright (c) 2015 Uber Technologies, Inc.
|
|
|
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
// of this software and associated documentation files (the "Software"), to deal
|
|
// in the Software without restriction, including without limitation the rights
|
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
// copies of the Software, and to permit persons to whom the Software is
|
|
// furnished to do so, subject to the following conditions:
|
|
//
|
|
// The above copyright notice and this permission notice shall be included in
|
|
// all copies or substantial portions of the Software.
|
|
//
|
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
// THE SOFTWARE.
|
|
|
|
package tchannel
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"math"
|
|
|
|
"github.com/uber/tchannel-go/typed"
|
|
)
|
|
|
|
const (
|
|
// MaxFrameSize is the total maximum size for a frame
|
|
MaxFrameSize = math.MaxUint16
|
|
|
|
// FrameHeaderSize is the size of the header element for a frame
|
|
FrameHeaderSize = 16
|
|
|
|
// MaxFramePayloadSize is the maximum size of the payload for a single frame
|
|
MaxFramePayloadSize = MaxFrameSize - FrameHeaderSize
|
|
)
|
|
|
|
// FrameHeader is the header for a frame, containing the MessageType and size
|
|
type FrameHeader struct {
|
|
// The size of the frame including the header
|
|
size uint16
|
|
|
|
// The type of message represented by the frame
|
|
messageType messageType
|
|
|
|
// Left empty
|
|
reserved1 byte
|
|
|
|
// The id of the message represented by the frame
|
|
ID uint32
|
|
|
|
// Left empty
|
|
reserved [8]byte
|
|
}
|
|
|
|
// SetPayloadSize sets the size of the frame payload
|
|
func (fh *FrameHeader) SetPayloadSize(size uint16) {
|
|
fh.size = size + FrameHeaderSize
|
|
}
|
|
|
|
// PayloadSize returns the size of the frame payload
|
|
func (fh FrameHeader) PayloadSize() uint16 {
|
|
return fh.size - FrameHeaderSize
|
|
}
|
|
|
|
// FrameSize returns the total size of the frame
|
|
func (fh FrameHeader) FrameSize() uint16 {
|
|
return fh.size
|
|
}
|
|
|
|
func (fh FrameHeader) String() string { return fmt.Sprintf("%v[%d]", fh.messageType, fh.ID) }
|
|
|
|
// MarshalJSON returns a `{"id":NNN, "msgType":MMM, "size":SSS}` representation
|
|
func (fh FrameHeader) MarshalJSON() ([]byte, error) {
|
|
s := struct {
|
|
ID uint32 `json:"id"`
|
|
MsgType messageType `json:"msgType"`
|
|
Size uint16 `json:"size"`
|
|
}{fh.ID, fh.messageType, fh.size}
|
|
return json.Marshal(s)
|
|
}
|
|
|
|
func (fh *FrameHeader) read(r *typed.ReadBuffer) error {
|
|
fh.size = r.ReadUint16()
|
|
fh.messageType = messageType(r.ReadSingleByte())
|
|
fh.reserved1 = r.ReadSingleByte()
|
|
fh.ID = r.ReadUint32()
|
|
r.ReadBytes(len(fh.reserved))
|
|
return r.Err()
|
|
}
|
|
|
|
func (fh *FrameHeader) write(w *typed.WriteBuffer) error {
|
|
w.WriteUint16(fh.size)
|
|
w.WriteSingleByte(byte(fh.messageType))
|
|
w.WriteSingleByte(fh.reserved1)
|
|
w.WriteUint32(fh.ID)
|
|
w.WriteBytes(fh.reserved[:])
|
|
return w.Err()
|
|
}
|
|
|
|
// A Frame is a header and payload
|
|
type Frame struct {
|
|
buffer []byte // full buffer, including payload and header
|
|
headerBuffer []byte // slice referencing just the header
|
|
|
|
// The header for the frame
|
|
Header FrameHeader
|
|
|
|
// The payload for the frame
|
|
Payload []byte
|
|
}
|
|
|
|
// NewFrame allocates a new frame with the given payload capacity
|
|
func NewFrame(payloadCapacity int) *Frame {
|
|
f := &Frame{}
|
|
f.buffer = make([]byte, payloadCapacity+FrameHeaderSize)
|
|
f.Payload = f.buffer[FrameHeaderSize:]
|
|
f.headerBuffer = f.buffer[:FrameHeaderSize]
|
|
return f
|
|
}
|
|
|
|
// ReadBody takes in a previously read frame header, and only reads in the body
|
|
// based on the size specified in the header. This allows callers to defer
|
|
// the frame allocation till the body needs to be read.
|
|
func (f *Frame) ReadBody(header []byte, r io.Reader) error {
|
|
// Copy the header into the underlying buffer so we have an assembled frame
|
|
// that can be directly forwarded.
|
|
copy(f.buffer, header)
|
|
|
|
// Parse the header into our typed struct.
|
|
if err := f.Header.read(typed.NewReadBuffer(header)); err != nil {
|
|
return err
|
|
}
|
|
|
|
switch payloadSize := f.Header.PayloadSize(); {
|
|
case payloadSize > MaxFramePayloadSize:
|
|
return fmt.Errorf("invalid frame size %v", f.Header.size)
|
|
case payloadSize > 0:
|
|
_, err := io.ReadFull(r, f.SizedPayload())
|
|
return err
|
|
default:
|
|
// No payload to read
|
|
return nil
|
|
}
|
|
}
|
|
|
|
// ReadIn reads the frame from the given io.Reader.
|
|
// Deprecated: Only maintained for backwards compatibility. Callers should
|
|
// use ReadBody instead.
|
|
func (f *Frame) ReadIn(r io.Reader) error {
|
|
header := make([]byte, FrameHeaderSize)
|
|
if _, err := io.ReadFull(r, header); err != nil {
|
|
return err
|
|
}
|
|
|
|
return f.ReadBody(header, r)
|
|
}
|
|
|
|
// WriteOut writes the frame to the given io.Writer
|
|
func (f *Frame) WriteOut(w io.Writer) error {
|
|
var wbuf typed.WriteBuffer
|
|
wbuf.Wrap(f.headerBuffer)
|
|
|
|
if err := f.Header.write(&wbuf); err != nil {
|
|
return err
|
|
}
|
|
|
|
fullFrame := f.buffer[:f.Header.FrameSize()]
|
|
if _, err := w.Write(fullFrame); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// SizedPayload returns the slice of the payload actually used, as defined by the header
|
|
func (f *Frame) SizedPayload() []byte {
|
|
return f.Payload[:f.Header.PayloadSize()]
|
|
}
|
|
|
|
// messageType returns the message type.
|
|
func (f *Frame) messageType() messageType {
|
|
return f.Header.messageType
|
|
}
|
|
|
|
func (f *Frame) write(msg message) error {
|
|
var wbuf typed.WriteBuffer
|
|
wbuf.Wrap(f.Payload[:])
|
|
if err := msg.write(&wbuf); err != nil {
|
|
return err
|
|
}
|
|
|
|
f.Header.ID = msg.ID()
|
|
f.Header.reserved1 = 0
|
|
f.Header.messageType = msg.messageType()
|
|
f.Header.SetPayloadSize(uint16(wbuf.BytesWritten()))
|
|
return nil
|
|
}
|
|
|
|
func (f *Frame) read(msg message) error {
|
|
var rbuf typed.ReadBuffer
|
|
rbuf.Wrap(f.SizedPayload())
|
|
return msg.read(&rbuf)
|
|
}
|