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

io.netty.handler.codec.spdy.SpdyFrameEncoder Maven / Gradle / Ivy

There is a newer version: 5.0.0.Alpha2
Show newest version
/*
 * Copyright 2012 The Netty Project
 *
 * The Netty Project licenses this file to you under the Apache License,
 * version 2.0 (the "License"); you may not use this file except in compliance
 * with the License. You may obtain a copy of the License at:
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations
 * under the License.
 */
package io.netty.handler.codec.spdy;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToByteEncoder;
import io.netty.handler.codec.UnsupportedMessageTypeException;
import io.netty.util.CharsetUtil;

import java.util.Set;

import static io.netty.handler.codec.spdy.SpdyCodecUtil.*;

/**
 * Encodes a SPDY Data or Control Frame into a {@link ByteBuf}.
 */
public class SpdyFrameEncoder extends MessageToByteEncoder {

    private final int version;
    private volatile boolean finished;
    private final SpdyHeaderBlockCompressor headerBlockCompressor;

    /**
     * Creates a new instance with the specified {@code version} and the
     * default {@code compressionLevel (6)}, {@code windowBits (15)},
     * and {@code memLevel (8)}.
     */
    public SpdyFrameEncoder(int version) {
        this(version, 6, 15, 8);
    }

    /**
     * Creates a new instance with the specified parameters.
     */
    public SpdyFrameEncoder(int version, int compressionLevel, int windowBits, int memLevel) {
        if (version < SpdyConstants.SPDY_MIN_VERSION || version > SpdyConstants.SPDY_MAX_VERSION) {
            throw new IllegalArgumentException(
                    "unknown version: " + version);
        }
        this.version = version;
        headerBlockCompressor = SpdyHeaderBlockCompressor.newInstance(
                version, compressionLevel, windowBits, memLevel);
    }

    @Override
    public void beforeAdd(ChannelHandlerContext ctx) throws Exception {
        ctx.channel().closeFuture().addListener(new ChannelFutureListener() {
            @Override
            public void operationComplete(ChannelFuture future) throws Exception {
                synchronized (headerBlockCompressor) {
                    if (finished) {
                        return;
                    }
                    finished = true;
                    headerBlockCompressor.end();
                }
            }
        });
    }

