Packages
baobab
0.21.1
0.35.1
0.35.0
0.34.0
0.31.0
0.30.0
0.21.2
0.21.1
0.21.0
0.20.0
0.19.2
0.19.1
0.19.0
0.18.1
0.18.0
0.17.1
0.17.0
0.16.8
0.16.7
0.16.6
0.16.5
0.16.4
0.16.3
0.16.2
0.16.1
0.16.0
0.15.2
0.15.1
0.15.0
0.14.1
0.14.0
0.13.1
0.13.0
0.12.1
0.12.0
0.11.1
0.11.0
0.10.6
0.10.5
0.10.4
0.10.3
0.10.2
0.10.1
0.10.0
0.9.4
0.9.3
0.9.2
0.9.1
0.9.0
0.8.3
0.8.2
0.8.1
0.8.0
0.7.0
0.6.0
0.5.2
0.5.1
0.5.0
0.4.0
0.3.3
0.3.2
0.3.1
0.3.0
0.2.1
0.2.0
0.1.0
Bamboo append-only logs
Current section
Files
Jump to
Current section
Files
lib/baobab/entry.ex
defmodule Baobab.Entry do
alias Baobab.Entry.Validator
alias Baobab.{Identity, Persistence}
@moduledoc """
A struct representing a Baobab entry
"""
defstruct tag: <<0>>,
author:
<<0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0>>,
log_id: 0,
seqnum: 0,
lipmaalink: nil,
backlink: nil,
size: 0,
payload_hash:
<<0, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0>>,
sig:
<<0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0>>,
payload: ""
@doc false
def create(payload, clump_id, identity, log_id) do
author = Identity.key(identity, :public)
signer = Identity.key(identity, :signing)
prev = Baobab.max_seqnum(author, log_id: log_id, clump_id: clump_id)
seq = prev + 1
head = <<0>> <> author <> Varu64.encode(log_id) <> Varu64.encode(seq)
ll =
case Lipmaa.linkseq(seq) do
^prev -> <<>>
n -> Persistence.content(:entry, :hash, {author, log_id, n}, clump_id)
end
bl =
case prev do
0 -> <<>>
n -> Persistence.content(:entry, :hash, {author, log_id, n}, clump_id)
end
tail = Varu64.encode(byte_size(payload)) <> YAMFhash.create(payload, 0)
meat = head <> ll <> bl <> tail
sig = :enacl.sign_detached(meat, signer)
entry = meat <> sig
Persistence.content(:both, :write, {author, log_id, seq}, clump_id, {entry, payload})
{final, ""} = (entry <> payload) |> from_binary({false, clump_id})
final
end
@doc false
def store(entry, clump_id, replace)
def store(
%Baobab.Entry{
author: author,
log_id: log_id,
seqnum: seq
} = entry,
clump_id,
false
) do
case Persistence.content(:entry, :exists, {author, log_id, seq}, clump_id) do
false -> store(entry, clump_id, true)
true -> entry
end
end
def store(%Baobab.Entry{author: author, log_id: log_id} = entry, clump_id, true) do
case Baobab.ClumpMeta.blocked?({author, log_id, 1}, clump_id) do
true ->
{:error, "Refusing to store for blocked author"}
false ->
case Validator.validate(clump_id, entry) do
%Baobab.Entry{
tag: tag,
log_id: log_id,
seqnum: seq,
lipmaalink: ll,
backlink: bl,
payload: payload,
payload_hash: ph,
sig: sig,
size: size
} ->
contents =
tag <>
author <>
Varu64.encode(log_id) <>
Varu64.encode(seq) <> option(ll) <> option(bl) <> Varu64.encode(size) <> ph <> sig
Persistence.content(
:both,
:write,
{author, log_id, seq},
clump_id,
{contents, payload}
)
entry
error ->
error
end
end
end
def store(_, _, _), do: {:error, "Attempt to store non-Baobab.Entry"}
defp option(val) when is_nil(val), do: <<>>
defp option(val), do: val
@doc false
def delete(author, seq, log_id, clump_id) do
entry_id = {author, log_id, seq}
Persistence.content(:entry, :delete, entry_id, clump_id)
end
@doc false
def from_binaries(stuff, validate, clump_id, acc \\ [])
def from_binaries(:error, _, _, _), do: [{:error, :missing}]
def from_binaries("", _, _, acc), do: Enum.reverse(acc)
def from_binaries(bin, validate, clump_id, acc) do
case {from_binary(bin, clump_id), validate} do
{{%Baobab.Entry{} = entry, rest}, true} ->
Validator.validate(clump_id, entry)
from_binaries(rest, true, clump_id, [entry | acc])
{{%Baobab.Entry{} = entry, rest}, false} ->
from_binaries(rest, true, clump_id, [entry | acc])
_ ->
[{:error, "Could not reify fully"}]
end
end
defp from_binary(<<tag::binary-size(1), author::binary-size(32), rest::binary>>, clump_id) do
add_logid(%Baobab.Entry{tag: tag, author: author}, rest, clump_id)
end
defp add_logid(map, bin, clump_id) do
{logid, rest} = Varu64.decode(bin)
add_sequence_num(Map.put(map, :log_id, logid), rest, clump_id)
end
defp add_sequence_num(map, bin, clump_id) do
{seqnum, rest} = Varu64.decode(bin)
add_lipmaa(Map.put(map, :seqnum, seqnum), rest, clump_id)
end
defp add_lipmaa(%Baobab.Entry{seqnum: 1} = map, bin, clump_id), do: add_size(map, bin, clump_id)
defp add_lipmaa(
%Baobab.Entry{seqnum: seq} = map,
full = <<yamfh::binary-size(66), rest::binary>>,
clump_id
) do
ll = Lipmaa.linkseq(seq)
case ll == seq - 1 do
true -> add_backlink(map, full, clump_id)
false -> add_backlink(Map.put(map, :lipmaalink, yamfh), rest, clump_id)
end
end
defp add_backlink(map, <<yamfh::binary-size(66), rest::binary>>, clump_id) do
add_size(Map.put(map, :backlink, yamfh), rest, clump_id)
end
defp add_size(map, bin, clump_id) do
{size, rest} = Varu64.decode(bin)
add_payload_hash(Map.put(map, :size, size), rest, clump_id)
end
defp add_payload_hash(map, <<yamfh::binary-size(66), rest::binary>>, clump_id) do
add_sig(Map.put(map, :payload_hash, yamfh), rest, clump_id)
end
defp add_sig(map, <<sig::binary-size(64), rest::binary>>, clump_id) do
add_payload(Map.put(map, :sig, sig), rest, clump_id)
end
# If we only got the `entry` portion, assume we might have it on disk
# The `:error` in the struct can act at a signal that we don't
defp add_payload(
%Baobab.Entry{author: author, log_id: log_id, seqnum: seqnum} = map,
"",
clump_id
) do
{Map.put(
map,
:payload,
Persistence.content(:payload, :contents, {author, log_id, seqnum}, clump_id)
), ""}
end
defp add_payload(%Baobab.Entry{size: pbytes} = map, full, _) do
<<payload::binary-size(pbytes), rest::binary>> = full
{Map.put(map, :payload, payload), rest}
end
end