1 module hunt.http.codec.http.decode.PushPromiseBodyParser;
2 
3 import hunt.io.ByteBuffer;
4 
5 import hunt.http.codec.http.decode.BodyParser;
6 import hunt.http.codec.http.decode.HeaderBlockParser;
7 import hunt.http.codec.http.decode.HeaderParser;
8 import hunt.http.codec.http.decode.Parser;
9 
10 import hunt.http.codec.http.frame.ErrorCode;
11 import hunt.http.codec.http.frame.Flags;
12 import hunt.http.codec.http.frame.PushPromiseFrame;
13 import hunt.http.HttpMetaData;
14 
15 import hunt.Exceptions;
16 
17 import std.algorithm;
18 
19 /**
20 */
21 class PushPromiseBodyParser :BodyParser {
22 	private HeaderBlockParser headerBlockParser;
23 	private State state = State.PREPARE;
24 	private int cursor;
25 	private int length;
26 	private int paddingLength;
27 	private int streamId;
28 
29 	this(HeaderParser headerParser, Parser.Listener listener,
30 			HeaderBlockParser headerBlockParser) {
31 		super(headerParser, listener);
32 		this.headerBlockParser = headerBlockParser;
33 	}
34 
35 	private void reset() {
36 		state = State.PREPARE;
37 		cursor = 0;
38 		length = 0;
39 		paddingLength = 0;
40 		streamId = 0;
41 	}
42 
43 	override
44 	bool parse(ByteBuffer buffer) {
45 		bool loop = false;
46 		while (buffer.hasRemaining() || loop) {
47 			switch (state) {
48 			case State.PREPARE: {
49 				// SPEC: wrong streamId is treated as connection error.
50 				if (getStreamId() == 0)
51 					return connectionFailure(buffer, cast(int)ErrorCode.PROTOCOL_ERROR, "invalid_push_promise_frame");
52 
53 				// For now we don't support PUSH_PROMISE frames that don't have
54 				// END_HEADERS.
55 				if (!hasFlag(Flags.END_HEADERS))
56 					return connectionFailure(buffer, cast(int)ErrorCode.INTERNAL_ERROR, "unsupported_push_promise_frame");
57 
58 				length = getBodyLength();
59 
60 				if (isPadding()) {
61 					state = State.PADDING_LENGTH;
62 				} else {
63 					state = State.STREAM_ID;
64 				}
65 				break;
66 			}
67 			case State.PADDING_LENGTH: {
68 				paddingLength = buffer.get() & 0xFF;
69 				--length;
70 				length -= paddingLength;
71 				state = State.STREAM_ID;
72 				if (length < 4)
73 					return connectionFailure(buffer, cast(int)ErrorCode.FRAME_SIZE_ERROR, "invalid_push_promise_frame");
74 				break;
75 			}
76 			case State.STREAM_ID: {
77 				if (buffer.remaining() >= 4) {
78 					streamId = buffer.get!int();
79 					streamId &= 0x7F_FF_FF_FF;
80 					length -= 4;
81 					state = State.HEADERS;
82 					loop = length == 0;
83 				} else {
84 					state = State.STREAM_ID_BYTES;
85 					cursor = 4;
86 				}
87 				break;
88 			}
89 			case State.STREAM_ID_BYTES: {
90 				int currByte = buffer.get() & 0xFF;
91 				--cursor;
92 				streamId += currByte << (8 * cursor);
93 				--length;
94 				if (cursor > 0 && length <= 0)
95 					return connectionFailure(buffer, cast(int)ErrorCode.FRAME_SIZE_ERROR, "invalid_push_promise_frame");
96 				if (cursor == 0) {
97 					streamId &= 0x7F_FF_FF_FF;
98 					state = State.HEADERS;
99 					loop = length == 0;
100 				}
101 				break;
102 			}
103 			case State.HEADERS: {
104 				HttpMetaData metaData = headerBlockParser.parse(buffer, length);
105 				if (metaData !is null) {
106 					state = State.PADDING;
107 					loop = paddingLength == 0;
108 					onPushPromise(streamId, metaData);
109 				}
110 				break;
111 			}
112 			case State.PADDING: {
113 				int size = std.algorithm.min(buffer.remaining(), paddingLength);
114 				buffer.position(buffer.position() + size);
115 				paddingLength -= size;
116 				if (paddingLength == 0) {
117 					reset();
118 					return true;
119 				}
120 				break;
121 			}
122 			default: {
123 				throw new IllegalStateException("");
124 			}
125 			}
126 		}
127 		return false;
128 	}
129 
130 	private void onPushPromise(int streamId, HttpMetaData metaData) {
131 		PushPromiseFrame frame = new PushPromiseFrame(getStreamId(), streamId, metaData);
132 		notifyPushPromise(frame);
133 	}
134 
135 	private enum State {
136 		PREPARE, PADDING_LENGTH, STREAM_ID, STREAM_ID_BYTES, HEADERS, PADDING
137 	}
138 }