    @Override
    protected void encode(ChannelHandlerContext ctx, SpdyDataOrControlFrame msg, ByteBuf out) throws Exception {
        if (msg instanceof SpdyDataFrame) {

            SpdyDataFrame spdyDataFrame = (SpdyDataFrame) msg;
            ByteBuf data = spdyDataFrame.data();
            byte flags = spdyDataFrame.isLast() ? SPDY_DATA_FLAG_FIN : 0;
            out.ensureWritable(SPDY_HEADER_SIZE + data.readableBytes());
            out.writeInt(spdyDataFrame.getStreamId() & 0x7FFFFFFF);
            out.writeByte(flags);
            out.writeMedium(data.readableBytes());
            out.writeBytes(data, data.readerIndex(), data.readableBytes());

        } else if (msg instanceof SpdySynStreamFrame) {

            SpdySynStreamFrame spdySynStreamFrame = (SpdySynStreamFrame) msg;
            ByteBuf data = compressHeaderBlock(
                    encodeHeaderBlock(version, spdySynStreamFrame));
            byte flags = spdySynStreamFrame.isLast() ? SPDY_FLAG_FIN : 0;
            if (spdySynStreamFrame.isUnidirectional()) {
                flags |= SPDY_FLAG_UNIDIRECTIONAL;
            }
            int headerBlockLength = data.readableBytes();
            int length;
            if (version < 3) {
                length = headerBlockLength == 0 ? 12 : 10 + headerBlockLength;
            } else {
                length = 10 + headerBlockLength;
            }
            out.ensureWritable(SPDY_HEADER_SIZE + length);
            out.writeShort(version | 0x8000);
            out.writeShort(SPDY_SYN_STREAM_FRAME);
            out.writeByte(flags);
            out.writeMedium(length);
            out.writeInt(spdySynStreamFrame.getStreamId());
            out.writeInt(spdySynStreamFrame.getAssociatedToStreamId());
            if (version < 3) {
                // Restrict priorities for SPDY/2 to between 0 and 3
                byte priority = spdySynStreamFrame.getPriority();
                if (priority > 3) {
                    priority = 3;
                }
                out.writeShort((priority & 0xFF) << 14);
            } else {
                out.writeShort((spdySynStreamFrame.getPriority() & 0xFF) << 13);
            }
            if (version < 3 && data.readableBytes() == 0) {
                out.writeShort(0);
            }
            out.writeBytes(data, data.readerIndex(), headerBlockLength);

        } else if (msg instanceof SpdySynReplyFrame) {

            SpdySynReplyFrame spdySynReplyFrame = (SpdySynReplyFrame) msg;
            ByteBuf data = compressHeaderBlock(
                    encodeHeaderBlock(version, spdySynReplyFrame));
            byte flags = spdySynReplyFrame.isLast() ? SPDY_FLAG_FIN : 0;
            int headerBlockLength = data.readableBytes();
            int length;
            if (version < 3) {
                length = headerBlockLength == 0 ? 8 : 6 + headerBlockLength;
            } else {
                length = 4 + headerBlockLength;
            }
            out.ensureWritable(SPDY_HEADER_SIZE + length);
            out.writeShort(version | 0x8000);
            out.writeShort(SPDY_SYN_REPLY_FRAME);
            out.writeByte(flags);
            out.writeMedium(length);
            out.writeInt(spdySynReplyFrame.getStreamId());
            if (version < 3) {
                if (headerBlockLength == 0) {
                    out.writeInt(0);
                } else {
                    out.writeShort(0);
                }
            }
            out.writeBytes(data, data.readerIndex(), headerBlockLength);

        } else if (msg instanceof SpdyRstStreamFrame) {

            SpdyRstStreamFrame spdyRstStreamFrame = (SpdyRstStreamFrame) msg;
            out.ensureWritable(SPDY_HEADER_SIZE + 8);
            out.writeShort(version | 0x8000);
            out.writeShort(SPDY_RST_STREAM_FRAME);
            out.writeInt(8);
            out.writeInt(spdyRstStreamFrame.getStreamId());
            out.writeInt(spdyRstStreamFrame.getStatus().getCode());

        } else if (msg instanceof SpdySettingsFrame) {

            SpdySettingsFrame spdySettingsFrame = (SpdySettingsFrame) msg;
            byte flags = spdySettingsFrame.clearPreviouslyPersistedSettings() ?
                SPDY_SETTINGS_CLEAR : 0;
            Set IDs = spdySettingsFrame.getIds();
            int numEntries = IDs.size();
            int length = 4 + numEntries * 8;
            out.ensureWritable(SPDY_HEADER_SIZE + length);
            out.writeShort(version | 0x8000);
            out.writeShort(SPDY_SETTINGS_FRAME);
            out.writeByte(flags);
            out.writeMedium(length);
            out.writeInt(numEntries);
            for (Integer ID: IDs) {
                int id = ID.intValue();
                byte ID_flags = 0;
                if (spdySettingsFrame.isPersistValue(id)) {
                    ID_flags |= SPDY_SETTINGS_PERSIST_VALUE;
                }
                if (spdySettingsFrame.isPersisted(id)) {
                    ID_flags |= SPDY_SETTINGS_PERSISTED;
                }
                if (version < 3) {
                    // Chromium Issue 79156
                    // SPDY setting ids are not written in network byte order
                    // Write id assuming the architecture is little endian
                    out.writeByte(id       & 0xFF);
                    out.writeByte(id >>  8 & 0xFF);
                    out.writeByte(id >> 16 & 0xFF);
                    out.writeByte(ID_flags);
                } else {
                    out.writeByte(ID_flags);
                    out.writeMedium(id);
                }
                out.writeInt(spdySettingsFrame.getValue(id));
            }

        } else if (msg instanceof SpdyNoOpFrame) {

            out.ensureWritable(SPDY_HEADER_SIZE);
            out.writeShort(version | 0x8000);
            out.writeShort(SPDY_NOOP_FRAME);
            out.writeInt(0);

        } else if (msg instanceof SpdyPingFrame) {

            SpdyPingFrame spdyPingFrame = (SpdyPingFrame) msg;
            out.ensureWritable(SPDY_HEADER_SIZE + 4);
            out.writeShort(version | 0x8000);
            out.writeShort(SPDY_PING_FRAME);
            out.writeInt(4);
            out.writeInt(spdyPingFrame.getId());

        } else if (msg instanceof SpdyGoAwayFrame) {

            SpdyGoAwayFrame spdyGoAwayFrame = (SpdyGoAwayFrame) msg;
            int length = version < 3 ? 4 : 8;
            out.ensureWritable(SPDY_HEADER_SIZE + length);
            out.writeShort(version | 0x8000);
            out.writeShort(SPDY_GOAWAY_FRAME);
            out.writeInt(length);
            out.writeInt(spdyGoAwayFrame.getLastGoodStreamId());
            if (version >= 3) {
                out.writeInt(spdyGoAwayFrame.getStatus().getCode());
            }

        } else if (msg instanceof SpdyHeadersFrame) {

            SpdyHeadersFrame spdyHeadersFrame = (SpdyHeadersFrame) msg;
            ByteBuf data = compressHeaderBlock(
                    encodeHeaderBlock(version, spdyHeadersFrame));
            byte flags = spdyHeadersFrame.isLast() ? SPDY_FLAG_FIN : 0;
            int headerBlockLength = data.readableBytes();
            int length;
            if (version < 3) {
                length = headerBlockLength == 0 ? 4 : 6 + headerBlockLength;
            } else {
                length = 4 + headerBlockLength;
            }
            out.ensureWritable(SPDY_HEADER_SIZE + length);
            out.writeShort(version | 0x8000);
            out.writeShort(SPDY_HEADERS_FRAME);
            out.writeByte(flags);
            out.writeMedium(length);
            out.writeInt(spdyHeadersFrame.getStreamId());
            if (version < 3 && headerBlockLength != 0) {
                out.writeShort(0);
            }
            out.writeBytes(data, data.readerIndex(), headerBlockLength);

        } else if (msg instanceof SpdyWindowUpdateFrame) {

            SpdyWindowUpdateFrame spdyWindowUpdateFrame = (SpdyWindowUpdateFrame) msg;
            out.ensureWritable(SPDY_HEADER_SIZE + 8);
            out.writeShort(version | 0x8000);
            out.writeShort(SPDY_WINDOW_UPDATE_FRAME);
            out.writeInt(8);
            out.writeInt(spdyWindowUpdateFrame.getStreamId());
            out.writeInt(spdyWindowUpdateFrame.getDeltaWindowSize());
        } else {
            throw new UnsupportedMessageTypeException(msg);
        }
    }

