From: Christopher Date: Sun, 16 Feb 2020 04:45:36 +0000 (-0600) Subject: initial prototype X-Git-Url: http://git.entropealabs.com/?a=commitdiff_plain;h=220ad8641eb11540bde98aeb7e8c420e078aa408;p=wampex.git initial prototype --- 220ad8641eb11540bde98aeb7e8c420e078aa408 diff --git a/.formatter.exs b/.formatter.exs new file mode 100644 index 0000000..d2cda26 --- /dev/null +++ b/.formatter.exs @@ -0,0 +1,4 @@ +# Used by "mix format" +[ + inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"] +] diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9f7b236 --- /dev/null +++ b/.gitignore @@ -0,0 +1,24 @@ +# The directory Mix will write compiled artifacts to. +/_build/ + +# If you run "mix test --cover", coverage assets end up here. +/cover/ + +# The directory Mix downloads your dependencies sources to. +/deps/ + +# Where third-party dependencies like ExDoc output generated docs. +/doc/ + +# Ignore .fetch files in case you like to edit your project deps locally. +/.fetch + +# If the VM crashes, it generates a dump, let's ignore it too. +erl_crash.dump + +# Also ignore archive artifacts (built via "mix archive.build"). +*.ez + +# Ignore package tarball (built via "mix hex.build"). +wampex-*.tar + diff --git a/.tool-versions b/.tool-versions new file mode 100644 index 0000000..c217a1b --- /dev/null +++ b/.tool-versions @@ -0,0 +1,2 @@ +elixir 1.10.1 +erlang 22.2.6 diff --git a/README.md b/README.md new file mode 100644 index 0000000..a6dd6c9 --- /dev/null +++ b/README.md @@ -0,0 +1,21 @@ +# Wampex + +**TODO: Add description** + +## Installation + +If [available in Hex](https://hex.pm/docs/publish), the package can be installed +by adding `wampex` to your list of dependencies in `mix.exs`: + +```elixir +def deps do + [ + {:wampex, "~> 0.1.0"} + ] +end +``` + +Documentation can be generated with [ExDoc](https://github.com/elixir-lang/ex_doc) +and published on [HexDocs](https://hexdocs.pm). Once published, the docs can +be found at [https://hexdocs.pm/wampex](https://hexdocs.pm/wampex). + diff --git a/lib/wampex.ex b/lib/wampex.ex new file mode 100644 index 0000000..96f2178 --- /dev/null +++ b/lib/wampex.ex @@ -0,0 +1,12 @@ +defmodule Wampex do + @moduledoc """ + Documentation for Wampex. + """ + alias Wampex.Session + + def test do + url = "ws://localhost:18080/ws" + sess = %Session{url: url} + Session.start_link(sess) + end +end diff --git a/lib/wampex/serializers/json.ex b/lib/wampex/serializers/json.ex new file mode 100644 index 0000000..99b45db --- /dev/null +++ b/lib/wampex/serializers/json.ex @@ -0,0 +1,7 @@ +defmodule Wampex.Serializer.JSON do + def data_type, do: :text + def serialize!(data), do: Jason.encode!(data) + def serialize(data), do: Jason.encode(data) + def deserialize!(binary), do: Jason.decode!(binary) + def deserialize(binary), do: Jason.decode(binary) +end diff --git a/lib/wampex/session.ex b/lib/wampex/session.ex new file mode 100644 index 0000000..089a24d --- /dev/null +++ b/lib/wampex/session.ex @@ -0,0 +1,85 @@ +defmodule Wampex.Session do + use StatesLanguage, data: "priv/session.json" + + alias StatesLanguage, as: SL + alias Wampex.Session, as: Sess + alias Wampex.Serializer.JSON + alias Wampex.Transport.WebSocket + + defstruct [ + :id, + :url, + :transport_pid, + :message, + request_id: 1, + transport: WebSocket, + protocol: "wamp.2.json", + serializer: JSON + ] + + def get_id do + Enum.random(1..9_007_199_254_740_992) + end + + def handle_resource( + "Hello", + _, + "Init", + %SL{data: %Sess{transport: tt, transport_pid: t}} = data + ) do + tt.send_message( + t, + [ + 1, + "com.myrealm", + %{roles: %{publisher: %{}, subscriber: %{}, caller: %{}, callee: %{}}} + ] + ) + + {:ok, data, []} + end + + def handle_resource( + "GoodBye", + _, + "GoodBye", + %SL{data: %Sess{transport: tt, transport_pid: t}} = data + ) do + tt.send_message(t, [6, %{}, "i.dont.know"]) + {:ok, data, []} + end + + def handle_resource("HandleMessage", _, _, data) do + Logger.info("Handling Message") + {:ok, data, []} + end + + def handle_resource("Abort", _, _, data) do + Logger.info("Aborting...") + {:ok, data, []} + end + + def handle_resource( + "InitTransport", + _, + "WaitForTransport", + %SL{data: %Sess{url: url, transport: t, serializer: s, protocol: p} = sess} = data + ) do + {:ok, pid} = t.start_link(url, self(), p, s) + {:ok, %SL{data | data: %Sess{sess | transport_pid: pid}}, []} + end + + def handle_resource("Established", _, "Established", data) do + Logger.info("Session #{data.data.id} Established") + {:ok, data, []} + end + + def handle_info({:set_session_id, id}, "Init", %SL{data: %Sess{} = sess} = data) do + {:ok, %SL{data | data: %Sess{sess | id: id}}, [{:next_event, :internal, :welcome}]} + end + + def handle_info({:set_message, message}, "Established", %SL{data: %Sess{} = sess} = data) do + {:ok, %SL{data | data: %Sess{sess | message: message}}, + [{:next_event, :internal, :message_received}]} + end +end diff --git a/lib/wampex/transport.ex b/lib/wampex/transport.ex new file mode 100644 index 0000000..8f7fe66 --- /dev/null +++ b/lib/wampex/transport.ex @@ -0,0 +1,64 @@ +defmodule Wampex.Transport.WebSocket do + use WebSockex + + alias WebSockex.Conn + + require Logger + + @header "Sec-WebSocket-Protocol" + + def send_message(t, data) do + WebSockex.cast(t, {:send_message, data}) + end + + def connected?(t, resp) do + WebSockex.cast(t, {:connected, resp}) + end + + def start_link( + url, + session, + protocol, + serializer, + opts \\ [] + ) do + url + |> Conn.new(extra_headers: [{@header, protocol}]) + |> WebSockex.start_link( + __MODULE__, + %{session: session, serializer: serializer, connected: false}, + opts + ) + end + + def handle_connect(%Conn{host: h, port: p, path: pa, query: q} = _conn, state) do + Logger.info("Connected to #{h}:#{p}#{pa}?#{q}") + send(state.session, {:connected, true}) + {:ok, %{state | connected: true}} + end + + def handle_cast({:send_message, data}, %{serializer: s} = state) do + {:reply, {s.data_type(), s.serialize!(data)}, state} + end + + def handle_cast({:connected, resp}, state) do + send(resp, {:connected, state.connected}) + {:ok, state} + end + + def handle_frame({type, data}, %{serializer: s} = state) do + Logger.info("Received #{type} data #{data}") + data = s.deserialize!(data) + Logger.info("Deserialized #{inspect(data)}") + handle_message(data, state) + end + + def handle_message([2, id, _dets], state) do + send(state.session, {:set_session_id, id}) + {:ok, state} + end + + def handle_message(message, state) do + send(state.session, {:set_message, message}) + end +end diff --git a/mix.exs b/mix.exs new file mode 100644 index 0000000..3a1fa87 --- /dev/null +++ b/mix.exs @@ -0,0 +1,31 @@ +defmodule Wampex.MixProject do + use Mix.Project + + def project do + [ + app: :wampex, + version: "0.1.0", + elixir: "~> 1.9", + start_permanent: Mix.env() == :prod, + deps: deps() + ] + end + + # Run "mix help compile.app" to learn about applications. + def application do + [ + extra_applications: [:logger] + ] + end + + # Run "mix help deps" to learn about dependencies. + defp deps do + [ + {:jason, "~> 1.1"}, + {:states_language, "~> 0.2"}, + {:websockex, "~> 0.4"} + # {:dep_from_hexpm, "~> 0.3.0"}, + # {:dep_from_git, git: "https://github.com/elixir-lang/my_dep.git", tag: "0.1.0"} + ] + end +end diff --git a/mix.lock b/mix.lock new file mode 100644 index 0000000..3fadc7a --- /dev/null +++ b/mix.lock @@ -0,0 +1,11 @@ +%{ + "conv_case": {:hex, :conv_case, "0.2.2", "5a98b74ab8f7ddbad670e5c7bb39ff280e60699aa3b25c7062ceccf48137433c", [:mix], [], "hexpm", "561c550ab6d55b2a4d4c14449e58c9957798613eb26ea182e14a962965377bca"}, + "elixpath": {:hex, :elixpath, "0.1.0", "f860e931db7bda6856dc68145694ca429643cc068ef30d7ff6b4096d4357963e", [:mix], [{:nimble_parsec, "~> 0.5", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "30ce06079b41f1f5216ea2cd11605cfe4c82239628555cb3fde9f10055a6eb67"}, + "jason": {:hex, :jason, "1.1.2", "b03dedea67a99223a2eaf9f1264ce37154564de899fd3d8b9a21b1a6fd64afe7", [:mix], [{:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "fdf843bca858203ae1de16da2ee206f53416bbda5dc8c9e78f43243de4bc3afe"}, + "json_xema": {:hex, :json_xema, "0.4.0", "377446cd5c0e2cbba52b9d7ab67c05579e6d4a788335220215a8870eac821996", [:mix], [{:conv_case, "~> 0.2", [hex: :conv_case, repo: "hexpm", optional: false]}, {:xema, "~> 0.11", [hex: :xema, repo: "hexpm", optional: false]}], "hexpm", "452724a5b2751cd69191edd3fd3da0c2194c164ebd49efd85f9abb64d6621b53"}, + "nimble_parsec": {:hex, :nimble_parsec, "0.5.3", "def21c10a9ed70ce22754fdeea0810dafd53c2db3219a0cd54cf5526377af1c6", [:mix], [], "hexpm", "589b5af56f4afca65217a1f3eb3fee7e79b09c40c742fddc1c312b3ac0b3399f"}, + "states_language": {:hex, :states_language, "0.2.8", "f9dfd3c0bd9a9d7bda25ef315f2d90944cd6b2022a7f3c403deb1d4ec451825e", [:mix], [{:elixpath, "~> 0.1.0", [hex: :elixpath, repo: "hexpm", optional: false]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:json_xema, "~> 0.4.0", [hex: :json_xema, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:xema, "~> 0.11.0", [hex: :xema, repo: "hexpm", optional: false]}], "hexpm", "a5231691e7cb37fe32dc7de54c2dc86d1d60e84c4f0379f3246e55be2a85ec78"}, + "telemetry": {:hex, :telemetry, "0.4.1", "ae2718484892448a24470e6aa341bc847c3277bfb8d4e9289f7474d752c09c7f", [:rebar3], [], "hexpm", "4738382e36a0a9a2b6e25d67c960e40e1a2c95560b9f936d8e29de8cd858480f"}, + "websockex": {:hex, :websockex, "0.4.2", "9a3b7dc25655517ecd3f8ff7109a77fce94956096b942836cdcfbc7c86603ecc", [:mix], [], "hexpm", "803cd76e91544b56f0e655e36790be797fa6436db9224f7c303db9b9df2a3df4"}, + "xema": {:hex, :xema, "0.11.0", "7b5118418633cffc27092110d02d4faeea938149dd3f6c64299e41e747067e80", [:mix], [{:conv_case, "~> 0.2.2", [hex: :conv_case, repo: "hexpm", optional: false]}, {:decimal, "~> 1.7", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "51491c9a953d65069d4b30aa2f70bc45ff99fd1bc3345bc72ce4e644d01ea14e"}, +} diff --git a/priv/session.json b/priv/session.json new file mode 100644 index 0000000..dadb1fa --- /dev/null +++ b/priv/session.json @@ -0,0 +1,61 @@ +{ + "Comment": "Session State Machine", + "StartAt": "WaitForTransport", + "States": { + "WaitForTransport": { + "Type": "Task", + "Resource": "InitTransport", + "TransitionEvent": "{:connected, true}", + "Next": "Init", + "Catch": [ + { + "ErrorEquals": ["{:connected, false}"], + "Next": "Abort" + } + ] + }, + "Init": { + "Type": "Task", + "Resource": "Hello", + "TransitionEvent": ":welcome", + "Next": "Established", + "Catch": [ + { + "ErrorEquals": [":abort"], + "Next": "Abort" + } + ] + }, + "Established": { + "Type": "Choice", + "Resource": "Established", + "Choices": [ + { + "StringEquals": ":message_received", + "Next": "HandleMessage" + }, + { + "StringEquals": ":good_bye", + "Next": "GoodBye" + } + ] + + }, + "HandleMessage": { + "Type": "Task", + "Resource": "HandleMessage", + "TransitionEvent": ":handled", + "Next": "Established" + }, + "GoodBye": { + "Type": "Task", + "Resource": "GoodBye", + "End": true + }, + "Abort": { + "Type": "Task", + "Resource": "Abort", + "End": true + } + } +} diff --git a/test/test_helper.exs b/test/test_helper.exs new file mode 100644 index 0000000..869559e --- /dev/null +++ b/test/test_helper.exs @@ -0,0 +1 @@ +ExUnit.start() diff --git a/test/wampex_test.exs b/test/wampex_test.exs new file mode 100644 index 0000000..f6cf6cf --- /dev/null +++ b/test/wampex_test.exs @@ -0,0 +1,8 @@ +defmodule WampexTest do + use ExUnit.Case + doctest Wampex + + test "greets the world" do + assert Wampex.hello() == :world + end +end