Files
teleport/lib/srv/db/sqlserver/protocol/packet.go
T

161 lines
4.1 KiB
Go

/*
Copyright 2022 Gravitational, Inc.
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 protocol
import (
"bytes"
"encoding/binary"
"io"
"github.com/gravitational/trace"
)
// PacketHeader represents a 8-byte packet header.
//
// https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-tds/7af53667-1b72-4703-8258-7984e838f746
//
// Note: the order of fields in the struct matters as it gets unpacked from the
// binary stream.
type PacketHeader struct {
Type uint8
Status uint8
Length uint16 // network byte order (big-endian)
SPID uint16 // network byte order (big-endian)
PacketID uint8
Window uint8
}
// Marshal marshals the packet header to the wire protocol byte representation.
func (h *PacketHeader) Marshal() ([]byte, error) {
buf := bytes.NewBuffer(make([]byte, 0, packetHeaderSize))
if err := binary.Write(buf, binary.BigEndian, h); err != nil {
return nil, trace.Wrap(err)
}
return buf.Bytes(), nil
}
// Packet is a packet interface.
type Packet interface {
// Bytes returns whole packet bytes.
Bytes() []byte
// Data returns packet data without data related to Header.
Data() []byte
// Header returns packet Header definition.
Header() PacketHeader
// Type returns packet type ID.
Type() uint8
}
// BasicPacket implements the Packet interfaces allowing to operate on
// PacketHeader and get underlying packet type.
type BasicPacket struct {
header PacketHeader
data []byte
raw bytes.Buffer
}
// Bytes returns raw packet bytes.
func (g BasicPacket) Bytes() []byte {
return g.raw.Bytes()
}
// Data is the packet data bytes without header.
func (g BasicPacket) Data() []byte {
return g.data
}
// Header is the parsed packet header.
func (g BasicPacket) Header() PacketHeader {
return g.header
}
// Type is the parsed packet header.
func (g BasicPacket) Type() uint8 {
return g.header.Type
}
// ReadPacket reads a single full packet from the reader.
func ReadPacket(r io.Reader) (*BasicPacket, error) {
var buff bytes.Buffer
tr := io.TeeReader(r, &buff)
// Read 8-byte packet header.
var headerBytes [packetHeaderSize]byte
if _, err := io.ReadFull(tr, headerBytes[:]); err != nil {
return nil, trace.ConvertSystemError(err)
}
// Unmarshal packet header from the binary form.
var header PacketHeader
if err := binary.Read(bytes.NewReader(headerBytes[:]), binary.BigEndian, &header); err != nil {
return nil, trace.Wrap(err)
}
// Read packet data. Packet length includes header.
dataBytes := make([]byte, header.Length-packetHeaderSize)
if _, err := io.ReadFull(tr, dataBytes); err != nil {
return nil, trace.ConvertSystemError(err)
}
p := &BasicPacket{
header: header,
data: dataBytes,
raw: buff,
}
return p, nil
}
// ToSQLPacket tries to convert basicPacket to MSServer SQL packet.
func ToSQLPacket(p *BasicPacket) (out Packet, err error) {
defer func() {
if r := recover(); r != nil {
err = trace.BadParameter("failed to convert packet to SQL packet: %v", r)
}
}()
switch p.Type() {
case PacketTypeRPCRequest:
sqlBatch, err := toRPCRequest(p)
if err != nil {
return p, trace.Wrap(err)
}
return sqlBatch, trace.Wrap(err)
case PacketTypeSQLBatch:
rpcRequest, err := toSQLBatch(p)
if err != nil {
return p, trace.Wrap(err)
}
return rpcRequest, trace.Wrap(err)
}
return p, trace.Wrap(err)
}
// makePacket prepends header to the provided packet data.
func makePacket(pktType uint8, pktData []byte) ([]byte, error) {
header := PacketHeader{
Type: pktType,
Status: packetStatusLast,
Length: uint16(packetHeaderSize + len(pktData)),
}
headerBytes, err := header.Marshal()
if err != nil {
return nil, trace.Wrap(err)
}
return append(headerBytes, pktData...), nil
}