    private static void writeLengthField(int version, ByteBuf buffer, int length) {
        if (version < 3) {
            buffer.writeShort(length);
        } else {
            buffer.writeInt(length);
        }
    }

    private static void setLengthField(int version, ByteBuf buffer, int writerIndex, int length) {
        if (version < 3) {
            buffer.setShort(writerIndex, length);
        } else {
            buffer.setInt(writerIndex, length);
        }
    }

    private static ByteBuf encodeHeaderBlock(int version, SpdyHeaderBlock headerFrame)
            throws Exception {
        Set names = headerFrame.headers().names();
        int numHeaders = names.size();
        if (numHeaders == 0) {
            return Unpooled.EMPTY_BUFFER;
        }
        if (numHeaders > SPDY_MAX_NV_LENGTH) {
            throw new IllegalArgumentException(
                    "header block contains too many headers");
        }
        ByteBuf headerBlock = Unpooled.buffer();
        writeLengthField(version, headerBlock, numHeaders);
        for (String name: names) {
            byte[] nameBytes = name.getBytes(CharsetUtil.UTF_8);
            writeLengthField(version, headerBlock, nameBytes.length);
            headerBlock.writeBytes(nameBytes);
            int savedIndex = headerBlock.writerIndex();
            int valueLength = 0;
            writeLengthField(version, headerBlock, valueLength);
            for (String value: headerFrame.headers().getAll(name)) {
                byte[] valueBytes = value.getBytes(CharsetUtil.UTF_8);
                if (valueBytes.length > 0) {
                    headerBlock.writeBytes(valueBytes);
                    headerBlock.writeByte(0);
                    valueLength += valueBytes.length + 1;
                }
            }
            if (valueLength == 0) {
                if (version < 3) {
                    throw new IllegalArgumentException(
                            "header value cannot be empty: " + name);
                }
            } else {
                valueLength --;
            }
            if (valueLength > SPDY_MAX_NV_LENGTH) {
                throw new IllegalArgumentException(
                        "header exceeds allowable length: " + name);
            }
            if (valueLength > 0) {
                setLengthField(version, headerBlock, savedIndex, valueLength);
                headerBlock.writerIndex(headerBlock.writerIndex() - 1);
            }
        }
        return headerBlock;
    }

    private synchronized ByteBuf compressHeaderBlock(
            ByteBuf uncompressed) throws Exception {
        if (uncompressed.readableBytes() == 0) {
            return Unpooled.EMPTY_BUFFER;
        }
        ByteBuf compressed = Unpooled.buffer();
        synchronized (headerBlockCompressor) {
            if (!finished) {
                headerBlockCompressor.setInput(uncompressed);
                headerBlockCompressor.encode(compressed);
            }
        }
        return compressed;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy