defmodule ThousandIsland.Socket do @moduledoc """ Encapsulates a client connection's underlying socket, providing a facility to read, write, and otherwise manipulate a connection from a client. """ @enforce_keys [:socket, :transport_module, :read_timeout, :span] defstruct @enforce_keys @typedoc "A reference to a socket along with metadata describing how to use it" @type t :: %__MODULE__{ socket: ThousandIsland.Transport.socket(), transport_module: module(), read_timeout: timeout(), span: ThousandIsland.Telemetry.t() } @doc false @spec new( ThousandIsland.Transport.socket(), ThousandIsland.ServerConfig.t(), ThousandIsland.Telemetry.t() ) :: t() def new(raw_socket, server_config, span) do %__MODULE__{ socket: raw_socket, transport_module: server_config.transport_module, read_timeout: server_config.read_timeout, span: span } end @doc """ Handshakes the underlying socket if it is required (as in the case of SSL sockets, for example). This is normally called internally by `ThousandIsland.Handler` and does not need to be called by implementations which are based on `ThousandIsland.Handler` """ @spec handshake(t()) :: ThousandIsland.Transport.on_handshake() def handshake(%__MODULE__{} = socket) do case socket.transport_module.handshake(socket.socket) do {:ok, _} -> {:ok, socket} {:error, reason} = err -> ThousandIsland.Telemetry.stop_span(socket.span, %{}, %{error: reason}) err end end @doc """ Returns available bytes on the given socket. Up to `length` bytes will be returned (0 can be passed in to get the next 'available' bytes, typically the next packet). If insufficient bytes are available, the function can wait `timeout` milliseconds for data to arrive. """ @spec recv(t(), non_neg_integer(), timeout() | nil) :: ThousandIsland.Transport.on_recv() def recv(%__MODULE__{} = socket, length \\ 0, timeout \\ nil) do case socket.transport_module.recv(socket.socket, length, timeout || socket.read_timeout) do {:ok, data} = ok -> ThousandIsland.Telemetry.untimed_span_event(socket.span, :recv, %{data: data}) ok {:error, reason} = err -> ThousandIsland.Telemetry.span_event(socket.span, :recv_error, %{error: reason}) err end end @doc """ Sends the given data (specified as a binary or an IO list) on the given socket. """ @spec send(t(), iodata()) :: ThousandIsland.Transport.on_send() def send(%__MODULE__{} = socket, data) do case socket.transport_module.send(socket.socket, data) do :ok -> ThousandIsland.Telemetry.untimed_span_event(socket.span, :send, %{data: data}) :ok {:error, reason} = err -> ThousandIsland.Telemetry.span_event(socket.span, :send_error, %{data: data, error: reason}) err end end @doc """ Sends the contents of the given file based on the provided offset & length """ @spec sendfile(t(), String.t(), non_neg_integer(), non_neg_integer()) :: ThousandIsland.Transport.on_sendfile() def sendfile(%__MODULE__{} = socket, filename, offset, length) do case socket.transport_module.sendfile(socket.socket, filename, offset, length) do {:ok, bytes_written} = ok -> measurements = %{filename: filename, offset: offset, bytes_written: bytes_written} ThousandIsland.Telemetry.untimed_span_event(socket.span, :sendfile, measurements) ok {:error, reason} = err -> measurements = %{filename: filename, offset: offset, length: length, error: reason} ThousandIsland.Telemetry.span_event(socket.span, :sendfile_error, measurements) err end end @doc """ Shuts down the socket in the given direction. """ @spec shutdown(t(), ThousandIsland.Transport.way()) :: ThousandIsland.Transport.on_shutdown() def shutdown(%__MODULE__{} = socket, way) do ThousandIsland.Telemetry.span_event(socket.span, :socket_shutdown, %{way: way}) socket.transport_module.shutdown(socket.socket, way) end @doc """ Closes the given socket. Note that a socket is automatically closed when the handler process which owns it terminates """ @spec close(t()) :: ThousandIsland.Transport.on_close() def close(%__MODULE__{} = socket) do socket.transport_module.close(socket.socket) end @doc """ Gets the given flags on the socket Errors are usually from :inet.posix(), however, SSL module defines return type as any() """ @spec getopts(t(), ThousandIsland.Transport.socket_get_options()) :: ThousandIsland.Transport.on_getopts() def getopts(%__MODULE__{} = socket, options) do socket.transport_module.getopts(socket.socket, options) end @doc """ Sets the given flags on the socket Errors are usually from :inet.posix(), however, SSL module defines return type as any() """ @spec setopts(t(), ThousandIsland.Transport.socket_set_options()) :: ThousandIsland.Transport.on_setopts() def setopts(%__MODULE__{} = socket, options) do socket.transport_module.setopts(socket.socket, options) end @doc """ Returns information in the form of `t:ThousandIsland.Transport.socket_info()` about the local end of the socket. """ @spec sockname(t()) :: ThousandIsland.Transport.on_sockname() def sockname(%__MODULE__{} = socket) do socket.transport_module.sockname(socket.socket) end @doc """ Returns information in the form of `t:ThousandIsland.Transport.socket_info()` about the remote end of the socket. """ @spec peername(t()) :: ThousandIsland.Transport.on_peername() def peername(%__MODULE__{} = socket) do socket.transport_module.peername(socket.socket) end @doc """ Returns information in the form of `t:public_key.der_encoded()` about the peer certificate of the socket. """ @spec peercert(t()) :: ThousandIsland.Transport.on_peercert() def peercert(%__MODULE__{} = socket) do socket.transport_module.peercert(socket.socket) end @doc """ Returns whether or not this protocol is secure. """ @spec secure?(t()) :: boolean() def secure?(%__MODULE__{} = socket) do socket.transport_module.secure?() end @doc """ Returns statistics about the connection. """ @spec getstat(t()) :: ThousandIsland.Transport.socket_stats() def getstat(%__MODULE__{} = socket) do socket.transport_module.getstat(socket.socket) end @doc """ Returns information about the protocol negotiated during transport handshaking (if any). """ @spec negotiated_protocol(t()) :: ThousandIsland.Transport.on_negotiated_protocol() def negotiated_protocol(%__MODULE__{} = socket) do socket.transport_module.negotiated_protocol(socket.socket) end @doc """ Returns the telemetry span representing the lifetime of this socket """ @spec telemetry_span(t()) :: ThousandIsland.Telemetry.t() def telemetry_span(%__MODULE__{} = socket) do socket.span end end