defmodule SData.SignRev do
@moduledoc"""
Implement a simple signed object with a revision number.
"""
defstruct [:term, :rev, :signed]
@doc"""
New SignRev with an unsigned initial state (use this if initial state is deterministic/not signed).
"""
def new(init_term) do
%__MODULE__{term: init_term, rev: 0, signed: nil}
end
@doc"""
New SignRev with a signed initial state.
"""
def new(init_term, pk) do
bin = SData.term_bin {0, init_term}
case Shard.Keys.sign(pk, bin) do
{:ok, signed} ->
{:ok, %__MODULE__{term: init_term, rev: 0, signed: signed}}
err -> err
end
end
@doc"""
Get the current value of the SignRev.
"""
def get(sr) do
sr.term
end
@doc"""
Update the SignRev with a new value and sign it with this pk
"""
def set(sr, new_term, pk) do
rev = sr.rev + 1
bin = SData.term_bin {rev, new_term}
case Shard.Keys.sign(pk, bin) do
{:ok, signed} ->
{:ok, %__MODULE__{term: new_term, rev: rev, signed: signed}}
err -> err
end
end
@doc"""
Get the signed binary for the SignRev. Just send the output of this
to a peer for him to update.
"""
def signed(sr) do
sr.signed
end
@doc"""
Check that a signed binary is correct and merge it into the SignRev.
Returns {true, new_sr} if an update happenned, {false, sr} otherwise.
"""
def merge(sr, signed, pk) do
case Shard.Keys.open(pk, signed) do
{:ok, bin} ->
{rev, new_term} = SData.term_unbin bin
if rev > sr.rev do
{true, %__MODULE__{term: new_term, rev: rev, signed: signed}}
else
{false, sr}
end
_ ->
{false, sr}
end
end
end