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