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

com.webpieces.http2parser.impl.marshallers.SettingsMarshaller Maven / Gradle / Ivy

package com.webpieces.http2parser.impl.marshallers;

import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Base64;
import java.util.Base64.Decoder;
import java.util.Base64.Encoder;
import java.util.List;

import org.webpieces.data.api.BufferPool;
import org.webpieces.data.api.DataWrapper;
import org.webpieces.data.api.DataWrapperGenerator;

import com.webpieces.http2.api.dto.error.CancelReasonCode;
import com.webpieces.http2.api.dto.error.ConnectionException;
import com.webpieces.http2.api.dto.lowlevel.SettingsFrame;
import com.webpieces.http2.api.dto.lowlevel.lib.AbstractHttp2Frame;
import com.webpieces.http2.api.dto.lowlevel.lib.Http2Frame;
import com.webpieces.http2.api.dto.lowlevel.lib.Http2Setting;
import com.webpieces.http2.api.dto.lowlevel.lib.SettingsParameter;
import com.webpieces.http2parser.impl.FrameHeaderData;
import com.webpieces.http2parser.impl.Http2MementoImpl;
import com.webpieces.http2parser.impl.UnsignedData;

public class SettingsMarshaller extends AbstractFrameMarshaller implements FrameMarshaller {

	public SettingsMarshaller(BufferPool bufferPool, DataWrapperGenerator dataGen) {
		super(bufferPool);
	}

	@Override
	public DataWrapper marshal(Http2Frame frame) {
    	if(frame.getStreamId() != 0)
    		throw new IllegalArgumentException("SettingsFrame can never be any other stream id except 0 which is already set");
    	
		SettingsFrame castFrame = (SettingsFrame) frame;
		for(Http2Setting setting : castFrame.getSettings()) {
			validate(setting);
		}

		byte flags = 0x0;
		if (castFrame.isAck())
			flags |= 0x1;

		DataWrapper dataPayload;
		if (castFrame.isAck()) {
			if(castFrame.getSettings() != null && castFrame.getSettings().size() > 0)
	    		throw new IllegalArgumentException("Ack SettingsFrame can not have setting in it");

			dataPayload = DATA_GEN.emptyWrapper();
		} else if(castFrame.getSettings().size() == 0) {
			dataPayload = DATA_GEN.emptyWrapper();
		} else {
			List settings = castFrame.getSettings();
			dataPayload = marshalOut(settings);
		}
		return super.marshalFrame(frame, flags, dataPayload);
	}

	@Override
	public AbstractHttp2Frame unmarshal(Http2MementoImpl state, DataWrapper payload) {
		FrameHeaderData frameHeaderData = state.getFrameHeaderData();
		int payloadLength = frameHeaderData.getPayloadLength();
		int streamId = frameHeaderData.getStreamId();
        
		SettingsFrame frame = new SettingsFrame();
		super.unmarshalFrame(state, frame);

		byte flags = state.getFrameHeaderData().getFlagsByte();
		frame.setAck((flags & 0x1) == 0x1);

		if(frame.isAck()) {
	        if(payloadLength != 0) {
	            throw new ConnectionException(CancelReasonCode.FRAME_SIZE_INCORRECT, streamId, 
	            		"size of payload of a settings frame ack must be 0 but was="+payloadLength);	        }
		} else if(payloadLength % 6 != 0) {
            throw new ConnectionException(CancelReasonCode.FRAME_SIZE_INCORRECT, streamId, 
            		"payload size must be a multiple of 6 but was="+state.getFrameHeaderData().getPayloadLength());
        } else if(streamId != 0)
            throw new ConnectionException(CancelReasonCode.INVALID_STREAM_ID, streamId, 
            		"settings frame had stream id="+streamId);
        
		ByteBuffer payloadByteBuffer = bufferPool.createWithDataWrapper(payload);

		List settingsList = unmarshal(payloadByteBuffer);
		frame.setSettings(settingsList);

		bufferPool.releaseBuffer(payloadByteBuffer);

		return frame;
	}

	private List unmarshal(ByteBuffer payloadByteBuffer) {
		List settings = new ArrayList<>();
		while (payloadByteBuffer.hasRemaining()) {
			int id = UnsignedData.getUnsignedShort(payloadByteBuffer);
			long value = UnsignedData.getUnsignedInt(payloadByteBuffer);
			Http2Setting http2Setting = new Http2Setting(id, value);
			settings.add(http2Setting);
			validate(http2Setting);
		}
		return settings;
	}

	private void validate(Http2Setting http2Setting) {
		SettingsParameter key = SettingsParameter.lookup(http2Setting.getId());
		long value = http2Setting.getValue();
		if(key == null)
			return; //unknown setting
		
		switch(key) {
			case SETTINGS_ENABLE_PUSH:
				if(value != 0 && value != 1)
		            throw new ConnectionException(CancelReasonCode.INVALID_SETTING, 0, 
		            		"push setting must be 0 or 1 but was="+value);
				break;
			case SETTINGS_INITIAL_WINDOW_SIZE:
				validateWindowSize(value);
				break;
			case SETTINGS_MAX_FRAME_SIZE:
				validateMaxFrameSize(value);
				break;
			case SETTINGS_HEADER_TABLE_SIZE:
			case SETTINGS_MAX_CONCURRENT_STREAMS:
			case SETTINGS_MAX_HEADER_LIST_SIZE:
				break;
			default:
				throw new IllegalArgumentException("case statement missing new setting="+key+" with value="+value);
		}
	}

	private void validateWindowSize(long value) {
        // 2^31 - 1 - max flow control window
		int min = 0;
		int max = 2147483647;
		
		if(value < min || value > max)
            throw new ConnectionException(CancelReasonCode.SETTINGS_WINDOW_SIZE_INVALID, 0, 
            		"window size must be between "+min+" and "+max+" but was="+value);
	}
	
	private void validateMaxFrameSize(long value) {
        // frame size must be between 16384 and 2^24 - 1
		int min = 16384;
		int max = 1677215;
		
		if(value < min || value > max)
            throw new ConnectionException(CancelReasonCode.INVALID_SETTING, 0, 
            		"window size must be between "+min+" and "+max+" but was="+value);
	}

	public List unmarshalPayload(String base64SettingsPayload) {
		Decoder decoder = Base64.getDecoder();
		byte[] decoded = decoder.decode(base64SettingsPayload);
		ByteBuffer buf = ByteBuffer.wrap(decoded);
		return unmarshal(buf);
	}

	public String marshalPayload(List settingsPayload) {
		DataWrapper data = marshalOut(settingsPayload);
		byte[] byteBuf = data.createByteArray();
		Encoder encoder = Base64.getEncoder();
		return encoder.encodeToString(byteBuf);
	}
	
	private DataWrapper marshalOut(List settings) {
		DataWrapper dataPayload;
		ByteBuffer payload = bufferPool.nextBuffer(6 * settings.size());

		for (Http2Setting setting : settings) {
			UnsignedData.putUnsignedShort(payload, setting.getId());
			UnsignedData.putUnsignedInt(payload, setting.getValue());
		}
		payload.flip();

		dataPayload = DATA_GEN.wrapByteBuffer(payload);
		return dataPayload;
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy