defmodule Bandit.HTTP2.Frame.Headers do @moduledoc false import Bandit.HTTP2.Frame.Flags alias Bandit.HTTP2.{Connection, Errors, Frame, Stream} defstruct stream_id: nil, end_stream: false, end_headers: false, exclusive_dependency: false, stream_dependency: nil, weight: nil, fragment: nil @typedoc "An HTTP/2 HEADERS frame" @type t :: %__MODULE__{ stream_id: Stream.stream_id(), end_stream: boolean(), end_headers: boolean(), exclusive_dependency: boolean(), stream_dependency: Stream.stream_id() | nil, weight: non_neg_integer() | nil, fragment: iodata() } @end_stream_bit 0 @end_headers_bit 2 @padding_bit 3 @priority_bit 5 @spec deserialize(Frame.flags(), Stream.stream_id(), iodata()) :: {:ok, t()} | {:error, Connection.error()} def deserialize(_flags, 0, _payload) do {:error, {:connection, Errors.protocol_error(), "HEADERS frame with zero stream_id (RFC9113§6.2)"}} end # Padding and priority def deserialize( flags, stream_id, <> ) when set?(flags, @padding_bit) and set?(flags, @priority_bit) and byte_size(rest) >= padding_length do {:ok, %__MODULE__{ stream_id: stream_id, end_stream: set?(flags, @end_stream_bit), end_headers: set?(flags, @end_headers_bit), exclusive_dependency: exclusive_dependency == 0x01, stream_dependency: stream_dependency, weight: weight, fragment: binary_part(rest, 0, byte_size(rest) - padding_length) }} end # Padding but not priority def deserialize(flags, stream_id, <>) when set?(flags, @padding_bit) and clear?(flags, @priority_bit) and byte_size(rest) >= padding_length do {:ok, %__MODULE__{ stream_id: stream_id, end_stream: set?(flags, @end_stream_bit), end_headers: set?(flags, @end_headers_bit), fragment: binary_part(rest, 0, byte_size(rest) - padding_length) }} end # Any other case where padding is set def deserialize(flags, _stream_id, <<_padding_length::8, _rest::binary>>) when set?(flags, @padding_bit) do {:error, {:connection, Errors.protocol_error(), "HEADERS frame with invalid padding length (RFC9113§6.2)"}} end def deserialize( flags, stream_id, <> ) when set?(flags, @priority_bit) do {:ok, %__MODULE__{ stream_id: stream_id, end_stream: set?(flags, @end_stream_bit), end_headers: set?(flags, @end_headers_bit), exclusive_dependency: exclusive_dependency == 0x01, stream_dependency: stream_dependency, weight: weight, fragment: fragment }} end def deserialize(flags, stream_id, <>) when clear?(flags, @priority_bit) and clear?(flags, @padding_bit) do {:ok, %__MODULE__{ stream_id: stream_id, end_stream: set?(flags, @end_stream_bit), end_headers: set?(flags, @end_headers_bit), fragment: fragment }} end defimpl Frame.Serializable do alias Bandit.HTTP2.Frame.{Continuation, Headers} @end_stream_bit 0 @end_headers_bit 2 def serialize( %Headers{exclusive_dependency: false, stream_dependency: nil, weight: nil} = frame, max_frame_size ) do flags = if frame.end_stream, do: [@end_stream_bit], else: [] fragment_length = IO.iodata_length(frame.fragment) if fragment_length <= max_frame_size do [{0x1, set([@end_headers_bit | flags]), frame.stream_id, frame.fragment}] else <> = IO.iodata_to_binary(frame.fragment) [ {0x1, set(flags), frame.stream_id, this_frame} | Frame.Serializable.serialize( %Continuation{ stream_id: frame.stream_id, fragment: rest }, max_frame_size ) ] end end end end