Packages
bandit
0.5.6
1.12.0
1.11.1
1.11.0
1.10.4
1.10.3
1.10.2
1.10.1
1.10.0
retired
1.9.0
1.8.0
1.7.0
1.6.11
1.6.10
1.6.9
1.6.8
1.6.7
1.6.6
1.6.5
1.6.4
1.6.3
1.6.2
1.6.1
1.6.0
1.5.7
1.5.6
1.5.5
1.5.4
1.5.3
1.5.2
1.5.1
1.5.0
1.4.2
1.4.1
1.4.0
1.3.0
1.2.3
1.2.2
1.2.1
1.2.0
1.1.3
1.1.2
1.1.1
1.1.0
1.0.0
1.0.0-pre.18
1.0.0-pre.17
1.0.0-pre.16
1.0.0-pre.15
1.0.0-pre.14
1.0.0-pre.13
1.0.0-pre.12
1.0.0-pre.11
1.0.0-pre.10
1.0.0-pre.9
1.0.0-pre.8
1.0.0-pre.7
1.0.0-pre.6
1.0.0-pre.5
1.0.0-pre.4
1.0.0-pre.3
1.0.0-pre.2
1.0.0-pre.1
0.7.7
0.7.6
0.7.5
0.7.4
0.7.3
0.7.2
0.7.1
0.7.0
0.6.11
0.6.10
0.6.9
0.6.8
0.6.7
0.6.6
0.6.5
0.6.4
0.6.3
0.6.2
0.6.1
0.6.0
0.5.11
0.5.10
0.5.9
0.5.8
0.5.7
0.5.6
0.5.5
0.5.4
0.5.3
0.5.2
0.5.1
0.5.0
0.4.10
0.4.9
0.4.8
0.4.7
0.4.6
0.4.5
0.4.4
0.4.3
0.4.2
0.4.1
0.4.0
0.3.9
0.3.8
0.3.7
0.3.6
0.3.5
0.3.4
0.3.3
0.3.2
0.2.3
0.2.2
0.2.1
0.2.0
0.1.1
0.1.0
A pure-Elixir HTTP server built for Plug & WebSock apps
Security advisory:
This version has known vulnerabilities.
View advisories
Current section
Files
Jump to
Current section
Files
lib/bandit/http2/stream_collection.ex
defmodule Bandit.HTTP2.StreamCollection do
@moduledoc false
# Represents a collection of HTTP/2 streams, accessible by stream id or pid.
# Provides the ability to track streams with any identifier, even though it
# only manages explicit state for existing (current) streams.
require Integer
alias Bandit.HTTP2.Stream
defstruct initial_recv_window_size: 65_535,
initial_send_window_size: 65_535,
max_concurrent_streams: :infinity,
last_local_stream_id: 0,
last_remote_stream_id: 0,
streams: %{}
@typedoc "A collection of Stream structs, accessisble by id or pid"
@type t :: %__MODULE__{
initial_recv_window_size: non_neg_integer(),
initial_send_window_size: non_neg_integer(),
max_concurrent_streams: non_neg_integer() | :infinity,
last_remote_stream_id: Stream.stream_id(),
last_local_stream_id: Stream.stream_id(),
streams: %{Stream.stream_id() => Stream.t()}
}
@spec update_max_concurrent_streams(t(), non_neg_integer()) :: t()
def update_max_concurrent_streams(collection, max_concurrent_streams) do
%{collection | max_concurrent_streams: max_concurrent_streams}
end
@spec update_initial_send_window_size(t(), non_neg_integer()) :: t()
def update_initial_send_window_size(collection, initial_send_window_size) do
delta = initial_send_window_size - collection.initial_send_window_size
streams =
collection.streams
|> Enum.map(fn
{id, %Stream{state: state} = stream} when state in [:open, :remote_closed] ->
{id, %{stream | send_window_size: stream.send_window_size + delta}}
{id, stream} ->
{id, stream}
end)
|> Map.new()
%{collection | streams: streams, initial_send_window_size: initial_send_window_size}
end
@spec get_stream(t(), Stream.stream_id()) :: {:ok, Stream.t()}
def get_stream(collection, stream_id) do
case Map.get(collection.streams, stream_id) do
%Stream{} = stream ->
{:ok, stream}
nil ->
state =
if (Integer.is_even(stream_id) && stream_id <= collection.last_local_stream_id) ||
(Integer.is_odd(stream_id) && stream_id <= collection.last_remote_stream_id) do
:closed
else
:idle
end
{:ok,
%Stream{
stream_id: stream_id,
state: state,
recv_window_size: collection.initial_recv_window_size,
send_window_size: collection.initial_send_window_size
}}
end
end
@spec get_active_stream_by_pid(t(), pid()) :: {:ok, Stream.t()} | {:error, :no_stream}
def get_active_stream_by_pid(collection, pid) do
case Enum.find(collection.streams, fn {_stream_id, stream} -> stream.pid == pid end) do
{_, %Stream{} = stream} -> {:ok, stream}
nil -> {:error, :no_stream}
end
end
@spec put_stream(t(), Stream.t()) :: {:ok, t()} | {:error, :invalid_stream}
def put_stream(collection, %Stream{state: state} = stream) when state in [:idle, :closed] do
case stream.pid do
nil -> {:ok, %{collection | streams: Map.delete(collection.streams, stream.stream_id)}}
_pid -> {:error, :invalid_stream}
end
end
def put_stream(collection, %Stream{} = stream) do
case stream.pid do
nil ->
{:error, :invalid_stream}
_pid ->
streams = Map.put(collection.streams, stream.stream_id, stream)
last_local_stream_id =
if Integer.is_even(stream.stream_id) do
max(stream.stream_id, collection.last_local_stream_id)
else
collection.last_local_stream_id
end
last_remote_stream_id =
if Integer.is_odd(stream.stream_id) do
max(stream.stream_id, collection.last_remote_stream_id)
else
collection.last_remote_stream_id
end
{:ok,
%{
collection
| streams: streams,
last_remote_stream_id: last_remote_stream_id,
last_local_stream_id: last_local_stream_id
}}
end
end
@spec can_send_new_push_promise(t()) :: :ok | {:error, :max_concurrent_streams}
def can_send_new_push_promise(collection) do
case collection.max_concurrent_streams do
:infinity ->
:ok
max_concurrent_streams ->
# Only count server-started (ie: push) streams per RFC7540ยง6.5.2
current_push_stream_count =
collection.streams
|> Map.values()
|> Enum.count(&(&1.state in [:open, :remote_closed] && Integer.is_even(&1.stream_id)))
if max_concurrent_streams > current_push_stream_count do
:ok
else
{:error, :max_concurrent_streams}
end
end
end
@spec next_local_stream_id(t()) :: Stream.stream_id()
def next_local_stream_id(collection), do: collection.last_local_stream_id + 2
@spec last_remote_stream_id(t()) :: Stream.stream_id()
def last_remote_stream_id(collection), do: collection.last_remote_stream_id
end