EXAMPLES.md

# SMPPEX

## General description

SMPPEX is a framework for building SMPP servers and clients (which are often
referred to as MC and ESME entities respectevely).

The major features exposed by the library are:

* `SMPPEX.ESME` module and behaviour for implementing ESME entities;
* `SMPPEX.MC` module and behaviour for implementing MC entities;
* `SMPPEX.ESME.Sync` module representing simple ready to use SMPP client.

Also one of the core features of the library is simplicity: both code simplicity and simplicity of use.

* The library does not have much TCP handling or session management functionality,
it is based on great [`ranch`](https://github.com/ninenines/ranch) library.
* SMPP session is symmetric(used both in ESME and MC) and is implemented as
`ranch_protocol` behaviour.
* The library includes an easy and ready to use SMPP client (`SMPP.ESME.Sync`) which
has capabilities of synchronous SMS sending and do not require implementing ESME behavior.
There is also an SMPP testing tool [`smppsend`](https://github.com/funbox/smppsend)
based on this client.

## SMPPEX.ESME.Sync

`SMPPEX.ESME.Sync` is the most straightforward way to interact with an SMSC. Example:

```elixir

{:ok, esme} = SMPPEX.ESME.Sync.start_link(host, port)

bind = SMPPEX.Pdu.Factory.bind_transmitter("system_id", "password")
{:ok, _bind_resp} = SMPPEX.ESME.Sync.request(esme, bind)

# We are bound, let's send a message

submit_sm = SMPPEX.Pdu.Factory.submit_sm({"from", 1, 1}, {"to", 1, 1}, "hello!")
{:ok, submit_sm_resp} = SMPPEX.ESME.Sync.request(esme, submit_sm)

# Message is sent, let's get the obtained id:

message_id = SMPPEX.Pdu.field(submit_sm_resp, :message_id)

# Now let's wait for a delivery report:

delivery_report? = fn(pdu) ->
SMPPEX.Pdu.command_name(pdu) == :deliver_sm and
  SMPPEX.Pdu.field(pdu, :receipted_message_id) == message_id
end

delivery_reports = case SMPPEX.ESME.Sync.wait_for_pdus(esme, 60000) do
:stop ->
  Logger.info("Ooops, ESME stopped")
  []
:timeout ->
  Logger.info("No DLR in 60 seconds")
  []
received_items ->
  # Let's filter out DLRs for the previously submitted message
  for {:pdu, pdu}  <- received_items, delivery_report?.(pdu), do: pdu
end
```

## SMPPEX.ESME

`SMPPEX.ESME` can be used when more complicated client logic is needed, for example
custom immediate reactions to all incoming PDUs, rps/window control, etc.

`SMPPEX.Session` provides "empty" defaults for all required callbacks, so minimal ESME
could be very simple:

```elixir
defmodule DummyESME do
  use SMPPEX.Session

  def start_link(host, port) do
    SMPPEX.ESME.start_link(host, port, {__MODULE__, []})
  end
end
```

It is still completely functional:

```elixir
{:ok, esme} = DummyESME.start_link(host, port)
SMPPEX.Session.send_pdu(esme, SMPPEX.Pdu.Factory.bind_transmitter("system_id", "password"))
```

Here's a more complicated example of ESME, which does the following:

* Receives port number and three arguments:

    - `waiting_pid` — a pid of the process which will be informed when ESME stops;
    - `count` — count of PDUs to send;
    - `window` — window size, the maximum number of sent PDU's without resps.

* Connects to the specified port on localhost and issues a bind command.
* Starts to send predefined PDUs after bind at maximum possible rate but regarding window size.
* Stops after all PDUs are sent and notifies the waiting process.

```elixir
defmodule SMPPBenchmarks.ESME do

  use SMPPEX.Session
  require Logger

  @from {"from", 1, 1}
  @to {"to", 1, 1}
  @message "hello"

  @system_id "system_id"
  @password "password"

  def start_link(port, waiting_pid, count, window) do
    SMPPEX.ESME.start_link("127.0.0.1", port, {__MODULE__, [waiting_pid, count, window]})
  end

  def init(_, _, [waiting_pid, count, window]) do
    Kernel.send(self(), :bind)
    {:ok, %{waiting_pid: waiting_pid, count_to_send: count, count_waiting_resp: 0, window: window}}
  end

  def handle_resp(pdu, _original_pdu, st) do
    case SMPPEX.Pdu.command_name(pdu) do
    :submit_sm_resp ->
      new_st = %{ st | count_waiting_resp: st.count_waiting_resp - 1 }
      send_pdus(new_st)
    :bind_transmitter_resp ->
      send_pdus(st)
    _ ->
      {:ok, st}
    end
  end

  def handle_resp_timeout(pdu, st) do
    Logger.error("PDU timeout: #{inspect pdu}, terminating")
    {:stop, :resp_timeout, st}

  end

  def terminate(reason, _, st) do
    Logger.info("ESME stopped with reason #{inspect reason}")
    Kernel.send(st.waiting_pid, {self(), :done})
    :stop
  end

  def handle_info(:bind, st) do
    {:noreply, [SMPPEX.Pdu.Factory.bind_transmitter(@system_id, @password)], st}
  end

  defp send_pdus(st) do
    cond do
      st.count_to_send > 0 ->
        count_to_send = min(st.window - st.count_waiting_resp, st.count_to_send)
        new_st = %{ st | count_waiting_resp: st.window, count_to_send: st.count_to_send - count_to_send }
        {:ok, make_pdus(count_to_send), new_st}
      st.count_waiting_resp > 0 ->
        {:ok, st}
      true ->
        Logger.info("All PDUs sent, all resps received, terminating")
        {:stop, :normal, st}
    end
  end

  defp make_pdus(0), do: []
  defp make_pdus(n) do
    for _ <- 1..n, do: SMPPEX.Pdu.Factory.submit_sm(@from, @to, @message)
  end

end
```

Not all callbacks are used yet in this example, for the full list see `SMPPEX.Session` documentation.

## SMPPEX.MC

`SMPPEX.MC` is used for _receiving_ and handling SMPP connections.

Here is an example of a very simple MC, which does the following:

* Starts and listens to connections on the specified port.
* Responds with OK status to all incoming binds.
* Responds with incremental message ids to all incoming `submit_sm` packets (regardless of the bind state).

```elixir

defmodule MC do

  use SMPPEX.Session

  alias SMPPEX.Pdu
  alias SMPPEX.Pdu.Factory, as: PduFactory

  def child_spec(port) do
    Supervisor.child_spec(
      {
        SMPPEX.MC,
        session: {__MODULE__, []},
        transport_opts: [port: port]
      },
      []
    )
  end

  def init(_socket, _transport, []) do
    {:ok, 0}
  end

  def handle_pdu(pdu, last_id) do
    case Pdu.command_name(pdu) do
      :submit_sm ->
        {:ok, [PduFactory.submit_sm_resp(0, to_string(last_id)) |> Pdu.as_reply_to(pdu)], last_id + 1}
      :bind_transmitter ->
        {:ok, [PduFactory.bind_transmitter_resp(0) |> Pdu.as_reply_to(pdu)], last_id}
      _ ->
        {:ok, last_id}
    end
  end

end
```

This server can be started by providing `{MC, port}` as a child of some supervisor.