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

net.luminis.quic.generic.VariableLengthInteger 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.generic;

import net.luminis.tls.util.ByteUtils;

import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;


// https://tools.ietf.org/html/draft-ietf-quic-transport-20#section-16
public class VariableLengthInteger {

    /**
     * Parses a variable length integer and returns the value as in int. Throws an exception when the actual value is
     * larger than Integer.MAX_VALUE, so only use it in cases where a large value can be considered an
     * error, e.g. when the QUIC specification defines a smaller range for a specific integer.
     * Note that smaller values (needlessly) encoded in eight bytes, are parsed correctly.
     * @param buffer
     * @return
     * @throws InvalidIntegerEncodingException
     */
    public static int parse(ByteBuffer buffer) throws InvalidIntegerEncodingException {
        long value = parseLong(buffer);
        if (value <= Integer.MAX_VALUE) {
            return (int) value;
        }
        else {
            // If value can be larger than int, parseLong should have called.
            throw new IllegalArgumentException("value to large for Java int");
        }
    }

    public static long parseLong(ByteBuffer buffer) throws InvalidIntegerEncodingException {
        if (buffer.remaining() < 1) {
            throw new InvalidIntegerEncodingException();
        }

        long value;
        byte firstLengthByte = buffer.get();
        switch ((firstLengthByte & 0xc0) >> 6) {
            case 0:
                value = firstLengthByte;
                break;
            case 1:
                if (buffer.remaining() < 1) {
                    throw new InvalidIntegerEncodingException();
                }
                buffer.position(buffer.position() - 1);
                value = buffer.getShort() & 0x3fff;
                break;
            case 2:
                if (buffer.remaining() < 3) {
                    throw new InvalidIntegerEncodingException();
                }
                buffer.position(buffer.position() - 1);
                value = buffer.getInt() & 0x3fffffff;
                break;
            case 3:
                if (buffer.remaining() < 7) {
                    throw new InvalidIntegerEncodingException();
                }
                buffer.position(buffer.position() - 1);
                value = buffer.getLong() & 0x3fffffffffffffffL;
                break;
            default:
                // Impossible, just to satisfy the compiler
                throw new RuntimeException();
        }
        return value;
    }

    public static int parse(InputStream inputStream) throws IOException {
        long value = parseLong(inputStream);
        if (value <= Integer.MAX_VALUE) {
            return (int) value;
        }
        else {
            throw new RuntimeException("value to large for Java int");
        }
    }

    public static long parseLong(InputStream inputStream) throws IOException {
        long value;
        int firstLengthByte = inputStream.read();
        if (firstLengthByte == -1) {
            throw new EOFException();
        }
        switch ((firstLengthByte & 0xc0) >> 6) {
            case 0:
                value = firstLengthByte;
                break;
            case 1:
                int nextByte = inputStream.read();
                if (nextByte == -1) {
                    throw new EOFException();
                }
                value = ((long) (firstLengthByte & 0x3f) << 8) | (nextByte & 0xff);
                break;
            case 2:
                int byte2 = inputStream.read();
                int byte3 = inputStream.read();
                int byte4 = inputStream.read();
                if (byte2 == -1 || byte3 == -1 || byte4 == -1) {
                    throw new EOFException();
                }
                value = ((long) (firstLengthByte & 0x3f) << 24) | ((byte2 & 0xff) << 16) | ((byte3 & 0xff) << 8) | (byte4 & 0xff);
                break;
            case 3:
                byte[] rawBytes = new byte[8];
                rawBytes[0] = (byte) (firstLengthByte & 0x3f);
                int bytesRead = 0;
                while (bytesRead != 7) {
                    int read = inputStream.read(rawBytes, 1 + bytesRead, 7 - bytesRead);
                    if (read > 0) {
                        bytesRead += read;
                    }
                    else {
                        throw new EOFException();
                    }
                }
                value = ByteBuffer.wrap(rawBytes).getLong();
                break;
            default:
                // Impossible, just to satisfy the compiler
                throw new RuntimeException();
        }
        return value;
    }

    public static int bytesNeeded(long value) {
        if (value <= 63) {
            return 1;
        }
        else if (value <= 16383) {
            return 2;
        }
        else if (value <= 1073741823) {
            return 4;
        }
        else {
            return 8;
        }
    }

    public static int encode(int value, ByteBuffer buffer) {
        // https://tools.ietf.org/html/draft-ietf-quic-transport-20#section-16
        // | 2Bit | Length | Usable Bits | Range                 |
        // +------+--------+-------------+-----------------------+
        // | 00   | 1      | 6           | 0-63                  |
        // | 01   | 2      | 14          | 0-16383               |
        // | 10   | 4      | 30          | 0-1073741823          |
        if (value <= 63) {
            buffer.put((byte) value);
            return 1;
        }
        else if (value <= 16383) {
            buffer.put((byte) ((value / 256) | 0x40));
            buffer.put((byte) (value % 256));
            return 2;
        }
        else if (value <= 1073741823) {
            int initialPosition = buffer.position();
            buffer.putInt(value);
            buffer.put(initialPosition, (byte) (buffer.get(initialPosition) | (byte) 0x80));
            return 4;
        }
        else {
            int initialPosition = buffer.position();
            buffer.putLong(value);
            buffer.put(initialPosition, (byte) (buffer.get(initialPosition) | (byte) 0xc0));
            return 8;
        }
    }

    public static int encode(long value, ByteBuffer buffer) {
        if (value <= Integer.MAX_VALUE) {
            return encode((int) value, buffer);
        }
        // https://tools.ietf.org/html/draft-ietf-quic-transport-20#section-16
        // | 2Bit | Length | Usable Bits | Range                 |
        // +------+--------+-------------+-----------------------+
        // | 11   | 8      | 62          | 0-4611686018427387903 |
        else if (value <= 4611686018427387903L) {
            int initialPosition = buffer.position();
            buffer.putLong(value);
            buffer.put(initialPosition, (byte) (buffer.get(initialPosition) | (byte) 0xc0));
            return 8;
        }
        else {
            throw new IllegalArgumentException("value cannot be encoded in variable-length integer");
        }
    }

    public static void main(String[] args) throws InvalidIntegerEncodingException {
        for (int i = 0; i < args.length; i++) {
            long value = parseLong(ByteBuffer.wrap(ByteUtils.hexToBytes(args[i])));
            System.out.println(args[i] + " => " + value);
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy