Skip to content

Commit 2709ca5

Browse files
committed
fix http2 frame parsing
1 parent 9fbeab6 commit 2709ca5

File tree

3 files changed

+126
-129
lines changed

3 files changed

+126
-129
lines changed

src/main/kotlin/dev/samicpp/http/http1/Http1Socket.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -327,7 +327,7 @@ class Http1Socket(private val conn:Socket):HttpSocket{
327327
val h2=
328328
if(settb64!=null){
329329
val setts=Base64.getDecoder().decode(settb64)
330-
val sett=parseHttp2Settings(setts)
330+
val sett=Http2Settings.parse(setts)
331331
conn.write("HTTP/1.1 101 Switching Protocols\r\nConnection: Upgrade\r\nUpgrade: h2c\r\n\r\n".encodeToByteArray())
332332
Http2Connection(conn,sett)
333333
} else {

src/main/kotlin/dev/samicpp/http/http2/Http2Connection.kt

Lines changed: 4 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -105,30 +105,18 @@ class Http2Connection(
105105
// if(lock)readLock.lock()
106106

107107
// var offset=9
108-
108+
109109
val head=read_certain(9,lock)
110110
buff.writeBytes(head)
111111

112112
val length=((head[0].toInt() and 0xff) shl 16) or ((head[1].toInt() and 0xff) shl 8) or (head[2].toInt() and 0xff)
113-
val padLength=
114-
if((head[4].toInt() and 0x8)!=0) {
115-
// offset+=1
116-
val one=ByteArray(1)
117-
conn.read(one)
118-
buff.writeBytes(one)
119-
one[0].toInt() and 0xff
120-
}
121-
else 0
122113

123114
val payload=read_certain(length,lock)
124-
val padding=read_certain(padLength,lock)
125115
// conn.read(payload)
126-
// conn.read(padding)
127116
buff.writeBytes(payload)
128-
buff.writeBytes(padding)
129117

130118
// if(lock)readLock.unlock()
131-
return parseHttp2Frame(buff.toByteArray()).first // nothing should remain
119+
return Http2Frame.parse(buff.toByteArray()).first // nothing should remain
132120
}
133121
fun readOne():Http2Frame{
134122
if(que.isNotEmpty())return que.removeFirst()
@@ -154,11 +142,11 @@ class Http2Connection(
154142

155143
do{
156144
try{
157-
val pout=parseHttp2Frame(remain)
145+
val pout=Http2Frame.parse(remain)
158146
frames.add(pout.first)
159147
remain=pout.second
160148
} catch(err:Throwable){
161-
// throw err
149+
throw err
162150
}
163151
} while(remain.size!=0)
164152
return frames

src/main/kotlin/dev/samicpp/http/http2/Http2Core.kt

Lines changed: 121 additions & 112 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,103 @@ data class Http2Frame(
2222
val length get()=payload.size
2323
val padLength get()=padding.size
2424
val prioLength get()=priotity.size
25+
26+
companion object{
27+
@JvmStatic
28+
fun parse(buff: ByteArray):Pair<Http2Frame,ByteArray>{
29+
if(buff.size<9)throw Http2Error.MalformedFrame("frame was ${buff.size}")
30+
var start=0
31+
val length=((buff[0].toInt() and 0xff) shl 16) or ((buff[1].toInt() and 0xff) shl 8) or (buff[2].toInt() and 0xff)
32+
val opcode=buff[3].toInt() and 0xff
33+
val flags=buff[4].toInt() and 0xff
34+
val streamID=((buff[5].toInt() and 0xff) shl 24) or ((buff[6].toInt() and 0xff) shl 16) or
35+
((buff[7].toInt() and 0xff) shl 8) or (buff[8].toInt() and 0xff)
36+
37+
val padLength=
38+
if((flags and 0x8)!=0){ // padded
39+
val b=buff[start].toInt() and 0xff
40+
start+=1
41+
b
42+
} else { 0 }
43+
44+
val frameLength=9+length
45+
46+
// println("length = $length")
47+
48+
val priority=
49+
if((flags and 0x20)!=0){
50+
val p=buff.copyOfRange(start, start+5)
51+
start+=5
52+
p
53+
} else {
54+
ByteArray(0)
55+
}
56+
57+
val payload=buff.copyOfRange(9+start, 9+length-padLength)
58+
// if((flags and 0x20)==0)buff.copyOfRange(9+start, 9+start+length-padLength)
59+
// else buff.copyOfRange(start+5, start+length)
60+
61+
val padding=buff.copyOfRange(9+length-padLength, 9+length)
62+
63+
val remaining=buff.copyOfRange(frameLength, buff.size)
64+
65+
val (type,stringType)=when(opcode){
66+
0->Http2FrameType.Data to "Data"
67+
1->Http2FrameType.Headers to "Headers"
68+
2->Http2FrameType.Priority to "Priority"
69+
3->Http2FrameType.RstStream to "RstStream"
70+
4->Http2FrameType.Settings to "Settings"
71+
5->Http2FrameType.PushPromise to "PushPromise"
72+
6->Http2FrameType.Ping to "Ping"
73+
7->Http2FrameType.Goaway to "Goaway"
74+
8->Http2FrameType.WindowUpdate to "WindowUpdate"
75+
9->Http2FrameType.Continuation to "Continuation"
76+
else->Http2FrameType.Unknown to "Unkown"
77+
}
78+
79+
val settings:Http2Settings=
80+
if(opcode==0x4){
81+
// var header_table_size:Int?=null
82+
// var enable_push:Int?=null
83+
// var max_concurrent_streams:Int?=null
84+
// var initial_window_size:Int?=null
85+
// var max_frame_size:Int?=null
86+
// var max_header_list_size:Int?=null
87+
// for(i in 0 until payload.size step 6){val it=payload.copyOfRange(i,6) // more efficient?
88+
// // buff.asList().chunked(6).forEach{
89+
// val name=((it[0].toInt() and 0xff) shl 8) or (it[1].toInt() and 0xff)
90+
// val value=((it[2].toInt() and 0xff) shl 24) or ((it[3].toInt() and 0xff) shl 16) or
91+
// ((it[4].toInt() and 0xff) shl 8) or (it[5].toInt() and 0xff)
92+
// when(name){
93+
// 1->header_table_size=value
94+
// 2->enable_push=value
95+
// 3->max_concurrent_streams=value
96+
// 4->initial_window_size=value
97+
// 5->max_frame_size=value
98+
// 6->max_header_list_size=value
99+
// }
100+
// }
101+
// Http2Settings(header_table_size, enable_push, max_concurrent_streams, initial_window_size, max_frame_size, max_header_list_size)
102+
Http2Settings.parse(payload)
103+
} else {
104+
Http2Settings(null,null,null,null,null,null)
105+
}
106+
107+
return Http2Frame(
108+
frameLength,
109+
streamID,
110+
opcode,
111+
type,
112+
stringType,
113+
flags,
114+
payload,
115+
padding,
116+
priority,
117+
settings,
118+
) to
119+
remaining
120+
}
121+
}
25122
}
26123

27124
data class Http2Settings(
@@ -58,121 +155,33 @@ data class Http2Settings(
58155
}
59156
return buff.toByteArray()
60157
}
61-
}
62-
63-
fun parseHttp2Frame(buff: ByteArray):Pair<Http2Frame,ByteArray>{
64-
if(buff.size<9)throw Http2Error.MalformedFrame("frame was ${buff.size}")
65-
var start=9
66-
val length=((buff[0].toInt() and 0xff) shl 16) or ((buff[1].toInt() and 0xff) shl 8) or (buff[2].toInt() and 0xff)
67-
val opcode=buff[3].toInt() and 0xff
68-
val flags=buff[4].toInt() and 0xff
69-
val streamID=((buff[5].toInt() and 0xff) shl 24) or ((buff[6].toInt() and 0xff) shl 16) or
70-
((buff[7].toInt() and 0xff) shl 8) or (buff[8].toInt() and 0xff)
71-
72-
val padLength=
73-
if((flags and 0x8)!=0){ // padded
74-
start+=1
75-
buff[9].toInt() and 0xff
76-
} else { 0 }
77158

78-
val frameLength=start+length+padLength
79-
80-
// println("length = $length")
81-
82-
val priority=
83-
if((flags and 0x20)!=0){
84-
buff.copyOfRange(start, start+5)
85-
} else {
86-
ByteArray(0)
87-
}
88-
89-
val payload=
90-
if((flags and 0x20)==0)buff.copyOfRange(start, start+length)
91-
else buff.copyOfRange(start+5, start+length)
92-
93-
val padding=buff.copyOfRange(start+length, start+length+padLength)
94-
95-
val remaining=buff.copyOfRange(frameLength, buff.size)
96-
97-
val (type,stringType)=when(opcode){
98-
0->Http2FrameType.Data to "Data"
99-
1->Http2FrameType.Headers to "Headers"
100-
2->Http2FrameType.Priority to "Priority"
101-
3->Http2FrameType.RstStream to "RstStream"
102-
4->Http2FrameType.Settings to "Settings"
103-
5->Http2FrameType.PushPromise to "PushPromise"
104-
6->Http2FrameType.Ping to "Ping"
105-
7->Http2FrameType.Goaway to "Goaway"
106-
8->Http2FrameType.WindowUpdate to "WindowUpdate"
107-
9->Http2FrameType.Continuation to "Continuation"
108-
else->Http2FrameType.Unknown to "Unkown"
109-
}
110-
111-
val settings:Http2Settings=
112-
if(opcode==0x4){
113-
// var header_table_size:Int?=null
114-
// var enable_push:Int?=null
115-
// var max_concurrent_streams:Int?=null
116-
// var initial_window_size:Int?=null
117-
// var max_frame_size:Int?=null
118-
// var max_header_list_size:Int?=null
119-
// for(i in 0 until payload.size step 6){val it=payload.copyOfRange(i,6) // more efficient?
120-
// // buff.asList().chunked(6).forEach{
121-
// val name=((it[0].toInt() and 0xff) shl 8) or (it[1].toInt() and 0xff)
122-
// val value=((it[2].toInt() and 0xff) shl 24) or ((it[3].toInt() and 0xff) shl 16) or
123-
// ((it[4].toInt() and 0xff) shl 8) or (it[5].toInt() and 0xff)
124-
// when(name){
125-
// 1->header_table_size=value
126-
// 2->enable_push=value
127-
// 3->max_concurrent_streams=value
128-
// 4->initial_window_size=value
129-
// 5->max_frame_size=value
130-
// 6->max_header_list_size=value
131-
// }
132-
// }
133-
// Http2Settings(header_table_size, enable_push, max_concurrent_streams, initial_window_size, max_frame_size, max_header_list_size)
134-
parseHttp2Settings(payload)
135-
} else {
136-
Http2Settings(null,null,null,null,null,null)
137-
}
138-
139-
return Http2Frame(
140-
frameLength,
141-
streamID,
142-
opcode,
143-
type,
144-
stringType,
145-
flags,
146-
payload,
147-
padding,
148-
priority,
149-
settings,
150-
) to
151-
remaining
152-
}
153-
154-
fun parseHttp2Settings(buff:ByteArray):Http2Settings{
155-
var header_table_size:Int?=null
156-
var enable_push:Int?=null
157-
var max_concurrent_streams:Int?=null
158-
var initial_window_size:Int?=null
159-
var max_frame_size:Int?=null
160-
var max_header_list_size:Int?=null
161-
for(i in 0 until buff.size step 6){ val it=buff.copyOfRange(i,i+6) // more efficient?
162-
// buff.asList().chunked(6).forEach{
163-
val name=((it[0].toInt() and 0xff) shl 8) or (it[1].toInt() and 0xff)
164-
val value=((it[2].toInt() and 0xff) shl 24) or ((it[3].toInt() and 0xff) shl 16) or
165-
((it[4].toInt() and 0xff) shl 8) or (it[5].toInt() and 0xff)
166-
when(name){
167-
1->header_table_size=value
168-
2->enable_push=value
169-
3->max_concurrent_streams=value
170-
4->initial_window_size=value
171-
5->max_frame_size=value
172-
6->max_header_list_size=value
159+
companion object{
160+
@JvmStatic
161+
fun parse(buff:ByteArray):Http2Settings{
162+
var header_table_size:Int?=null
163+
var enable_push:Int?=null
164+
var max_concurrent_streams:Int?=null
165+
var initial_window_size:Int?=null
166+
var max_frame_size:Int?=null
167+
var max_header_list_size:Int?=null
168+
for(i in 0 until buff.size step 6){ val it=buff.copyOfRange(i,i+6) // more efficient?
169+
// buff.asList().chunked(6).forEach{
170+
val name=((it[0].toInt() and 0xff) shl 8) or (it[1].toInt() and 0xff)
171+
val value=((it[2].toInt() and 0xff) shl 24) or ((it[3].toInt() and 0xff) shl 16) or
172+
((it[4].toInt() and 0xff) shl 8) or (it[5].toInt() and 0xff)
173+
when(name){
174+
1->header_table_size=value
175+
2->enable_push=value
176+
3->max_concurrent_streams=value
177+
4->initial_window_size=value
178+
5->max_frame_size=value
179+
6->max_header_list_size=value
180+
}
181+
}
182+
return Http2Settings(header_table_size, enable_push, max_concurrent_streams, initial_window_size, max_frame_size, max_header_list_size)
173183
}
174184
}
175-
return Http2Settings(header_table_size, enable_push, max_concurrent_streams, initial_window_size, max_frame_size, max_header_list_size)
176185
}
177186

178187
//11.2 #autoid-88

0 commit comments

Comments
 (0)