Current section
Files
Jump to
Current section
Files
lib/xander/handshake/response.ex
defmodule Xander.Handshake.Response do
@moduledoc """
This module is responsible for validating and parsing the
handshake response from a Cardano node.
"""
defstruct [:type, :version_number, :network_magic, :query]
alias Xander.Util
@supported_versions [32783, 32784, 32785]
@msg_accept_version 1
@msg_refuse 2
@msg_query_reply 3
@doc """
Validates the handshake response from a Cardano node.
## Examples
# Valid msgAcceptVersion response
iex> payload = CBOR.encode([1, 32784, [764824073, %{"versions" => [1, 2, 3]}]])
iex> response = Xander.Util.plex_encode(payload)
iex> {:ok, result} = Xander.Handshake.Response.validate(response)
iex> result.type == :msg_accept_version and result.version_number == 32784
true
# Unsupported version in msgAcceptVersion
iex> payload = CBOR.encode([1, 12345, [764824073, %{"versions" => [1, 2, 3]}]])
iex> response = Xander.Util.plex_encode(payload)
iex> Xander.Handshake.Response.validate(response)
{:error, "Only versions [32783, 32784, 32785] are supported."}
# Version mismatch refuse response
iex> payload = CBOR.encode([2, [0, <<1, 2, 3, 4>>]])
iex> response = Xander.Util.plex_encode(payload)
iex> {:refused, result} = Xander.Handshake.Response.validate(response)
iex> result.type
:version_mismatch
# Handshake decode error refuse response
iex> payload = CBOR.encode([2, [1, 32784, "decode error"]])
iex> response = Xander.Util.plex_encode(payload)
iex> {:refused, result} = Xander.Handshake.Response.validate(response)
iex> result.type
:handshake_decode_error
# Refused version refuse response
iex> payload = CBOR.encode([2, [2, 32784, "refused"]])
iex> response = Xander.Util.plex_encode(payload)
iex> {:refused, result} = Xander.Handshake.Response.validate(response)
iex> result.type
:refused
# Unknown refuse reason
iex> payload = CBOR.encode([2, [99, "unknown"]])
iex> response = Xander.Util.plex_encode(payload)
iex> {:refused, result} = Xander.Handshake.Response.validate(response)
iex> result.type
:unknown_refuse_reason
# Query reply response
iex> tag = %CBOR.Tag{tag: :bytes, value: "supported"}
iex> payload = CBOR.encode([3, %{tag => [32783, 32784, 32785]}])
iex> response = Xander.Util.plex_encode(payload)
iex> Xander.Handshake.Response.validate(response)
{:versions, %{%CBOR.Tag{tag: :bytes, value: "supported"} => [32783, 32784, 32785]}}
# Unknown message type
iex> payload = CBOR.encode([99, "unknown"])
iex> response = Xander.Util.plex_encode(payload)
iex> Xander.Handshake.Response.validate(response)
{:error, "Unknown message format"}
# Error in CBOR decoding
iex> response = <<0, 1, 2, 3>> # Invalid CBOR
iex> Xander.Handshake.Response.validate(response)
{:error, :invalid_format}
"""
def validate(response) do
with {:ok, %{payload: payload}} <- Util.plex(response),
{:ok, decoded} <- decode_cbor(payload) do
process_decoded_message(decoded)
end
end
defp decode_cbor(payload) do
case CBOR.decode(payload) do
{:ok, decoded, ""} -> {:ok, decoded}
{:error, reason} -> {:error, reason}
end
end
# Handle msgAcceptVersion (1)
defp process_decoded_message([@msg_accept_version, version, [magic, query]]) do
if version in @supported_versions do
{:ok,
%__MODULE__{
network_magic: magic,
query: query,
type: :msg_accept_version,
version_number: version
}}
else
{:error, "Only versions #{inspect(@supported_versions)} are supported."}
end
end
# Handle msgRefuse (2)
defp process_decoded_message([@msg_refuse, refuse_reason]) do
process_refuse_reason(refuse_reason)
end
# Handle msgQueryReply (3)
defp process_decoded_message([@msg_query_reply, version_table]) do
# TODO: parse version_table
{:versions, version_table}
end
defp process_decoded_message(_) do
{:error, "Unknown message format"}
end
defp process_refuse_reason([0, _version_number_binary]) do
# TODO: return accepted versions; reduce to 32783-32785
{:refused, %__MODULE__{type: :version_mismatch}}
end
defp process_refuse_reason([1, _any_version_number, _tstr]) do
{:refused, %__MODULE__{type: :handshake_decode_error}}
end
defp process_refuse_reason([2, _any_version_number, _tstr]) do
{:refused, %__MODULE__{type: :refused}}
end
defp process_refuse_reason(_) do
{:refused, %__MODULE__{type: :unknown_refuse_reason}}
end
end