Current section

Files

Jump to
socket lib socket udp.ex
Raw

lib/socket/udp.ex

# DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
# Version 2, December 2004
#
# DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
# TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
#
# 0. You just DO WHAT THE FUCK YOU WANT TO.
defmodule Socket.UDP do
@moduledoc """
This module wraps a UDP socket using `gen_udp`.
## Options
When creating a socket you can pass a series of options to use for it.
* `:as` sets the kind of value returned by recv, either `:binary` or `:list`,
the default is `:binary`.
* `:mode` can be either `:passive` or `:active`, default is `:passive`
* `:local` must be a keyword list
- `:address` the local address to use
- `:fd` an already opened file descriptor to use
* `:version` sets the IP version to use
* `:broadcast` enables broadcast sending
## Examples
server = Socket.UDP.open!(1337)
{ data, client } = server |> Socket.Datagram.recv!
server |> Socket.Datagram.send! data, client
"""
@type t :: port
use Socket.Helpers
@doc """
Create a UDP socket listening on an OS chosen port, use `local` to know the
port it was bound on.
"""
@spec open :: { :ok, t } | { :error, Error.t }
def open do
open(0, [])
end
@doc """
Create a UDP socket listening on an OS chosen port, use `local` to know the
port it was bound on, raising if an error occurs.
"""
@spec open! :: t | no_return
defbang open
@doc """
Create a UDP socket listening on the given port or using the given options.
"""
@spec open(:inet.port_number | Keyword.t) :: { :ok, t } | { :error, Error.t }
def open(port) when port |> is_integer do
open(port, [])
end
def open(options) when options |> is_list do
open(0, options)
end
@doc """
Create a UDP socket listening on the given port or using the given options,
raising if an error occurs.
"""
@spec open!(:inet.port_number | Keyword.t) :: t | no_return
defbang open(port_or_options)
@doc """
Create a UDP socket listening on the given port and using the given options.
"""
@spec open(:inet.port_number, Keyword.t) :: { :ok, t } | { :error, Error.t }
def open(port, options) do
options = Keyword.put_new(options, :mode, :passive)
:gen_udp.open(port, arguments(options))
end
@doc """
Create a UDP socket listening on the given port and using the given options,
raising if an error occurs.
"""
@spec open!(:inet.port_number, Keyword.t) :: t | no_return
defbang open(port, options)
@doc """
Set the process which will receive the messages.
"""
@spec process(t | port, pid) :: :ok | { :error, :closed | :not_owner | Error.t }
def process(sock, pid) when sock |> is_port do
:gen_udp.controlling_process(sock, pid)
end
@doc """
Set the process which will receive the messages, raising if an error occurs.
"""
@spec process!(t | port, pid) :: :ok | no_return
def process!(sock, pid) do
case process(sock, pid) do
:ok ->
:ok
:closed ->
raise RuntimeError, message: "the socket is closed"
:not_owner ->
raise RuntimeError, message: "the current process isn't the owner"
code ->
raise Socket.Error, reason: code
end
end
@doc """
Set options of the socket.
"""
@spec options(t, Keyword.t) :: :ok | { :error, Error.t }
def options(sock, opts) when sock |> is_port do
:inet.setopts(sock, arguments(opts))
end
@doc """
Convert UDP options to `:inet.setopts` compatible arguments.
"""
@spec arguments(Keyword.t) :: list
def arguments(options) do
options = options
|> Keyword.put_new(:as, :binary)
options = Enum.group_by(options, fn
{ :as, _ } -> true
{ :local, _ } -> true
{ :version, _ } -> true
{ :broadcast, _ } -> true
{ :multicast, _ } -> true
{ :membership, _ } -> true
_ -> false
end)
{ local, global } = {
Map.get(options, true, []),
Map.get(options, false, [])
}
Socket.arguments(global) ++ Enum.flat_map(local, fn
{ :as, :binary } ->
[:binary]
{ :as, :list } ->
[:list]
{ :local, options } ->
Enum.flat_map(options, fn
{ :address, address } ->
[{ :ip, Socket.Address.parse(address) }]
{ :fd, fd } ->
[{ :fd, fd }]
end)
{ :version, 4 } ->
[:inet]
{ :version, 6 } ->
[:inet6]
{ :broadcast, broadcast } ->
[{ :broadcast, broadcast }]
{ :multicast, options } ->
Enum.flat_map(options, fn
{ :address, address } ->
[{ :multicast_if, Socket.Address.parse(address) }]
{ :loop, loop } ->
[{ :multicast_loop, loop }]
{ :ttl, ttl } ->
[{ :multicast_ttl, ttl }]
end)
{ :membership, membership } ->
[{ :add_membership, membership }]
end)
end
end