aboutsummaryrefslogtreecommitdiff
path: root/shard/lib/app
diff options
context:
space:
mode:
authorAlex Auvolat <alex@adnab.me>2018-09-26 14:37:10 +0200
committerAlex Auvolat <alex@adnab.me>2018-09-26 14:44:29 +0200
commitf16973d3a492ae6d4890c40d77b0a93d3293bf3a (patch)
tree36a38ec318ed9d0f87d28d508169271729b8507a /shard/lib/app
parent1df3aa74a6870f276bead1ed5650f0d86355ce09 (diff)
downloadshard-f16973d3a492ae6d4890c40d77b0a93d3293bf3a.tar.gz
shard-f16973d3a492ae6d4890c40d77b0a93d3293bf3a.zip
Signing and stuff
Diffstat (limited to 'shard/lib/app')
-rw-r--r--shard/lib/app/chat.ex77
-rw-r--r--shard/lib/app/identity.ex113
2 files changed, 165 insertions, 25 deletions
diff --git a/shard/lib/app/chat.ex b/shard/lib/app/chat.ex
index db2cb64..e61d648 100644
--- a/shard/lib/app/chat.ex
+++ b/shard/lib/app/chat.ex
@@ -28,7 +28,7 @@ defmodule SApp.Chat do
defimpl Shard.Manifest, for: Manifest do
def start(m) do
- SApp.Chat.start_link(m.channel)
+ DynamicSupervisor.start_child(Shard.DynamicSupervisor, {SApp.Chat, m.channel})
end
end
@@ -69,6 +69,12 @@ defmodule SApp.Chat do
end
end
+ def find_proc(chan) do
+ manifest = %Manifest{channel: chan}
+ id = SData.term_hash manifest
+ Shard.Manager.find_proc id
+ end
+
@doc """
Implementation of the :manifest call that returns the chat room's manifest
"""
@@ -98,10 +104,11 @@ defmodule SApp.Chat do
to send a message to the chat room. Puts the message in the store and syncs
with all connected peers.
"""
- def handle_cast({:chat_send, msg}, state) do
- msgitem = {(System.os_time :seconds),
- Shard.Identity.get_nickname(),
- msg}
+ def handle_cast({:chat_send, pk, msg}, state) do
+ msgbin = SData.term_bin {(System.os_time :seconds), msg}
+ {:ok, sign} = Shard.Keys.sign_detached(pk, msgbin)
+ msgitem = {pk, msgbin, sign}
+
prev_root = state.mst.root
mst = MST.insert(state.mst, msgitem)
state = %{state | mst: mst}
@@ -157,21 +164,26 @@ defmodule SApp.Chat do
state
else
# Try adding the message
- if prev_root == state.mst.root do
- mst2 = MST.insert(state.mst, msgitem)
- if mst2.root == new_root do
- # This was the only message missing, we are happy!
- state = %{state | mst: mst2}
- Shard.Manager.save_state(state.id, mst2.root)
- GenServer.cast(state.page_store, {:set_roots, [mst2.root]})
- msg_callback(state, msgitem)
- state
+ {pk, bin, sign} = msgitem
+ if Shard.Keys.verify(pk, bin, sign) == :ok do
+ if prev_root == state.mst.root do
+ mst2 = MST.insert(state.mst, msgitem)
+ if mst2.root == new_root do
+ # This was the only message missing, we are happy!
+ state = %{state | mst: mst2}
+ Shard.Manager.save_state(state.id, mst2.root)
+ GenServer.cast(state.page_store, {:set_roots, [mst2.root]})
+ msg_callback(state, msgitem)
+ state
+ else
+ Logger.warn("Invalid new root after inserting same message item!")
+ state
+ end
else
- # More messages are missing, start a full merge
init_merge(state, new_root, peer_id)
end
else
- init_merge(state, new_root, peer_id)
+ state
end
end
{:root, new_root} ->
@@ -190,21 +202,33 @@ defmodule SApp.Chat do
defp init_merge(state, new_root, source_peer) do
# TODO: make the merge asynchronous
+
+ Logger.info("Starting merge for #{inspect state.manifest}, merging root: #{new_root|>Base.encode16}")
+
prev_last = for {x, true} <- MST.last(state.mst, nil, 100), into: MapSet.new, do: x
mgmst = %{state.mst | root: new_root}
mgmst = put_in(mgmst.store.prefer_ask, [source_peer])
mst = MST.merge(state.mst, mgmst)
- for {x, true} <- MST.last(mst, nil, 100) do
+ correct = for {x, true} <- MST.last(mst, nil, 100) do
if not MapSet.member? prev_last, x do
msg_callback(state, x)
+ {pk, bin, sign} = x
+ Shard.Keys.verify(pk, bin, sign)
+ else
+ true
end
end
- GenServer.cast(state.page_store, {:set_roots, [mst.root]})
- Shard.Manager.save_state(state.id, mst.root)
- %{state | mst: mst}
+ if Enum.all? correct do
+ GenServer.cast(state.page_store, {:set_roots, [mst.root]})
+ Shard.Manager.save_state(state.id, mst.root)
+ %{state | mst: mst}
+ else
+ Logger.warn("Incorrect signatures somewhere while merging, dropping merged data")
+ state
+ end
end
def handle_info({:DOWN, _ref, :process, pid, _reason}, state) do
@@ -212,20 +236,23 @@ defmodule SApp.Chat do
{:noreply, %{ state | subs: new_subs }}
end
- defp msg_callback(state, {ts, nick, msg}) do
+ defp msg_callback(state, {pk, msgbin, _sign}) do
+ {ts, msg} = SData.term_unbin msgbin
for pid <- state.subs do
if Process.alive?(pid) do
- send(pid, {:chat_recv, state.channel, {ts, nick, msg}})
+ send(pid, {:chat_recv, state.channel, {ts, pk, msg}})
end
end
end
- defp msg_cmp({ts1, nick1, msg1}, {ts2, nick2, msg2}) do
+ defp msg_cmp({pk1, msgbin1, _sign1}, {pk2, msgbin2, _sign2}) do
+ {ts1, msg1} = SData.term_unbin msgbin1
+ {ts2, msg2} = SData.term_unbin msgbin2
cond do
ts1 > ts2 -> :after
ts1 < ts2 -> :before
- nick1 > nick2 -> :after
- nick1 < nick2 -> :before
+ pk1 > pk2 -> :after
+ pk1 < pk2 -> :before
msg1 > msg2 -> :after
msg1 < msg2 -> :before
true -> :duplicate
diff --git a/shard/lib/app/identity.ex b/shard/lib/app/identity.ex
new file mode 100644
index 0000000..7e97897
--- /dev/null
+++ b/shard/lib/app/identity.ex
@@ -0,0 +1,113 @@
+defmodule SApp.Identity do
+ use GenServer
+
+ require Logger
+
+ defmodule Manifest do
+ defstruct [:pk]
+ end
+
+ defmodule State do
+ defstruct [:info, :rev, :signed]
+ end
+
+ defimpl Shard.Manifest, for: Manifest do
+ def start(m) do
+ DynamicSupervisor.start_child(Shard.DynamicSupervisor, {SApp.Identity, m.pk})
+ end
+ end
+
+ def start_link(pk) do
+ GenServer.start_link(__MODULE__, pk)
+ end
+
+ def init(pk) do
+ manifest = %Manifest{pk: pk}
+ id = SData.term_hash manifest
+
+ case Shard.Manager.register(id, manifest, self()) do
+ :ok ->
+ Shard.Manager.dispatch_to(id, nil, self())
+ state = case Shard.Manager.load_state(id) do
+ nil ->
+ info = %{nick: default_nick(pk)}
+ SData.SignRev.new info
+ st ->
+ st
+ end
+ GenServer.cast(self(), :init_pull)
+ {:ok, %{pk: pk, id: id, state: state}}
+ :redundant ->
+ exit(:redundant)
+ end
+ end
+
+ def default_nick(pk) do
+ nick_suffix = pk
+ |> binary_part(0, 4)
+ |> Base.encode16
+ |> String.downcase
+ "Anon" <> nick_suffix
+ end
+
+ def find_proc(pk) do
+ manifest = %Manifest{pk: pk}
+ id = SData.term_hash manifest
+ Shard.Manager.find_proc id
+ end
+
+ def handle_call(:manifest, _from, state) do
+ {:replyl, state.manifest, state}
+ end
+
+ def handle_call(:get_info, _from, state) do
+ {:reply, SData.SignRev.get(state.state), state}
+ end
+
+ def handle_call({:set_info, new_info}, _from, state) do
+ case SData.SignRev.set(state.state, new_info, state.pk) do
+ {:ok, st2} ->
+ Shard.Manager.save_state(state.id, st2)
+ state = put_in(state.state, st2)
+ bcast_state(state)
+ {:reply, :ok, state}
+ err ->
+ {:reply, err, state}
+ end
+ end
+
+ def handle_cast(:init_pull, state) do
+ for {_, pid, _, _} <- Shard.Manager.list_peers do
+ GenServer.cast(pid, {:send_msg, {:interested, [state.id]}})
+ end
+ {:noreply, state}
+ end
+
+ def handle_cast({:interested, peer_id}, state) do
+ Shard.Manager.send(peer_id, {state.id, nil, {:update, SData.SignRev.signed(state.state)}})
+ end
+
+ def handle_cast({:msg, peer_id, _shard_id, nil, msg}, state) do
+ state = case msg do
+ {:update, signed} ->
+ case SData.SignRev.merge(state.state, signed, state.pk) do
+ {true, st2} ->
+ Shard.Manager.save_state(state.id, st2)
+ state = put_in(state.state, st2)
+ bcast_state(state, [peer_id])
+ state
+ {false, _} ->
+ state
+ end
+ end
+ {:noreply, state}
+ end
+
+ def bcast_state(state, exclude \\ []) do
+ for peer_id <- Shard.Manager.get_shard_peers(state.id) do
+ if not Enum.member? exclude, peer_id do
+ Shard.Manager.send(peer_id, {state.id, nil, {:update, SData.SignRev.signed(state.state)}})
+ end
+ end
+ end
+end