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

net.luminis.quic.frame.StreamFrame Maven / Gradle / Ivy

There is a newer version: 0.9.1
Show newest version
/*
 * 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.frame;

import net.luminis.quic.generic.InvalidIntegerEncodingException;
import net.luminis.quic.generic.VariableLengthInteger;
import net.luminis.quic.impl.Version;
import net.luminis.quic.log.Logger;
import net.luminis.quic.packet.QuicPacket;
import net.luminis.quic.stream.StreamElement;

import java.nio.ByteBuffer;
import java.time.Instant;
import java.util.Arrays;
import java.util.Objects;
import java.util.stream.Stream;


public class StreamFrame extends QuicFrame implements StreamElement {

    private StreamType streamType;
    private int streamId;
    private long offset;
    private int length;
    private byte[] streamData;
    private boolean isFinal;
    private int frameLength;

    public StreamFrame() {
    }

    public StreamFrame(int streamId, byte[] applicationData, boolean fin) {
        this(Version.getDefault(), streamId, 0, applicationData, 0, applicationData.length, fin);
    }

    public StreamFrame(int streamId, long offset, byte[] applicationData, boolean fin) {
        this(Version.getDefault(), streamId, offset, applicationData, 0, applicationData.length, fin);
    }

    public StreamFrame(Version quicVersion, int streamId, long offset, byte[] applicationData, boolean fin) {
        this(quicVersion, streamId, offset, applicationData, 0, applicationData.length, fin);
    }

    public StreamFrame(int streamId, long offset, byte[] applicationData, int dataOffset, int dataLength, boolean fin) {
        this(Version.getDefault(), streamId, offset, applicationData, dataOffset, dataLength, fin);
    }

    public StreamFrame(Version quicVersion, int streamId, long streamOffset, byte[] applicationData, int dataOffset, int dataLength, boolean fin) {
        streamType = Stream.of(StreamType.values()).filter(t -> t.value == (streamId & 0x03)).findFirst().get();
        this.streamId = streamId;
        this.offset = streamOffset;
        this.streamData = new byte[dataLength];
        // This implementation copies the application data, which would not be necessary if the caller guarantees
        // it will not reuse the data buffer (or at least, the range that is used by this frame) and its content
        // will never change.
        ByteBuffer.wrap(streamData).put(applicationData, dataOffset, dataLength);
        this.length = dataLength;
        isFinal = fin;

        frameLength = 1  // frame type
                + VariableLengthInteger.bytesNeeded(streamId)
                + VariableLengthInteger.bytesNeeded(offset)
                + VariableLengthInteger.bytesNeeded(length)
                + length;
    }

    @Override
    public void serialize(ByteBuffer buffer) {
        if (frameLength > buffer.remaining()) {
            throw new IllegalArgumentException();
        }

        byte baseType = (byte) 0x08;
        byte frameType = (byte) (baseType | 0x04 | 0x02 | 0x00);  // OFF-bit, LEN-bit, (no) FIN-bit
        if (isFinal) {
            frameType |= 0x01;
        }
        buffer.put(frameType);
        VariableLengthInteger.encode(streamId, buffer);
        VariableLengthInteger.encode(offset, buffer);
        VariableLengthInteger.encode(length, buffer);
        buffer.put(streamData);
    }

    @Override
    public int getFrameLength() {
        return frameLength;
    }

    public StreamFrame parse(ByteBuffer buffer, Logger log) throws InvalidIntegerEncodingException {
        int startPosition = buffer.position();

        int frameType = buffer.get();
        boolean withOffset = ((frameType & 0x04) == 0x04);
        boolean withLength = ((frameType & 0x02) == 0x02);
        isFinal = ((frameType & 0x01) == 0x01);

        streamId = VariableLengthInteger.parse(buffer);
        streamType = Stream.of(StreamType.values()).filter(t -> t.value == (streamId & 0x03)).findFirst().get();

        if (withOffset) {
            offset = VariableLengthInteger.parseLong(buffer);
        }
        if (withLength) {
            length = VariableLengthInteger.parse(buffer);
        }
        else {
            length = buffer.limit() - buffer.position();
        }

        streamData = new byte[length];
        buffer.get(streamData);
        frameLength = buffer.position() - startPosition;

        log.decrypted("Stream data", streamData);

        return this;
    }

    @Override
    public String toString() {
        return "StreamFrame[" + streamId + "(" + streamType.abbrev + ")" + "," + offset + "," + length + (isFinal? ",fin": "") + "]";
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof StreamFrame)) return false;
        StreamFrame that = (StreamFrame) o;
        return streamId == that.streamId &&
                offset == that.offset &&
                length == that.length &&
                isFinal == that.isFinal &&
                Arrays.equals(streamData, that.streamData);
    }

    @Override
    public int hashCode() {
        return Objects.hash(streamId, offset, length);
    }

    @Override
    public int compareTo(StreamElement other) {
        if (this.offset != other.getOffset()) {
            return Long.compare(this.offset, other.getOffset());
        }
        else {
            return Long.compare(this.length, other.getLength());
        }
    }

    public int getStreamId() {
        return streamId;
    }

    public long getOffset() {
        return offset;
    }

    public int getLength() {
        return length;
    }

    public byte[] getStreamData() {
        return streamData;
    }

    @Override
    public long getUpToOffset() {
        return offset + length;
    }

    public boolean isFinal() {
        return isFinal;
    }

    static public int maxOverhead() {
        return 1  // frame type
        + 4 // stream id
        + 4 // offset
        + 4 // length
        ;
    }

    @Override
    public void accept(FrameProcessor frameProcessor, QuicPacket packet, Instant timeReceived) {
        frameProcessor.process(this, packet, timeReceived);
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy