-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
27fa12d
commit c7ad9a0
Showing
3 changed files
with
213 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,135 @@ | ||
defmodule Sender do | ||
@behaviour :gen_statem | ||
|
||
require Logger | ||
|
||
defstruct [:d4_connection, :socket, callers: []] | ||
|
||
## Public API | ||
|
||
def start_link(opts) do | ||
d4_connection = Keyword.fetch!(opts, :d4_connection) | ||
:gen_statem.start_link(__MODULE__, d4_connection, []) | ||
end | ||
|
||
def send(pid, payload) do | ||
:gen_statem.call(pid, {:payload, payload}) | ||
end | ||
|
||
def get_status(pid) do | ||
:gen_statem.call(pid, {:status}) | ||
end | ||
|
||
## :gen_statem callbacks | ||
|
||
@impl true | ||
def callback_mode(), do: [:state_functions, :state_enter] | ||
|
||
@impl true | ||
def init(d4_connection) do | ||
data = %__MODULE__{d4_connection: d4_connection} | ||
actions = [{:next_event, :internal, :connect}] | ||
{:ok, :disconnected, data, actions} | ||
end | ||
|
||
## Disconnected state | ||
|
||
def disconnected(:enter, :disconnected, _data), do: :keep_state_and_data | ||
|
||
def disconnected(:enter, :connected, data) do | ||
Logger.error("Connection closed") | ||
|
||
Enum.each(data.callers, fn {from} -> | ||
:gen_statem.reply(from, {:error, :disconnected}) | ||
end) | ||
|
||
data = %{data | socket: nil, callers: []} | ||
|
||
actions = [{{:timeout, :reconnect}, 5000, nil}] | ||
{:keep_state, data, actions} | ||
end | ||
|
||
def disconnected(:internal, :connect, data) do | ||
socket_opts = [:binary, active: true, verify: :verify_none] | ||
|
||
case :ssl.connect(data.d4_connection.destination, data.d4_connection.port, socket_opts, 5_000) do | ||
{:ok, socket} -> | ||
# Create an empty d4 packet to check whether a session already exists with the same uuid | ||
{:ok, packet} = Exd4.encapsulate!(data.d4_connection, "") | ||
|
||
case :ssl.send(socket, packet) do | ||
:ok -> | ||
# Logger.debug("Connected to d4 server.") | ||
{:next_state, :connected, %{data | socket: socket}} | ||
|
||
{:error, reason} -> | ||
Logger.error("D4 server kicked us out #{inspect({:error, reason})}") | ||
:keep_state_and_data | ||
end | ||
|
||
{:error, reason} -> | ||
Logger.error( | ||
"Connection to d4 server could not be established #{inspect({:error, reason})}" | ||
) | ||
|
||
:keep_state_and_data | ||
end | ||
end | ||
|
||
def disconnected({:timeout, :reconnect}, _, data) do | ||
actions = [{:next_event, :internal, :connect}] | ||
{:keep_state, data, actions} | ||
end | ||
|
||
def disconnected({:call, from}, {:payload, _payload}, _data) do | ||
actions = [{:reply, from, {:error, :disconnected}}] | ||
{:keep_state_and_data, actions} | ||
end | ||
|
||
def disconnected({:call, from}, {:status}, _data) do | ||
actions = [{:reply, from, {:error, :disconnected}}] | ||
{:keep_state_and_data, actions} | ||
end | ||
|
||
## Connected state | ||
|
||
def connected({:call, from}, {:status}, _data) do | ||
actions = [{:reply, from, {:ok}}] | ||
{:keep_state_and_data, actions} | ||
end | ||
|
||
def connected(:enter, :disconnected, _data) do | ||
:keep_state_and_data | ||
end | ||
|
||
def connected(:enter, :connected, _data) do | ||
:keep_state_and_data | ||
end | ||
|
||
def connected(:info, {:ssl_closed, socket}, %{socket: socket} = data) do | ||
# Logger.debug(":ssl_closed") | ||
{:next_state, :disconnected, data} | ||
end | ||
|
||
def connected({:call, from}, {:payload, payload}, data) do | ||
{:ok, packet} = Exd4.encapsulate!(data.d4_connection, payload) | ||
|
||
case :ssl.send(data.socket, packet) do | ||
:ok -> | ||
:gen_statem.reply(from, {:ok}) | ||
data = %{data | callers: [data.callers | from]} | ||
{:keep_state, data} | ||
|
||
{:error, reason} -> | ||
Logger.error("Connection to d4 server lost #{inspect({:error, reason})}") | ||
:gen_statem.reply(from, {:error, reason}) | ||
:ok = :ssl.close(data.socket) | ||
{:next_state, :disconnected, data} | ||
end | ||
end | ||
|
||
def connected(:info, {:tcp, _socket, _packet}, _ = _data) do | ||
# D4 server should never send us anything back | ||
:keep_state_and_data | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
defmodule SenderTest do | ||
use ExUnit.Case, async: false | ||
|
||
require Logger | ||
|
||
test "Sender connecting to D4 server" do | ||
d4_connection = %Exd4{ | ||
destination: Exd4.d4_ip(), | ||
port: Exd4.d4_port(), | ||
uuid: Exd4.d4_uuid(), | ||
type: Exd4.d4_type(), | ||
version: Exd4.d4_version(), | ||
snaplen: Exd4.d4_snaplen(), | ||
key: Exd4.d4_key() | ||
} | ||
|
||
assert {:ok, _} = Sender.start_link(d4_connection: d4_connection) | ||
end | ||
|
||
test "Sender sending data to D4 Server" do | ||
d4_connection = %Exd4{ | ||
destination: Exd4.d4_ip(), | ||
port: Exd4.d4_port(), | ||
uuid: "b5062e1f-674e-45a0-9c30-2557d6e70ef6", | ||
type: Exd4.d4_type(), | ||
version: Exd4.d4_version(), | ||
snaplen: Exd4.d4_snaplen(), | ||
key: Exd4.d4_key() | ||
} | ||
|
||
assert {:ok, pid} = Sender.start_link(d4_connection: d4_connection) | ||
|
||
Sender.send(pid, "😀 😃 😄 😁 😆 😅 😂 🤣 🥲 🤩 🥳 😏 😒 😞 😔 😟 😕 🙁 😐 😑 😬 ☠️ 👽 👾 🤖 🎃 😺 😸 😹 😻 😼 😽 🙀 😿 😾 \n") | ||
Sender.send(pid, "blip blop\n") | ||
end | ||
|
||
test "Sender thrown out because of duplucate uuid" do | ||
d4_connection = %Exd4{ | ||
destination: Exd4.d4_ip(), | ||
port: Exd4.d4_port(), | ||
uuid: "b5062e1f-674e-45a0-9c30-2557d6e70ef7", | ||
type: Exd4.d4_type(), | ||
version: Exd4.d4_version(), | ||
snaplen: Exd4.d4_snaplen(), | ||
key: Exd4.d4_key() | ||
} | ||
|
||
assert {:ok, pid1} = Sender.start_link(d4_connection: d4_connection) | ||
assert {:ok} = Sender.send(pid1, "pid1\n") | ||
assert {:ok, pid2} = Sender.start_link(d4_connection: d4_connection) | ||
Process.sleep(1000) | ||
assert {:error, :disconnected} = Sender.get_status(pid2) | ||
end | ||
|
||
test "D4 server unreachable" do | ||
d4_connection = %Exd4{ | ||
destination: {10, 106, 129, 1}, | ||
port: 5000, | ||
uuid: "b5062e1f-674e-45a0-9c30-2557d6e70ef7", | ||
type: Exd4.d4_type(), | ||
version: Exd4.d4_version(), | ||
snaplen: Exd4.d4_snaplen(), | ||
key: Exd4.d4_key() | ||
} | ||
|
||
assert {:ok, pid} = Sender.start_link(d4_connection: d4_connection) | ||
Process.sleep(1000) | ||
assert {:error, :disconnected} = Sender.get_status(pid) | ||
end | ||
end |