Skip to content

Commit

Permalink
Bug fixes, tests for fragmented messages
Browse files Browse the repository at this point in the history
  • Loading branch information
bokner committed Jul 10, 2023
1 parent 14ab8a9 commit 472155a
Show file tree
Hide file tree
Showing 4 changed files with 127 additions and 99 deletions.
71 changes: 63 additions & 8 deletions lib/mllp/client.ex
Original file line number Diff line number Diff line change
@@ -1,3 +1,47 @@
defmodule MLLP.ClientContract do
@moduledoc """
MLLP.ClientContract provides the behavior implemented by MLLP.Client. It may be useful
for testing in your own application with tools such as [`Mox`](https://hexdocs.pm/mox/)
"""
@type error_type :: :connect_failure | :send_error | :recv_error
@type error_reason :: :closed | :timeout | :no_socket | :inet.posix()

@type client_error :: MLLP.Client.Error.t()

@type options :: [
auto_reconnect_interval: non_neg_integer(),
use_backoff: boolean(),
backoff_max_seconds: integer(),
reply_timeout: non_neg_integer() | :infinity,
socket_opts: [:gen_tcp.option()],
telemetry_module: nil,
close_on_recv_error: boolean(),
tls: [:ssl.tls_client_option()]
]

@type send_options :: %{
optional(:reply_timeout) => non_neg_integer() | :infinity
}

@callback send(
pid :: pid,
payload :: HL7.Message.t() | String.t(),
options :: send_options(),
timeout :: non_neg_integer() | :infinity
) ::
{:ok, String.t()}
| MLLP.Ack.ack_verification_result()
| {:error, client_error()}

@callback send_async(
pid :: pid,
payload :: HL7.Message.t() | String.t(),
timeout :: non_neg_integer | :infinity
) ::
{:ok, :sent}
| {:error, client_error()}
end

defmodule MLLP.Client do
@moduledoc """
MLLP.Client provides a simple tcp client for sending and receiving data
Expand Down Expand Up @@ -195,7 +239,7 @@ defmodule MLLP.Client do
client and a server. This functions similarly to the `:send_timeout` option provided by
[`:gen_tcp`](`:gen_tcp`). Defaults to `true`.
* `:tls` - A list of tls options as supported by [`:ssl`](`:ssl`). When using TLS it is highly recommendeded you
* `:tls` - A list of tls options as supported by [`:ssl`](`:ssl`). When using TLS it is highly recommended you
set `:verify` to `:verify_peer`, select a CA trust store using the `:cacertfile` or `:cacerts` options.
Additionally, further hardening can be achieved through other ssl options such as enabling
certificate revocation via the `:crl_check` and `:crl_cache` options and customization of
Expand Down Expand Up @@ -369,7 +413,7 @@ defmodule MLLP.Client do
end

def disconnected(event, unknown, _state) do
unexpected_message(event, unknown)
unexpected_message(:disconnected, event, unknown)
end

#########################
Expand All @@ -381,7 +425,6 @@ defmodule MLLP.Client do
end

def connected(:enter, :receiving, _state) do
Logger.debug("Response received!")
:keep_state_and_data
end

Expand Down Expand Up @@ -420,13 +463,18 @@ defmodule MLLP.Client do
{:keep_state_and_data, [{:reply, from, :ok}]}
end

def connected(:info, {transport, socket, data} = msg, %{socket: socket} = state)
when transport in [:tcp, :ssl] do
receiving(:info, msg, state)
end

def connected(:info, {transport_closed, _socket}, state)
when transport_closed in [:tcp_closed, :ssl_closed] do
{:next_state, :disconnected, handle_closed(state)}
end

def connected(event, unknown, _state) do
unexpected_message(event, unknown)
unexpected_message(:connected, event, unknown)
end

defp reconnect_action(
Expand Down Expand Up @@ -472,7 +520,9 @@ defmodule MLLP.Client do

def receiving(:info, {transport, socket, data}, %{socket: socket} = state)
when transport in [:tcp, :ssl] do
{:next_state, :connected, handle_received(data, state)}
new_data = handle_received(data, state)
next_state = (new_data.caller && :receiving) || :connected
{:next_state, next_state, new_data}
end

def receiving(:info, {transport_closed, socket}, %{socket: socket} = state)
Expand All @@ -486,15 +536,18 @@ defmodule MLLP.Client do
end

def receiving(event, unknown, _state) do
unexpected_message(event, unknown)
unexpected_message(:receiving, event, unknown)
end

########################################
### End of GenStateMachine callbacks ###
########################################

defp unexpected_message(event, message) do
Logger.warn("Unknown message received => #{inspect(message)}")
defp unexpected_message(state, event, message) do
Logger.warn(
"Event: #{inspect(event)} in state #{state}. Unknown message received => #{inspect(message)}"
)

:keep_state_and_data
end

Expand All @@ -512,9 +565,11 @@ defmodule MLLP.Client do
case new_buf do
<<@header, _ack::binary-size(check), @trailer>> ->
## The response is completed, send back to caller
Logger.debug("Client #{inspect(self())} received a full MLLP!")
reply_to_caller({:ok, new_buf}, state)

<<@header, _rest::binary>> ->
Logger.debug("Client #{inspect(self())} received a MLLP fragment: #{reply}")
Map.put(state, :receive_buffer, new_buf)

_ ->
Expand Down
4 changes: 1 addition & 3 deletions mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,7 @@ defmodule MLLP.MixProject do
{:dialyxir, "~> 1.1.0", only: [:dev, :test], runtime: false},
{:mix_test_watch, "~> 1.0.2", only: :dev, runtime: false},
{:mox, "~> 1.0.0", only: :test},
{:excoveralls, "~> 0.14.4", only: :test, runtime: false},
## Debugging
{:replbug, github: "bokner/replbug"}
{:excoveralls, "~> 0.14.4", only: :test, runtime: false}
]
end

Expand Down
26 changes: 11 additions & 15 deletions mix.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2,31 +2,27 @@
"backoff": {:hex, :backoff, "1.1.6", "83b72ed2108ba1ee8f7d1c22e0b4a00cfe3593a67dbc792799e8cce9f42f796b", [:rebar3], [], "hexpm", "cf0cfff8995fb20562f822e5cc47d8ccf664c5ecdc26a684cbe85c225f9d7c39"},
"certifi": {:hex, :certifi, "2.9.0", "6f2a475689dd47f19fb74334859d460a2dc4e3252a3324bd2111b8f0429e7e21", [:rebar3], [], "hexpm", "266da46bdb06d6c6d35fde799bcb28d36d985d424ad7c08b5bb48f5b5cdd4641"},
"dialyxir": {:hex, :dialyxir, "1.1.0", "c5aab0d6e71e5522e77beff7ba9e08f8e02bad90dfbeffae60eaf0cb47e29488", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "07ea8e49c45f15264ebe6d5b93799d4dd56a44036cf42d0ad9c960bc266c0b9a"},
"earmark_parser": {:hex, :earmark_parser, "1.4.17", "6f3c7e94170377ba45241d394389e800fb15adc5de51d0a3cd52ae766aafd63f", [:mix], [], "hexpm", "f93ac89c9feca61c165b264b5837bf82344d13bebc634cd575cb711e2e342023"},
"elixir_hl7": {:hex, :elixir_hl7, "0.6.2", "b066608f2e63258da596d99794745ab42cb4e62ac9b363014d1611b49146da07", [:mix], [], "hexpm", "43db06b2f9333581b02a03ada7a22c3406088cd9b4564a60b2ab63e2191e5ee9"},
"erlang_term": {:hex, :erlang_term, "2.0.6", "b004de5bc7e83785c7bf69fff1ccff838ac49a75af693f397308306213b41ea8", [:rebar3], [], "hexpm", "b9f227336ea08d4e416824899e2613d50470246d7d28776856cace9d7c9c0f07"},
"earmark_parser": {:hex, :earmark_parser, "1.4.33", "3c3fd9673bb5dcc9edc28dd90f50c87ce506d1f71b70e3de69aa8154bc695d44", [:mix], [], "hexpm", "2d526833729b59b9fdb85785078697c72ac5e5066350663e5be6a1182da61b8f"},
"elixir_hl7": {:hex, :elixir_hl7, "0.6.6", "ae01b991eb1aa8d199bb75006a87f3f37f02238a82443bea7cd5f36c7f70204a", [:mix], [], "hexpm", "8e9fd9ad2512f9560a4176c4ae9e9613bee4857103b2521a76aa47fbdc26369c"},
"erlex": {:hex, :erlex, "0.2.6", "c7987d15e899c7a2f34f5420d2a2ea0d659682c06ac607572df55a43753aa12e", [:mix], [], "hexpm", "2ed2e25711feb44d52b17d2780eabf998452f6efda104877a3881c2f8c0c0c75"},
"ex_doc": {:hex, :ex_doc, "0.24.2", "e4c26603830c1a2286dae45f4412a4d1980e1e89dc779fcd0181ed1d5a05c8d9", [:mix], [{:earmark_parser, "~> 1.4.0", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "e134e1d9e821b8d9e4244687fb2ace58d479b67b282de5158333b0d57c6fb7da"},
"excoveralls": {:hex, :excoveralls, "0.14.6", "610e921e25b180a8538229ef547957f7e04bd3d3e9a55c7c5b7d24354abbba70", [:mix], [{:hackney, "~> 1.16", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "0eceddaa9785cfcefbf3cd37812705f9d8ad34a758e513bb975b081dce4eb11e"},
"file_system": {:hex, :file_system, "0.2.10", "fb082005a9cd1711c05b5248710f8826b02d7d1784e7c3451f9c1231d4fc162d", [:mix], [], "hexpm", "41195edbfb562a593726eda3b3e8b103a309b733ad25f3d642ba49696bf715dc"},
"gen_state_machine": {:hex, :gen_state_machine, "3.0.0", "1e57f86a494e5c6b14137ebef26a7eb342b3b0070c7135f2d6768ed3f6b6cdff", [:mix], [], "hexpm", "0a59652574bebceb7309f6b749d2a41b45fdeda8dbb4da0791e355dd19f0ed15"},
"hackney": {:hex, :hackney, "1.18.1", "f48bf88f521f2a229fc7bae88cf4f85adc9cd9bcf23b5dc8eb6a1788c662c4f6", [:rebar3], [{:certifi, "~>2.9.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "~>6.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "~>1.0.0", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~>1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.3.1", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~>1.1.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}, {:unicode_util_compat, "~>0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "a4ecdaff44297e9b5894ae499e9a070ea1888c84afdd1fd9b7b2bc384950128e"},
"idna": {:hex, :idna, "6.1.1", "8a63070e9f7d0c62eb9d9fcb360a7de382448200fbbd1b106cc96d3d8099df8d", [:rebar3], [{:unicode_util_compat, "~>0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "92376eb7894412ed19ac475e4a86f7b413c1b9fbb5bd16dccd57934157944cea"},
"jason": {:hex, :jason, "1.4.0", "e855647bc964a44e2f67df589ccf49105ae039d4179db7f6271dfd3843dc27e6", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "79a3791085b2a0f743ca04cec0f7be26443738779d09302e01318f97bdb82121"},
"makeup": {:hex, :makeup, "1.0.5", "d5a830bc42c9800ce07dd97fa94669dfb93d3bf5fcf6ea7a0c67b2e0e4a7f26c", [:mix], [{:nimble_parsec, "~> 0.5 or ~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "cfa158c02d3f5c0c665d0af11512fed3fba0144cf1aadee0f2ce17747fba2ca9"},
"makeup_elixir": {:hex, :makeup_elixir, "0.15.2", "dc72dfe17eb240552857465cc00cce390960d9a0c055c4ccd38b70629227e97c", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.1", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "fd23ae48d09b32eff49d4ced2b43c9f086d402ee4fd4fcb2d7fad97fa8823e75"},
"makeup_erlang": {:hex, :makeup_erlang, "0.1.1", "3fcb7f09eb9d98dc4d208f49cc955a34218fc41ff6b84df7c75b3e6e533cc65f", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "174d0809e98a4ef0b3309256cbf97101c6ec01c4ab0b23e926a9e17df2077cbb"},
"jason": {:hex, :jason, "1.4.1", "af1504e35f629ddcdd6addb3513c3853991f694921b1b9368b0bd32beb9f1b63", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "fbb01ecdfd565b56261302f7e1fcc27c4fb8f32d56eab74db621fc154604a7a1"},
"makeup": {:hex, :makeup, "1.1.0", "6b67c8bc2882a6b6a445859952a602afc1a41c2e08379ca057c0f525366fc3ca", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "0a45ed501f4a8897f580eabf99a2e5234ea3e75a4373c8a52824f6e873be57a6"},
"makeup_elixir": {:hex, :makeup_elixir, "0.16.1", "cc9e3ca312f1cfeccc572b37a09980287e243648108384b97ff2b76e505c3555", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "e127a341ad1b209bd80f7bd1620a15693a9908ed780c3b763bccf7d200c767c6"},
"makeup_erlang": {:hex, :makeup_erlang, "0.1.2", "ad87296a092a46e03b7e9b0be7631ddcf64c790fa68a9ef5323b6cbb36affc72", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "f3f5a1ca93ce6e092d92b6d9c049bcda58a3b617a8d888f8e7231c85630e8108"},
"metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"},
"mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm", "f278585650aa581986264638ebf698f8bb19df297f66ad91b18910dfc6e19323"},
"mix_test_watch": {:hex, :mix_test_watch, "1.0.2", "34900184cbbbc6b6ed616ed3a8ea9b791f9fd2088419352a6d3200525637f785", [:mix], [{:file_system, "~> 0.2.1 or ~> 0.3", [hex: :file_system, repo: "hexpm", optional: false]}], "hexpm", "47ac558d8b06f684773972c6d04fcc15590abdb97aeb7666da19fcbfdc441a07"},
"mox": {:hex, :mox, "1.0.0", "4b3c7005173f47ff30641ba044eb0fe67287743eec9bd9545e37f3002b0a9f8b", [:mix], [], "hexpm", "201b0a20b7abdaaab083e9cf97884950f8a30a1350a1da403b3145e213c6f4df"},
"nimble_parsec": {:hex, :nimble_parsec, "1.2.0", "b44d75e2a6542dcb6acf5d71c32c74ca88960421b6874777f79153bbbbd7dccc", [:mix], [], "hexpm", "52b2871a7515a5ac49b00f214e4165a40724cf99798d8e4a65e4fd64ebd002c1"},
"mix_test_watch": {:hex, :mix_test_watch, "1.0.3", "63d5b21e9278abf519f359e6d59aed704ed3c72ec38be6ab22306ae5dc9a2e06", [:mix], [{:file_system, "~> 0.2.1 or ~> 0.3", [hex: :file_system, repo: "hexpm", optional: false]}], "hexpm", "7352e91952d9748fb4f8aebe0a60357cdaf4bd6d6c42b5139c78fbcda6a0d7a2"},
"mox": {:hex, :mox, "1.0.2", "dc2057289ac478b35760ba74165b4b3f402f68803dd5aecd3bfd19c183815d64", [:mix], [], "hexpm", "f9864921b3aaf763c8741b5b8e6f908f44566f1e427b2630e89e9a73b981fef2"},
"nimble_parsec": {:hex, :nimble_parsec, "1.3.1", "2c54013ecf170e249e9291ed0a62e5832f70a476c61da16f6aac6dca0189f2af", [:mix], [], "hexpm", "2682e3c0b2eb58d90c6375fc0cc30bc7be06f365bf72608804fb9cffa5e1b167"},
"parse_trans": {:hex, :parse_trans, "3.3.1", "16328ab840cc09919bd10dab29e431da3af9e9e7e7e6f0089dd5a2d2820011d8", [:rebar3], [], "hexpm", "07cd9577885f56362d414e8c4c4e6bdf10d43a8767abb92d24cbe8b24c54888b"},
"ranch": {:hex, :ranch, "1.8.0", "8c7a100a139fd57f17327b6413e4167ac559fbc04ca7448e9be9057311597a1d", [:make, :rebar3], [], "hexpm", "49fbcfd3682fab1f5d109351b61257676da1a2fdbe295904176d5e521a2ddfe5"},
"redbug": {:git, "git@github.com:massemanet/redbug.git", "a249400ee173e1cb7db37e725df973a7a3b0d9b9", []},
"replbug": {:git, "https://github.com/bokner/replbug.git", "0670e5d07c909a8635fb2aae79cc220696c26a2a", []},
"rexbug": {:hex, :rexbug, "1.0.6", "024071c67d970151fbdc06f299faf8db3e1b2ac759a28623a9cc80a517fc74f2", [:mix], [{:mix_test_watch, ">= 0.5.0", [hex: :mix_test_watch, repo: "hexpm", optional: true]}, {:redbug, "~> 1.2", [hex: :redbug, repo: "hexpm", optional: false]}], "hexpm", "148ea724979413e9fd84ca3b4bb5d2d8b840ac481adfd645f5846fda409a642c"},
"ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.6", "cf344f5692c82d2cd7554f5ec8fd961548d4fd09e7d22f5b62482e5aeaebd4b0", [:make, :mix, :rebar3], [], "hexpm", "bdb0d2471f453c88ff3908e7686f86f9be327d065cc1ec16fa4540197ea04680"},
"telemetry": {:hex, :telemetry, "1.1.0", "a589817034a27eab11144ad24d5c0f9fab1f58173274b1e9bae7074af9cbee51", [:rebar3], [], "hexpm", "b727b2a1f75614774cff2d7565b64d0dfa5bd52ba517f16543e6fc7efcc0df48"},
"ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.7", "354c321cf377240c7b8716899e182ce4890c5938111a1296add3ec74cf1715df", [:make, :mix, :rebar3], [], "hexpm", "fe4c190e8f37401d30167c8c405eda19469f34577987c76dde613e838bbc67f8"},
"telemetry": {:hex, :telemetry, "1.2.1", "68fdfe8d8f05a8428483a97d7aab2f268aaff24b49e0f599faa091f1d4e7f61c", [:rebar3], [], "hexpm", "dad9ce9d8effc621708f99eac538ef1cbe05d6a874dd741de2e689c47feafed5"},
"unicode_util_compat": {:hex, :unicode_util_compat, "0.7.0", "bc84380c9ab48177092f43ac89e4dfa2c6d62b40b8bd132b1059ecc7232f9a78", [:rebar3], [], "hexpm", "25eee6d67df61960cf6a794239566599b09e17e668d3700247bc498638152521"},
}
Loading

0 comments on commit 472155a

Please sign in to comment.