forked from quic-go/quic-go
The Public Header will not change before the switch to IETF QUIC. We will have to accept the wrong bit in the client Public Header.
242 lines
7.6 KiB
Go
242 lines
7.6 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
|
|
}
|
|
}
|
|
|
|
// Contrary to what the gQUIC wire spec says, the 0x4 bit only indicates the presence of the diversification nonce for packets sent by the server.
|
|
// It doesn't have any meaning when sent by the client.
|
|
if packetSentBy == protocol.PerspectiveServer && publicFlagByte&0x04 > 0 {
|
|
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 negotiation 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 = h.Version.String()
|
|
}
|
|
utils.Debugf(" Public Header{ConnectionID: %s, PacketNumber: %#x, PacketNumberLen: %d, Version: %s, DiversificationNonce: %#v}", connID, h.PacketNumber, h.PacketNumberLen, ver, h.DiversificationNonce)
|
|
}
|