All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.luminis.quic.packet.VersionNegotiationPacket Maven / Gradle / Ivy

There is a newer version: 0.9.1
Show newest version
/*
 * Copyright © 2019, 2020, 2021, 2022, 2023 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.crypto.Aead;
import net.luminis.quic.log.Logger;
import net.luminis.quic.core.*;

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;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy