net.luminis.quic.packet.VersionNegotiationPacket Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of kwik Show documentation
Show all versions of kwik Show documentation
A QUIC implementation in Java
/*
* Copyright © 2019, 2020, 2021, 2022, 2023, 2024 Peter Doornbosch
*
* This file is part of Kwik, an implementation of the QUIC protocol in Java.
*
* Kwik is free software: you can redistribute it and/or modify it under
* the terms of the GNU Lesser General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your option)
* any later version.
*
* Kwik is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
* more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see .
*/
package net.luminis.quic.packet;
import net.luminis.quic.common.EncryptionLevel;
import net.luminis.quic.common.PnSpace;
import net.luminis.quic.crypto.Aead;
import net.luminis.quic.impl.*;
import net.luminis.quic.log.Logger;
import java.nio.ByteBuffer;
import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.stream.Collectors;
/**
* Represents a Version Negotiation Packet as specified by
* https://tools.ietf.org/html/draft-ietf-quic-transport-16#section-17.4
*/
public class VersionNegotiationPacket extends QuicPacket {
// Minimal length for a valid packet: type version dcid len dcid scid len scid version
private static int MIN_PACKET_LENGTH = 1 + 4 + 1 + 0 + 1 + 0 + 4;
private static Random random = new Random();
private byte[] sourceConnectionId;
private int packetSize;
private List serverSupportedVersions = new ArrayList<>();
public VersionNegotiationPacket() {
this(Version.getDefault());
}
public VersionNegotiationPacket(Version quicVersion) {
this.quicVersion = quicVersion;
}
public VersionNegotiationPacket(Version quicVersion, byte[] sourceConnectionId, byte[] destinationConnectionId) {
serverSupportedVersions = List.of(quicVersion);
this.sourceConnectionId = sourceConnectionId;
this.destinationConnectionId = destinationConnectionId;
}
public VersionNegotiationPacket(List supportedVersions, byte[] sourceConnectionId, byte[] destinationConnectionId) {
serverSupportedVersions = supportedVersions;
this.sourceConnectionId = sourceConnectionId;
this.destinationConnectionId = destinationConnectionId;
}
public List getServerSupportedVersions() {
return serverSupportedVersions;
}
@Override
public void parse(ByteBuffer buffer, Aead aead, long largestPacketNumber, Logger log, int sourceConnectionIdLength) throws DecryptionException, InvalidPacketException {
log.debug("Parsing VersionNegotationPacket");
int packetLength = buffer.limit() - buffer.position();
if (packetLength < MIN_PACKET_LENGTH) {
throw new InvalidPacketException();
}
buffer.get(); // Type
// https://tools.ietf.org/html/draft-ietf-quic-transport-16#section-17.4:
// "A Version Negotiation packet ... will appear to be a packet using the long header, but
// will be identified as a Version Negotiation packet based on the
// Version field having a value of 0."
int zeroVersion = buffer.getInt();
if (zeroVersion != 0) {
throw new ImplementationError();
}
int dstConnIdLength = buffer.get() & 0xff;
if (packetLength < MIN_PACKET_LENGTH + dstConnIdLength) {
throw new InvalidPacketException();
}
destinationConnectionId = new byte[dstConnIdLength];
buffer.get(destinationConnectionId);
int srcConnIdLength = buffer.get() & 0xff;
if (packetLength < MIN_PACKET_LENGTH + dstConnIdLength + srcConnIdLength) {
throw new InvalidPacketException();
}
sourceConnectionId = new byte[srcConnIdLength];
buffer.get(sourceConnectionId);
log.debug("Destination connection id", destinationConnectionId);
log.debug("Source connection id", sourceConnectionId);
while (buffer.remaining() >= 4) {
int versionData = buffer.getInt();
Version supportedVersion = Version.parse(versionData);
serverSupportedVersions.add(supportedVersion);
log.debug("Server supports version " + supportedVersion);
}
packetSize = buffer.limit();
}
@Override
public EncryptionLevel getEncryptionLevel() {
return null;
}
@Override
public PnSpace getPnSpace() {
return null;
}
@Override
public Long getPacketNumber() {
// Version Negotiation Packet doesn't have a packet number
return null;
}
@Override
public int estimateLength(int additionalPayload) {
throw new NotYetImplementedException();
}
@Override
public byte[] generatePacketBytes(Aead aead) {
ByteBuffer buffer = ByteBuffer.allocate(1 + 4 + 1 + destinationConnectionId.length + 1 + sourceConnectionId.length + 4 * serverSupportedVersions.size());
// https://tools.ietf.org/html/draft-ietf-quic-transport-32#section-17.2.1
// "The value in the Unused field is selected randomly by the server. (...)
// Servers SHOULD set the most significant bit of this field (0x40) to 1 so that Version Negotiation packets
// appear to have the Fixed Bit field."
buffer.put((byte) ((byte) random.nextInt(256) | 0b11000000));
// "The Version field of a Version Negotiation packet MUST be set to 0x00000000."
buffer.putInt(0x00000000);
buffer.put((byte) destinationConnectionId.length);
buffer.put(destinationConnectionId);
buffer.put((byte) sourceConnectionId.length);
buffer.put(sourceConnectionId);
serverSupportedVersions.forEach(version -> buffer.put(version.getBytes()));
return buffer.array();
}
@Override
public PacketProcessor.ProcessResult accept(PacketProcessor processor, Instant time) {
return processor.process(this, time);
}
@Override
public boolean canBeAcked() {
// https://tools.ietf.org/html/draft-ietf-quic-transport-18#section-17.2.1
// "A Version Negotiation packet cannot be explicitly acknowledged in an ACK frame by a client."
return false;
}
@Override
public String toString() {
return "Packet "
+ "V" + "|"
+ "-" + "|"
+ "V" + "|"
+ (packetSize >= 0? packetSize: ".") + "|"
+ "0" + " "
+ serverSupportedVersions.stream().map(v -> v.toString()).collect(Collectors.joining(", "));
}
public byte[] getSourceConnectionId() {
return sourceConnectionId;
}
}