@@ -8,48 +8,133 @@ defmodule Xander.Handshake.Response do
88
99 alias Xander.Util
1010
11+ @ supported_versions [ 32783 , 32784 , 32785 ]
12+ @ msg_accept_version 1
13+ @ msg_refuse 2
14+ @ msg_query_reply 3
15+
1116 @ doc """
1217 Validates the handshake response from a Cardano node.
18+
19+ ## Examples
20+
21+ # Valid msgAcceptVersion response
22+ iex> payload = CBOR.encode([1, 32784, [764824073, %{"versions" => [1, 2, 3]}]])
23+ iex> response = Xander.Util.plex_encode(payload)
24+ iex> {:ok, result} = Xander.Handshake.Response.validate(response)
25+ iex> result.type == :msg_accept_version and result.version_number == 32784
26+ true
27+
28+ # Unsupported version in msgAcceptVersion
29+ iex> payload = CBOR.encode([1, 12345, [764824073, %{"versions" => [1, 2, 3]}]])
30+ iex> response = Xander.Util.plex_encode(payload)
31+ iex> Xander.Handshake.Response.validate(response)
32+ {:error, "Only versions [32783, 32784, 32785] are supported."}
33+
34+ # Version mismatch refuse response
35+ iex> payload = CBOR.encode([2, [0, <<1, 2, 3, 4>>]])
36+ iex> response = Xander.Util.plex_encode(payload)
37+ iex> {:refused, result} = Xander.Handshake.Response.validate(response)
38+ iex> result.type
39+ :version_mismatch
40+
41+ # Handshake decode error refuse response
42+ iex> payload = CBOR.encode([2, [1, 32784, "decode error"]])
43+ iex> response = Xander.Util.plex_encode(payload)
44+ iex> {:refused, result} = Xander.Handshake.Response.validate(response)
45+ iex> result.type
46+ :handshake_decode_error
47+
48+ # Refused version refuse response
49+ iex> payload = CBOR.encode([2, [2, 32784, "refused"]])
50+ iex> response = Xander.Util.plex_encode(payload)
51+ iex> {:refused, result} = Xander.Handshake.Response.validate(response)
52+ iex> result.type
53+ :refused
54+
55+ # Unknown refuse reason
56+ iex> payload = CBOR.encode([2, [99, "unknown"]])
57+ iex> response = Xander.Util.plex_encode(payload)
58+ iex> {:refused, result} = Xander.Handshake.Response.validate(response)
59+ iex> result.type
60+ :unknown_refuse_reason
61+
62+ # Query reply response
63+ iex> tag = %CBOR.Tag{tag: :bytes, value: "supported"}
64+ iex> payload = CBOR.encode([3, %{tag => [32783, 32784, 32785]}])
65+ iex> response = Xander.Util.plex_encode(payload)
66+ iex> Xander.Handshake.Response.validate(response)
67+ {:versions, %{%CBOR.Tag{tag: :bytes, value: "supported"} => [32783, 32784, 32785]}}
68+
69+ # Unknown message type
70+ iex> payload = CBOR.encode([99, "unknown"])
71+ iex> response = Xander.Util.plex_encode(payload)
72+ iex> Xander.Handshake.Response.validate(response)
73+ {:error, "Unknown message format"}
74+
75+ # Error in CBOR decoding
76+ iex> response = <<0, 1, 2, 3>> # Invalid CBOR
77+ iex> Xander.Handshake.Response.validate(response)
78+ {:error, :invalid_format}
1379 """
1480 def validate ( response ) do
15- % { payload: payload } = Util . plex ( response )
81+ with { :ok , % { payload: payload } } <- Util . plex ( response ) ,
82+ { :ok , decoded } <- decode_cbor ( payload ) do
83+ process_decoded_message ( decoded )
84+ end
85+ end
1686
87+ defp decode_cbor ( payload ) do
1788 case CBOR . decode ( payload ) do
18- # msgAcceptVersion
19- { :ok , [ 1 , version , [ magic , query ] ] , "" } ->
20- if version in [ 32783 , 32784 ] do
21- { :ok ,
22- % __MODULE__ {
23- network_magic: magic ,
24- query: query ,
25- type: :msg_accept_version ,
26- version_number: version
27- } }
28- else
29- { :error , "Only versions 32783 and 32784 are supported." }
30- end
31-
32- # msgRefuse
33- { :ok , [ 2 , refuse_reason ] , "" } ->
34- case refuse_reason do
35- # TODO: return accepted versions; reduce to 32783 and 32784
36- [ 0 , _version_number_binary ] ->
37- { :refused , % __MODULE__ { type: :version_mismatch } }
38-
39- [ 1 , _anyVersionNumber , _tstr ] ->
40- { :refused , % __MODULE__ { type: :handshake_decode_error } }
41-
42- [ 2 , _anyVersionNumber , _tstr ] ->
43- { :refused , % __MODULE__ { type: :refused } }
44- end
45-
46- # TODO: parse version_table
47- # msgQueryReply
48- { :ok , [ 3 , version_table ] , "" } ->
49- { :versions , version_table }
50-
51- { :error , reason } ->
52- { :error , reason }
89+ { :ok , decoded , "" } -> { :ok , decoded }
90+ { :error , reason } -> { :error , reason }
91+ end
92+ end
93+
94+ # Handle msgAcceptVersion (1)
95+ defp process_decoded_message ( [ @ msg_accept_version , version , [ magic , query ] ] ) do
96+ if version in @ supported_versions do
97+ { :ok ,
98+ % __MODULE__ {
99+ network_magic: magic ,
100+ query: query ,
101+ type: :msg_accept_version ,
102+ version_number: version
103+ } }
104+ else
105+ { :error , "Only versions #{ inspect ( @ supported_versions ) } are supported." }
53106 end
54107 end
108+
109+ # Handle msgRefuse (2)
110+ defp process_decoded_message ( [ @ msg_refuse , refuse_reason ] ) do
111+ process_refuse_reason ( refuse_reason )
112+ end
113+
114+ # Handle msgQueryReply (3)
115+ defp process_decoded_message ( [ @ msg_query_reply , version_table ] ) do
116+ # TODO: parse version_table
117+ { :versions , version_table }
118+ end
119+
120+ defp process_decoded_message ( _ ) do
121+ { :error , "Unknown message format" }
122+ end
123+
124+ defp process_refuse_reason ( [ 0 , _version_number_binary ] ) do
125+ # TODO: return accepted versions; reduce to 32783-32785
126+ { :refused , % __MODULE__ { type: :version_mismatch } }
127+ end
128+
129+ defp process_refuse_reason ( [ 1 , _any_version_number , _tstr ] ) do
130+ { :refused , % __MODULE__ { type: :handshake_decode_error } }
131+ end
132+
133+ defp process_refuse_reason ( [ 2 , _any_version_number , _tstr ] ) do
134+ { :refused , % __MODULE__ { type: :refused } }
135+ end
136+
137+ defp process_refuse_reason ( _ ) do
138+ { :refused , % __MODULE__ { type: :unknown_refuse_reason } }
139+ end
55140end
0 commit comments