forked from quic-go/quic-go
This function was needed when we supported both Q039 (using big endian encoding) and ealier versions (using little endian encoding).
243 lines
7.7 KiB
Go
243 lines
7.7 KiB
Go
package wire
|
|
|
|
import (
|
|
"bytes"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
|
|
"github.com/lucas-clemente/quic-go/internal/protocol"
|
|
"github.com/lucas-clemente/quic-go/internal/utils"
|
|
"github.com/lucas-clemente/quic-go/qerr"
|
|
)
|
|
|
|
var (
|
|
errResetAndVersionFlagSet = errors.New("PublicHeader: Reset Flag and Version Flag should not be set at the same time")
|
|
errReceivedOmittedConnectionID = qerr.Error(qerr.InvalidPacketHeader, "receiving packets with omitted ConnectionID is not supported")
|
|
errInvalidConnectionID = qerr.Error(qerr.InvalidPacketHeader, "connection ID cannot be 0")
|
|
errGetLengthNotForVersionNegotiation = errors.New("PublicHeader: GetLength cannot be called for VersionNegotiation packets")
|
|
)
|
|
|
|
// writePublicHeader writes a Public Header.
|
|
func (h *Header) writePublicHeader(b *bytes.Buffer, pers protocol.Perspective, _ protocol.VersionNumber) error {
|
|
if h.VersionFlag && h.ResetFlag {
|
|
return errResetAndVersionFlagSet
|
|
}
|
|
|
|
publicFlagByte := uint8(0x00)
|
|
if h.VersionFlag {
|
|
publicFlagByte |= 0x01
|
|
}
|
|
if h.ResetFlag {
|
|
publicFlagByte |= 0x02
|
|
}
|
|
if !h.OmitConnectionID {
|
|
publicFlagByte |= 0x08
|
|
}
|
|
if len(h.DiversificationNonce) > 0 {
|
|
if len(h.DiversificationNonce) != 32 {
|
|
return errors.New("invalid diversification nonce length")
|
|
}
|
|
publicFlagByte |= 0x04
|
|
}
|
|
// only set PacketNumberLen bits if a packet number will be written
|
|
if h.hasPacketNumber(pers) {
|
|
switch h.PacketNumberLen {
|
|
case protocol.PacketNumberLen1:
|
|
publicFlagByte |= 0x00
|
|
case protocol.PacketNumberLen2:
|
|
publicFlagByte |= 0x10
|
|
case protocol.PacketNumberLen4:
|
|
publicFlagByte |= 0x20
|
|
case protocol.PacketNumberLen6:
|
|
publicFlagByte |= 0x30
|
|
}
|
|
}
|
|
b.WriteByte(publicFlagByte)
|
|
|
|
if !h.OmitConnectionID {
|
|
utils.BigEndian.WriteUint64(b, uint64(h.ConnectionID))
|
|
}
|
|
if h.VersionFlag && pers == protocol.PerspectiveClient {
|
|
utils.BigEndian.WriteUint32(b, uint32(h.Version))
|
|
}
|
|
if len(h.DiversificationNonce) > 0 {
|
|
b.Write(h.DiversificationNonce)
|
|
}
|
|
// if we're a server, and the VersionFlag is set, we must not include anything else in the packet
|
|
if !h.hasPacketNumber(pers) {
|
|
return nil
|
|
}
|
|
|
|
switch h.PacketNumberLen {
|
|
case protocol.PacketNumberLen1:
|
|
b.WriteByte(uint8(h.PacketNumber))
|
|
case protocol.PacketNumberLen2:
|
|
utils.BigEndian.WriteUint16(b, uint16(h.PacketNumber))
|
|
case protocol.PacketNumberLen4:
|
|
utils.BigEndian.WriteUint32(b, uint32(h.PacketNumber))
|
|
case protocol.PacketNumberLen6:
|
|
utils.BigEndian.WriteUint48(b, uint64(h.PacketNumber)&(1<<48-1))
|
|
default:
|
|
return errors.New("PublicHeader: PacketNumberLen not set")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// parsePublicHeader parses a QUIC packet's Public Header.
|
|
// The packetSentBy is the perspective of the peer that sent this PublicHeader, i.e. if we're the server, packetSentBy should be PerspectiveClient.
|
|
func parsePublicHeader(b *bytes.Reader, packetSentBy protocol.Perspective) (*Header, error) {
|
|
header := &Header{}
|
|
|
|
// First byte
|
|
publicFlagByte, err := b.ReadByte()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
header.ResetFlag = publicFlagByte&0x02 > 0
|
|
header.VersionFlag = publicFlagByte&0x01 > 0
|
|
|
|
// TODO: activate this check once Chrome sends the correct value
|
|
// see https://github.com/lucas-clemente/quic-go/issues/232
|
|
// if publicFlagByte&0x04 > 0 {
|
|
// return nil, errors.New("diversification nonces should only be sent by servers")
|
|
// }
|
|
|
|
header.OmitConnectionID = publicFlagByte&0x08 == 0
|
|
if header.OmitConnectionID && packetSentBy == protocol.PerspectiveClient {
|
|
return nil, errReceivedOmittedConnectionID
|
|
}
|
|
if header.hasPacketNumber(packetSentBy) {
|
|
switch publicFlagByte & 0x30 {
|
|
case 0x30:
|
|
header.PacketNumberLen = protocol.PacketNumberLen6
|
|
case 0x20:
|
|
header.PacketNumberLen = protocol.PacketNumberLen4
|
|
case 0x10:
|
|
header.PacketNumberLen = protocol.PacketNumberLen2
|
|
case 0x00:
|
|
header.PacketNumberLen = protocol.PacketNumberLen1
|
|
}
|
|
}
|
|
|
|
// Connection ID
|
|
if !header.OmitConnectionID {
|
|
var connID uint64
|
|
connID, err = utils.BigEndian.ReadUint64(b)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
header.ConnectionID = protocol.ConnectionID(connID)
|
|
if header.ConnectionID == 0 {
|
|
return nil, errInvalidConnectionID
|
|
}
|
|
}
|
|
|
|
if packetSentBy == protocol.PerspectiveServer && publicFlagByte&0x04 > 0 {
|
|
// TODO: remove the if once the Google servers send the correct value
|
|
// assume that a packet doesn't contain a diversification nonce if the version flag or the reset flag is set, no matter what the public flag says
|
|
// see https://github.com/lucas-clemente/quic-go/issues/232
|
|
if !header.VersionFlag && !header.ResetFlag {
|
|
header.DiversificationNonce = make([]byte, 32)
|
|
if _, err := io.ReadFull(b, header.DiversificationNonce); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
}
|
|
|
|
// Version (optional)
|
|
if !header.ResetFlag && header.VersionFlag {
|
|
if packetSentBy == protocol.PerspectiveServer { // parse the version negotiaton packet
|
|
if b.Len() == 0 {
|
|
return nil, qerr.Error(qerr.InvalidVersionNegotiationPacket, "empty version list")
|
|
}
|
|
if b.Len()%4 != 0 {
|
|
return nil, qerr.InvalidVersionNegotiationPacket
|
|
}
|
|
header.IsVersionNegotiation = true
|
|
header.SupportedVersions = make([]protocol.VersionNumber, 0)
|
|
for {
|
|
var versionTag uint32
|
|
versionTag, err = utils.BigEndian.ReadUint32(b)
|
|
if err != nil {
|
|
break
|
|
}
|
|
v := protocol.VersionNumber(versionTag)
|
|
header.SupportedVersions = append(header.SupportedVersions, v)
|
|
}
|
|
// a version negotiation packet doesn't have a packet number
|
|
return header, nil
|
|
}
|
|
// packet was sent by the client. Read the version number
|
|
var versionTag uint32
|
|
versionTag, err = utils.BigEndian.ReadUint32(b)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
header.Version = protocol.VersionNumber(versionTag)
|
|
}
|
|
|
|
// Packet number
|
|
if header.hasPacketNumber(packetSentBy) {
|
|
packetNumber, err := utils.BigEndian.ReadUintN(b, uint8(header.PacketNumberLen))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
header.PacketNumber = protocol.PacketNumber(packetNumber)
|
|
}
|
|
return header, nil
|
|
}
|
|
|
|
// getPublicHeaderLength gets the length of the publicHeader in bytes.
|
|
// It can only be called for regular packets.
|
|
func (h *Header) getPublicHeaderLength(pers protocol.Perspective) (protocol.ByteCount, error) {
|
|
if h.VersionFlag && h.ResetFlag {
|
|
return 0, errResetAndVersionFlagSet
|
|
}
|
|
if h.VersionFlag && pers == protocol.PerspectiveServer {
|
|
return 0, errGetLengthNotForVersionNegotiation
|
|
}
|
|
|
|
length := protocol.ByteCount(1) // 1 byte for public flags
|
|
if h.hasPacketNumber(pers) {
|
|
if h.PacketNumberLen != protocol.PacketNumberLen1 && h.PacketNumberLen != protocol.PacketNumberLen2 && h.PacketNumberLen != protocol.PacketNumberLen4 && h.PacketNumberLen != protocol.PacketNumberLen6 {
|
|
return 0, errPacketNumberLenNotSet
|
|
}
|
|
length += protocol.ByteCount(h.PacketNumberLen)
|
|
}
|
|
if !h.OmitConnectionID {
|
|
length += 8 // 8 bytes for the connection ID
|
|
}
|
|
// Version Number in packets sent by the client
|
|
if h.VersionFlag {
|
|
length += 4
|
|
}
|
|
length += protocol.ByteCount(len(h.DiversificationNonce))
|
|
return length, nil
|
|
}
|
|
|
|
// hasPacketNumber determines if this Public Header will contain a packet number
|
|
// this depends on the ResetFlag, the VersionFlag and who sent the packet
|
|
func (h *Header) hasPacketNumber(packetSentBy protocol.Perspective) bool {
|
|
if h.ResetFlag {
|
|
return false
|
|
}
|
|
if h.VersionFlag && packetSentBy == protocol.PerspectiveServer {
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
func (h *Header) logPublicHeader() {
|
|
connID := "(omitted)"
|
|
if !h.OmitConnectionID {
|
|
connID = fmt.Sprintf("%#x", h.ConnectionID)
|
|
}
|
|
ver := "(unset)"
|
|
if h.Version != 0 {
|
|
ver = fmt.Sprintf("%s", h.Version)
|
|
}
|
|
utils.Debugf(" Public Header{ConnectionID: %s, PacketNumber: %#x, PacketNumberLen: %d, Version: %s, DiversificationNonce: %#v}", connID, h.PacketNumber, h.PacketNumberLen, ver, h.DiversificationNonce)
|
|
}
|