1
using System;
2
using System.Collections.Generic;
3
using System.Collections.Immutable;
4
using System.IO;
5
using System.Linq;
6
using System.Runtime.Serialization.Formatters.Binary;
7
using Libplanet.Blocks;
8
using Libplanet.Crypto;
9
using NetMQ;
10

11
namespace Libplanet.Net.Messages
12
{
13
    internal abstract class Message
14
    {
15
        public const int CommonFrames = 4;
16

17
        internal enum MessageType : byte
18
        {
19
            /// <summary>
20
            /// Check message to determine peer is alive.
21
            /// </summary>
22
            Ping = 0x01,
23

24
            /// <summary>
25
            /// A reply to <see cref="Ping"/>.
26
            /// </summary>
27
            Pong = 0x14,
28

29
            /// <summary>
30
            /// Request to query block hashes.
31
            /// </summary>
32
            GetBlockHashes = 0x04,
33

34
            /// <summary>
35
            /// Inventory to transfer transactions.
36
            /// </summary>
37
            TxIds = 0x06,
38

39
            /// <summary>
40
            /// Request to query blocks.
41
            /// </summary>
42
            GetBlocks = 0x07,
43

44
            /// <summary>
45
            /// Request to query transactions.
46
            /// </summary>
47
            GetTxs = 0x08,
48

49
            /// <summary>
50
            /// Message containing serialized blocks.
51
            /// </summary>
52
            Blocks = 0x0a,
53

54
            /// <summary>
55
            /// Message containing serialized transaction.
56
            /// </summary>
57
            Tx = 0x10,
58

59
            /// <summary>
60
            /// Message containing request for nearby peers.
61
            /// </summary>
62
            FindNeighbors = 0x11,
63

64
            /// <summary>
65
            /// Message containing nearby peers.
66
            /// </summary>
67
            Neighbors = 0x12,
68

69
            /// <summary>
70
            /// Request to query calculated states.
71
            /// </summary>
72
            GetRecentStates = 0x0b,
73

74
            /// <summary>
75
            /// A reply to <see cref="GetRecentStates"/>.
76
            /// Contains the calculated recent states and state references.
77
            /// </summary>
78
            RecentStates = 0x13,
79

80
            /// <summary>
81
            /// Message containing a single <see cref="BlockHeader"/>.
82
            /// </summary>
83
            BlockHeaderMessage = 0x0c,
84

85
            /// <summary>
86
            /// Message containing demand block hashes with their index numbers.
87
            /// </summary>
88
            BlockHashes = 0x0e,
89

90
            /// <summary>
91
            /// Request current chain status of the peer.
92
            /// </summary>
93
            GetChainStatus = 0x20,
94

95
            /// <summary>
96
            /// A reply to <see cref="GetChainStatus"/>.
97
            /// Contains the chain status of the peer at the moment.
98
            /// </summary>
99
            ChainStatus = 0x24,
100

101
            /// <summary>
102
            /// Request a block's delta states.
103
            /// </summary>
104
            GetBlockStates = 0x22,
105

106
            /// <summary>
107
            /// A reply to <see cref="GetBlockStates"/>.
108
            /// Contains the delta states of the requested block.
109
            /// </summary>
110
            BlockStates = 0x23,
111

112
            /// <summary>
113
            /// A reply to any messages with different <see cref="AppProtocolVersion"/>.
114
            /// Contains the expected and actual <see cref="AppProtocolVersion"/>
115
            /// value of the message.
116
            /// </summary>
117
            DifferentVersion = 0x30,
118
        }
119

120
        private enum MessageFrame
121
        {
122
            /// <summary>
123
            /// Frame containing <see cref="AppProtocolVersion"/>.
124
            /// </summary>
125
            Version = 0,
126

127
            /// <summary>
128
            /// Frame containing <see cref="MessageType"/>.
129
            /// </summary>
130
            Type = 1,
131

132
            /// <summary>
133
            /// Frame containing the sender <see cref="Peer"/> of the <see cref="Message"/>.
134
            /// </summary>
135
            Peer = 2,
136

137
            /// <summary>
138
            /// Frame containing signature of the <see cref="Message"/>.
139
            /// </summary>
140
            Sign = 3,
141
        }
142

143 1
        public byte[] Identity { get; set; }
144

145 1
        public AppProtocolVersion Version { get; set; }
146

147 1
        public Peer Remote { get; set; }
148

149
        protected abstract MessageType Type { get; }
150

151
        protected abstract IEnumerable<NetMQFrame> DataFrames { get; }
152

153
        public static Message Parse(
154
            NetMQMessage raw,
155
            bool reply,
156
            AppProtocolVersion localVersion,
157
            IImmutableSet<PublicKey> trustedAppProtocolVersionSigners,
158
            DifferentAppProtocolVersionEncountered differentAppProtocolVersionEncountered)
159 1
        {
160 1
            if (raw.FrameCount == 0)
161 0
            {
162 0
                throw new ArgumentException("Can't parse empty NetMQMessage.");
163
            }
164

165
            // (reply == true)  [version, type, peer, sign, frames...]
166
            // (reply == false) [identity, version, type, peer, sign, frames...]
167 1
            NetMQFrame[] remains = reply ? raw.ToArray() : raw.Skip(1).ToArray();
168

169 1
            var versionToken = remains[(int)MessageFrame.Version].ConvertToString();
170

171 1
            AppProtocolVersion remoteVersion = AppProtocolVersion.FromToken(versionToken);
172 1
            Peer remotePeer = null;
173
            try
174 1
            {
175 1
                remotePeer = DeserializePeer(remains[(int)MessageFrame.Peer].ToByteArray());
176 1
            }
177 1
            catch (Exception)
178 1
            {
179
                // If failed to find out remotePeer, leave it null.
180 1
            }
181

182 1
            if (!IsAppProtocolVersionValid(
183 1
                remotePeer,
184 1
                localVersion,
185 1
                remoteVersion,
186 1
                trustedAppProtocolVersionSigners,
187 1
                differentAppProtocolVersionEncountered))
188 1
            {
189 1
                throw new DifferentAppProtocolVersionException(
190 1
                    "Received message's version is not valid.",
191 1
                    reply ? null : raw[0].Buffer.ToArray(),
192 1
                    localVersion,
193 1
                    remoteVersion);
194
            }
195

196 1
            var rawType = (MessageType)remains[(int)MessageFrame.Type].ConvertToInt32();
197 1
            var peer = remains[(int)MessageFrame.Peer].ToByteArray();
198 1
            byte[] signature = remains[(int)MessageFrame.Sign].ToByteArray();
199

200 1
            NetMQFrame[] body = remains.Skip(CommonFrames).ToArray();
201

202
            // FIXME: The below code is too repetitive and prone to miss to add, which means,
203
            // when you add a new message type, you adds an enum member to MessageType and
204
            // a corresponding subclass of Message, but misses to add that correspondence here,
205
            // you may take a long time to be aware you've missed here, because the code is still
206
            // built well and it looks like just Swarm<T> silently ignore new messages.
207
            // At least this correspondence map should not be here.
208 1
            var types = new Dictionary<MessageType, Type>
209 1
            {
210 1
                { MessageType.Ping, typeof(Ping) },
211 1
                { MessageType.Pong, typeof(Pong) },
212 1
                { MessageType.GetBlockHashes, typeof(GetBlockHashes) },
213 1
                { MessageType.BlockHashes, typeof(BlockHashes) },
214 1
                { MessageType.TxIds, typeof(TxIds) },
215 1
                { MessageType.GetBlocks, typeof(GetBlocks) },
216 1
                { MessageType.GetTxs, typeof(GetTxs) },
217 1
                { MessageType.Blocks, typeof(Blocks) },
218 1
                { MessageType.Tx, typeof(Tx) },
219 1
                { MessageType.FindNeighbors, typeof(FindNeighbors) },
220 1
                { MessageType.Neighbors, typeof(Neighbors) },
221 1
                { MessageType.GetRecentStates, typeof(GetRecentStates) },
222 1
                { MessageType.RecentStates, typeof(RecentStates) },
223 1
                { MessageType.BlockHeaderMessage, typeof(BlockHeaderMessage) },
224 1
                { MessageType.GetChainStatus, typeof(GetChainStatus) },
225 1
                { MessageType.ChainStatus, typeof(ChainStatus) },
226 1
                { MessageType.GetBlockStates, typeof(GetBlockStates) },
227 1
                { MessageType.BlockStates, typeof(BlockStates) },
228 1
                { MessageType.DifferentVersion, typeof(DifferentVersion) },
229 1
            };
230

231 1
            if (!types.TryGetValue(rawType, out Type type))
232 0
            {
233 0
                throw new ArgumentException(
234 0
                    $"Can't determine NetMQMessage. [type: {rawType}]",
235 0
                    nameof(raw)
236 0
                );
237
            }
238

239 1
            var message = (Message)Activator.CreateInstance(
240 1
                type, new[] { body });
241 1
            message.Version = remoteVersion;
242 1
            message.Remote = remotePeer;
243

244 1
            if (!message.Remote.PublicKey.Verify(body.ToByteArray(), signature))
245 0
            {
246 0
                throw new InvalidMessageException("The message signature is invalid", message);
247
            }
248

249 1
            if (!reply)
250 1
            {
251 1
                message.Identity = raw[0].Buffer.ToArray();
252 1
            }
253

254 1
            return message;
255 1
        }
256

257
        public NetMQMessage ToNetMQMessage(PrivateKey key, Peer peer, AppProtocolVersion version)
258 1
        {
259 1
            if (peer is null)
260 0
            {
261 0
                throw new ArgumentNullException(nameof(peer));
262
            }
263

264 1
            var message = new NetMQMessage();
265

266
            // Write body (by concrete class)
267 1
            foreach (NetMQFrame frame in DataFrames)
268 1
            {
269 1
                message.Append(frame);
270 1
            }
271

272
            // Write headers. (inverse order)
273 1
            message.Push(key.Sign(message.ToByteArray()));
274 1
            message.Push(SerializePeer(peer));
275 1
            message.Push((byte)Type);
276 1
            message.Push(version.Token);
277

278 1
            if (Identity is byte[] to)
279 1
            {
280 1
                message.Push(to);
281 1
            }
282

283 1
            return message;
284 1
        }
285

286
        protected static Peer DeserializePeer(byte[] bytes)
287 1
        {
288 1
            var formatter = new BinaryFormatter();
289 1
            using MemoryStream stream = new MemoryStream(bytes);
290 1
            return (Peer)formatter.Deserialize(stream);
291 1
        }
292

293
        protected byte[] SerializePeer(Peer peer)
294 1
        {
295 1
            var formatter = new BinaryFormatter();
296 1
            using MemoryStream stream = new MemoryStream();
297 1
            formatter.Serialize(stream, peer);
298 1
            return stream.ToArray();
299 1
        }
300

301
        private static bool IsAppProtocolVersionValid(
302
            Peer remotePeer,
303
            AppProtocolVersion localVersion,
304
            AppProtocolVersion remoteVersion,
305
            IImmutableSet<PublicKey> trustedAppProtocolVersionSigners,
306
            DifferentAppProtocolVersionEncountered differentAppProtocolVersionEncountered)
307 1
        {
308 1
            if (remoteVersion.Equals(localVersion))
309 1
            {
310 1
                return true;
311
            }
312

313 1
            if (!(trustedAppProtocolVersionSigners is null) &&
314 1
                !trustedAppProtocolVersionSigners.Any(remoteVersion.Verify))
315 1
            {
316 1
                return false;
317
            }
318

319 1
            if (differentAppProtocolVersionEncountered is null)
320 1
            {
321 1
                return false;
322
            }
323

324 1
            return differentAppProtocolVersionEncountered(remotePeer, remoteVersion, localVersion);
325 1
        }
326
    }
327
}

Read our documentation on viewing source code .

Loading