Packages
mist
6.0.3
6.0.3
6.0.2
6.0.1
6.0.0
5.0.4
5.0.3
5.0.2
5.0.1
5.0.0
5.0.0-rc1
4.0.7
4.0.6
4.0.5
4.0.4
4.0.3
4.0.2
4.0.1
4.0.0
3.0.0
2.0.0
1.2.0
1.1.0
1.0.0
1.0.0-rc3
1.0.0-rc2
1.0.0-rc1
0.17.0
0.15.0
0.14.3
0.14.2
0.14.1
0.14.0
0.13.2
0.13.1
0.13.0
0.12.0
0.11.0
0.10.0
0.9.4
0.9.3
0.9.2
0.9.1
0.9.0
0.8.3
0.8.2
0.8.1
0.8.0
0.7.1
0.7.0
0.6.1
0.6.0
0.5.2
0.5.1
0.5.0
0.4.5
0.4.4
0.4.3
0.4.2
0.4.1
0.4.0
0.3.3
0.3.2
0.3.1
0.3.0
0.2.1
0.2.0
0.1.3
a misty Gleam web server
Current section
Files
Jump to
Current section
Files
src/mist/internal/http2/frame.gleam
import gleam/bit_array
import gleam/list
import gleam/option.{type Option, None, Some}
import gleam/result
import logging
pub opaque type StreamIdentifier(phantom) {
StreamIdentifier(Int)
}
pub fn stream_identifier(value: Int) -> StreamIdentifier(Frame) {
StreamIdentifier(value)
}
pub fn get_stream_identifier(identifier: StreamIdentifier(phantom)) -> Int {
let StreamIdentifier(value) = identifier
value
}
pub type HeaderPriority {
HeaderPriority(
exclusive: Bool,
stream_dependency: StreamIdentifier(Frame),
weight: Int,
)
}
pub type Data {
Complete(BitArray)
Continued(BitArray)
}
pub type PushState {
Enabled
Disabled
}
pub type Setting {
HeaderTableSize(Int)
ServerPush(PushState)
MaxConcurrentStreams(Int)
InitialWindowSize(Int)
MaxFrameSize(Int)
MaxHeaderListSize(Int)
}
pub type Frame {
Data(data: BitArray, end_stream: Bool, identifier: StreamIdentifier(Frame))
Header(
data: Data,
end_stream: Bool,
identifier: StreamIdentifier(Frame),
priority: Option(HeaderPriority),
)
Priority(
exclusive: Bool,
identifier: StreamIdentifier(Frame),
stream_dependency: StreamIdentifier(Frame),
weight: Int,
)
Termination(error: ConnectionError, identifier: StreamIdentifier(Frame))
Settings(ack: Bool, settings: List(Setting))
PushPromise(
data: Data,
identifier: StreamIdentifier(Frame),
promised_stream_id: StreamIdentifier(Frame),
)
Ping(ack: Bool, data: BitArray)
GoAway(
data: BitArray,
error: ConnectionError,
last_stream_id: StreamIdentifier(Frame),
)
WindowUpdate(amount: Int, identifier: StreamIdentifier(Frame))
Continuation(data: Data, identifier: StreamIdentifier(Frame))
}
pub type ConnectionError {
NoError
ProtocolError
InternalError
FlowControlError
SettingsTimeout
StreamClosed
FrameSizeError
RefusedStream
Cancel
CompressionError
ConnectError
EnhanceYourCalm
InadequateSecurity
Http11Required
Unsupported(Int)
}
pub fn decode(frame: BitArray) -> Result(#(Frame, BitArray), ConnectionError) {
case frame {
<<
length:size(24),
frame_type:size(8),
flags:bits-size(8),
_reserved:size(1),
identifier:size(31),
payload:bytes-size(length),
rest:bits,
>> -> {
case frame_type {
0 -> parse_data(identifier, flags, length, payload)
1 -> parse_header(identifier, flags, length, payload)
2 -> parse_priority(identifier, flags, length, payload)
3 -> parse_termination(identifier, flags, length, payload)
4 -> parse_settings(identifier, flags, length, payload)
5 -> parse_push_promise(identifier, flags, length, payload)
6 -> parse_ping(identifier, flags, length, payload)
7 -> parse_go_away(identifier, flags, length, payload)
8 -> parse_window_update(identifier, flags, length, payload)
9 -> parse_continuation(identifier, flags, length, payload)
_ -> Error(ProtocolError)
}
|> result.map(fn(frame) { #(frame, rest) })
}
<<
length:size(24),
_frame_type:size(8),
_flags:size(8),
_reserved:size(1),
_identifier:size(31),
rest:bits,
>> -> {
case bit_array.byte_size(rest) < length {
True -> Error(NoError)
False -> Error(ProtocolError)
}
}
_ -> Error(ProtocolError)
}
}
fn parse_data(
identifier: Int,
flags: BitArray,
length: Int,
payload: BitArray,
) -> Result(Frame, ConnectionError) {
case <<flags:bits, payload:bits>> {
<<
_unused:size(4),
padding:size(1),
_unused:size(2),
end_stream:size(1),
pad_length:size(padding)-unit(8),
data_and_padding:bits,
>>
if identifier != 0
-> {
let data_length = case padding {
1 -> length - pad_length
0 -> length
_ -> panic as "Somehow a bit was neither 0 nor 1"
}
case data_and_padding {
<<data:bytes-size(data_length), _padding:bits>> -> {
Ok(Data(
data: data,
end_stream: end_stream == 1,
identifier: stream_identifier(identifier),
))
}
_ -> Error(ProtocolError)
}
}
_ -> Error(ProtocolError)
}
}
fn parse_header(
identifier: Int,
flags: BitArray,
length: Int,
payload: BitArray,
) -> Result(Frame, ConnectionError) {
case <<flags:bits, payload:bits>> {
<<
_unused:size(2),
priority:size(1),
_unused:size(1),
padded:size(1),
end_headers:size(1),
_unused:size(1),
end_stream:size(1),
pad_length:size(padded)-unit(8),
exclusive:size(priority),
stream_dependency:size(priority)-unit(31),
weight:size(priority)-unit(8),
data_and_padding:bits,
>>
if identifier != 0 && pad_length < length
-> {
let data_length = case padded, priority {
1, 1 -> length - pad_length - 6
1, 0 -> length - pad_length - 1
0, 1 -> length - 5
0, 0 -> length
_, _ -> panic as "Somehow a bit was set to neither 0 nor 1"
}
case data_and_padding {
<<data:bytes-size(data_length), _padding:bits>> -> {
Ok(
Header(
data: case end_headers {
1 -> Complete(data)
0 -> Continued(data)
_ -> panic as "Somehow a bit was set to neither 0 nor 1"
},
end_stream: { end_stream == 1 },
identifier: stream_identifier(identifier),
priority: case priority == 1 {
True ->
Some(HeaderPriority(
exclusive: { exclusive == 1 },
stream_dependency: stream_identifier(stream_dependency),
weight: weight,
))
False -> None
},
),
)
}
_ -> {
logging.log(logging.Debug, "oh noes!")
Error(ProtocolError)
}
}
}
_ -> {
Error(ProtocolError)
}
}
}
fn parse_priority(
identifier: Int,
flags: BitArray,
length: Int,
payload: BitArray,
) -> Result(Frame, ConnectionError) {
case length, <<flags:bits, payload:bits>> {
5,
<<
_unused:size(8),
exclusive:size(1),
dependency:size(31),
weight:size(8),
>>
if identifier != 0
-> {
Ok(Priority(
exclusive: exclusive == 1,
identifier: stream_identifier(identifier),
stream_dependency: stream_identifier(dependency),
weight: weight,
))
}
5, _ -> Error(ProtocolError)
_, _ -> Error(FrameSizeError)
}
}
fn parse_termination(
identifier: Int,
flags: BitArray,
length: Int,
payload: BitArray,
) -> Result(Frame, ConnectionError) {
case length, <<flags:bits, payload:bits>> {
4, <<_unused:size(8), error:size(32)>> if identifier != 0 -> {
Ok(Termination(
error: get_error(error),
identifier: stream_identifier(identifier),
))
}
4, _ -> Error(ProtocolError)
_, _ -> Error(FrameSizeError)
}
}
fn parse_settings(
identifier: Int,
flags: BitArray,
length: Int,
payload: BitArray,
) -> Result(Frame, ConnectionError) {
case length % 6, <<flags:bits, payload:bits>> {
0, <<_unused:size(7), ack:size(1), settings:bytes-size(length)>>
if identifier == 0
-> {
use settings <- result.try(get_settings(settings, []))
Ok(Settings(ack: ack == 1, settings: settings))
}
0, _ -> Error(ProtocolError)
_, _ -> Error(FrameSizeError)
}
}
fn parse_push_promise(
identifier: Int,
flags: BitArray,
length: Int,
payload: BitArray,
) -> Result(Frame, ConnectionError) {
case <<flags:bits, payload:bits>> {
<<
_unused:size(4),
padded:size(1),
end_headers:size(1),
_unused:size(2),
pad_length:size(padded)-unit(8),
_reserved:size(1),
promised_identifier:size(31),
data:bytes-size(length),
_padding:bytes-size(pad_length),
>>
if identifier != 0
-> {
Ok(PushPromise(
data: case end_headers == 1 {
True -> Complete(data)
False -> Continued(data)
},
identifier: stream_identifier(identifier),
promised_stream_id: stream_identifier(promised_identifier),
))
}
_ -> Error(ProtocolError)
}
}
fn parse_ping(
identifier: Int,
flags: BitArray,
length: Int,
payload: BitArray,
) -> Result(Frame, ConnectionError) {
case length, <<flags:bits, payload:bits>> {
8, <<_unused:size(7), ack:size(1), data:bits-size(64)>> if identifier == 0 -> {
Ok(Ping(ack: ack == 1, data: data))
}
8, _ -> Error(ProtocolError)
_, _ -> Error(FrameSizeError)
}
}
fn parse_go_away(
identifier: Int,
flags: BitArray,
length: Int,
payload: BitArray,
) -> Result(Frame, ConnectionError) {
case <<flags:bits, payload:bits>> {
<<
_unused:size(8),
_reserved:size(1),
last_stream_id:size(31),
error:size(32),
data:bytes-size(length),
>>
if identifier == 0
-> {
Ok(GoAway(
data: data,
error: get_error(error),
last_stream_id: stream_identifier(last_stream_id),
))
}
_ -> Error(ProtocolError)
}
}
fn parse_window_update(
identifier: Int,
flags: BitArray,
length: Int,
payload: BitArray,
) -> Result(Frame, ConnectionError) {
case length, <<flags:bits, payload:bits>> {
4, <<_unused:size(8), _reserved:size(1), window_size:size(31)>>
if window_size != 0
-> {
Ok(WindowUpdate(
amount: window_size,
identifier: stream_identifier(identifier),
))
}
4, _ -> Error(FrameSizeError)
_, _ -> Error(ProtocolError)
}
}
fn parse_continuation(
identifier: Int,
flags: BitArray,
length: Int,
payload: BitArray,
) -> Result(Frame, ConnectionError) {
case <<flags:bits, payload:bits>> {
<<
_unused:size(5),
end_headers:size(1),
_unused:size(2),
data:bytes-size(length),
>>
if identifier != 0
-> {
Ok(Continuation(
data: case end_headers == 1 {
True -> Complete(data)
False -> Continued(data)
},
identifier: stream_identifier(identifier),
))
}
_ -> Error(ProtocolError)
}
}
pub fn encode(frame: Frame) -> BitArray {
case frame {
Data(data, end_stream, StreamIdentifier(identifier)) -> {
let length = bit_array.byte_size(data)
let end = from_bool(end_stream)
<<
length:size(24),
0:size(8),
0:size(4),
0:size(1),
0:size(2),
end:size(1),
0:size(1),
identifier:size(31),
data:bits,
>>
}
Header(data, end_stream, StreamIdentifier(identifier), priority) -> {
let #(end_header, data) = encode_data(data)
let length = bit_array.byte_size(data)
let end = from_bool(end_stream)
let priority_flags = encode_priority(priority)
let has_priority = from_bool(option.is_some(priority))
<<
length:size(24),
1:size(8),
0:size(2),
has_priority:size(1),
0:size(1),
0:size(1),
end_header:size(1),
0:size(1),
end:size(1),
0:size(1),
identifier:size(31),
priority_flags:bits,
data:bits,
>>
}
Priority(
exclusive,
StreamIdentifier(identifier),
StreamIdentifier(dependency),
weight,
) -> {
let exclusive = from_bool(exclusive)
<<
5:size(24),
2:size(2),
0:size(8),
0:size(1),
identifier:size(31),
exclusive:size(1),
dependency:size(31),
weight:size(8),
>>
}
Termination(error, StreamIdentifier(identifier)) -> {
let error_code = encode_error(error)
<<
4:size(24),
3:size(8),
0:size(8),
0:size(1),
identifier:size(31),
error_code:size(32),
>>
}
Settings(ack, settings) -> {
let ack = from_bool(ack)
let settings = encode_settings(settings)
let length = bit_array.byte_size(settings)
<<
length:size(24),
4:size(8),
0:size(7),
ack:size(1),
0:size(1),
0:size(31),
settings:bits,
>>
}
PushPromise(
data,
StreamIdentifier(identifier),
StreamIdentifier(promised_identifier),
) -> {
let #(end_headers, data) = encode_data(data)
<<
0:size(24),
5:size(8),
0:size(4),
0:size(0),
end_headers:size(1),
0:size(2),
0:size(1),
identifier:size(31),
0:size(1),
promised_identifier:size(31),
data:bits,
>>
}
Ping(ack, data) -> {
let ack = from_bool(ack)
<<
0:size(24),
6:size(8),
0:size(7),
ack:size(1),
0:size(1),
0:size(31),
data:bits,
>>
}
GoAway(data, error, StreamIdentifier(last_stream_id)) -> {
let error = encode_error(error)
let payload_size = bit_array.byte_size(data)
<<
payload_size:size(24),
7:size(8),
0:size(8),
0:size(1),
0:size(31),
0:size(1),
last_stream_id:size(31),
error:size(32),
data:bits,
>>
}
WindowUpdate(amount, StreamIdentifier(identifier)) -> {
<<
4:size(24),
8:size(8),
0:size(8),
0:size(1),
identifier:size(31),
0:size(1),
amount:size(31),
>>
}
Continuation(data, StreamIdentifier(identifier)) -> {
let #(end_headers, data) = encode_data(data)
let payload_size = bit_array.byte_size(data)
<<
payload_size:size(24),
9:size(8),
0:size(5),
end_headers:size(1),
0:size(2),
0:size(1),
identifier:size(31),
data:bits,
>>
}
}
}
fn get_error(value: Int) -> ConnectionError {
case value {
0 -> NoError
1 -> ProtocolError
2 -> InternalError
3 -> FlowControlError
4 -> SettingsTimeout
5 -> StreamClosed
6 -> FrameSizeError
7 -> RefusedStream
8 -> Cancel
9 -> CompressionError
10 -> ConnectError
11 -> EnhanceYourCalm
12 -> InadequateSecurity
13 -> Http11Required
n -> Unsupported(n)
}
}
fn get_settings(
data: BitArray,
acc: List(Setting),
) -> Result(List(Setting), ConnectionError) {
case data {
<<>> -> Ok(acc)
<<identifier:size(16), value:size(32), rest:bits>> -> {
case get_setting(identifier, value) {
Ok(setting) -> get_settings(rest, [setting, ..acc])
Error(err) -> Error(err)
}
}
_ -> Error(ProtocolError)
}
}
fn get_setting(identifier: Int, value: Int) -> Result(Setting, ConnectionError) {
case identifier {
1 -> Ok(HeaderTableSize(value))
2 ->
Ok(
ServerPush(case value {
0 -> Disabled
1 -> Enabled
_ -> panic as "Somehow a bit was neither 0 nor 1"
}),
)
3 -> Ok(MaxConcurrentStreams(value))
4 -> {
case value {
n if n > 2_147_483_647 -> Error(FlowControlError)
_ -> Ok(InitialWindowSize(value))
}
}
5 -> {
case value {
n if n > 16_777_215 -> Error(ProtocolError)
_ -> Ok(MaxFrameSize(value))
}
}
6 -> Ok(MaxHeaderListSize(value))
_ -> Error(ProtocolError)
}
}
fn from_bool(bool: Bool) -> Int {
case bool {
True -> 1
False -> 0
}
}
fn encode_priority(priority: Option(HeaderPriority)) -> BitArray {
case priority {
Some(HeaderPriority(exclusive, StreamIdentifier(dependency), weight)) -> {
let exclusive = from_bool(exclusive)
<<exclusive:size(1), dependency:size(31), weight:size(8)>>
}
None -> <<>>
}
}
fn encode_data(data: Data) -> #(Int, BitArray) {
case data {
Complete(data) -> #(1, data)
Continued(data) -> #(0, data)
}
}
fn encode_error(error: ConnectionError) -> Int {
case error {
NoError -> 0
ProtocolError -> 1
InternalError -> 2
FlowControlError -> 3
SettingsTimeout -> 4
StreamClosed -> 5
FrameSizeError -> 6
RefusedStream -> 7
Cancel -> 8
CompressionError -> 9
ConnectError -> 10
EnhanceYourCalm -> 11
InadequateSecurity -> 12
Http11Required -> 13
// TODO
Unsupported(..) -> 69
}
}
fn encode_settings(settings: List(Setting)) -> BitArray {
list.fold(settings, <<>>, fn(acc, setting) {
case setting {
HeaderTableSize(value) ->
bit_array.append(acc, <<1:size(16), value:size(32)>>)
ServerPush(Enabled) -> bit_array.append(acc, <<2:size(16), 1:size(32)>>)
ServerPush(Disabled) -> bit_array.append(acc, <<2:size(16), 0:size(32)>>)
MaxConcurrentStreams(value) ->
bit_array.append(acc, <<3:size(16), value:size(32)>>)
InitialWindowSize(value) ->
bit_array.append(acc, <<4:size(16), value:size(32)>>)
MaxFrameSize(value) ->
bit_array.append(acc, <<5:size(16), value:size(32)>>)
MaxHeaderListSize(value) ->
bit_array.append(acc, <<6:size(16), value:size(32)>>)
}
})
}
pub fn settings_ack() -> Frame {
Settings(ack: True, settings: [])
}