]> git.lizzy.rs Git - minetest.git/blob - util/wireshark/minetest.lua
Add keybind to swap items between hands
[minetest.git] / util / wireshark / minetest.lua
1 -- minetest.lua
2 -- Packet dissector for the UDP-based Minetest protocol
3 -- Copy this to $HOME/.wireshark/plugins/
4
5
6 --
7 -- Minetest
8 -- Copyright (C) 2011 celeron55, Perttu Ahola <celeron55@gmail.com>
9 --
10 -- This program is free software; you can redistribute it and/or modify
11 -- it under the terms of the GNU General Public License as published by
12 -- the Free Software Foundation; either version 2 of the License, or
13 -- (at your option) any later version.
14 --
15 -- This program is distributed in the hope that it will be useful,
16 -- but WITHOUT ANY WARRANTY; without even the implied warranty of
17 -- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18 -- GNU General Public License for more details.
19 --
20 -- You should have received a copy of the GNU General Public License along
21 -- with this program; if not, write to the Free Software Foundation, Inc.,
22 -- 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
23 --
24
25
26 -- Wireshark documentation:
27 -- https://web.archive.org/web/20170711121726/https://www.wireshark.org/docs/wsdg_html_chunked/lua_module_Proto.html
28 -- https://web.archive.org/web/20170711121844/https://www.wireshark.org/docs/wsdg_html_chunked/lua_module_Tree.html
29 -- https://web.archive.org/web/20170711121917/https://www.wireshark.org/docs/wsdg_html_chunked/lua_module_Tvb.html
30
31
32 -- Table of Contents:
33 --   Part 1: Utility functions
34 --   Part 2: Client command dissectors (TOSERVER_*)
35 --   Part 3: Server command dissectors (TOCLIENT_*)
36 --   Part 4: Wrapper protocol subdissectors
37 --   Part 5: Wrapper protocol main dissector
38 --   Part 6: Utility functions part 2
39
40
41 -----------------------
42 -- Part 1            --
43 -- Utility functions --
44 -----------------------
45
46 -- Creates two ProtoFields to hold a length and variable-length text content
47 -- lentype must be either "uint16" or "uint32"
48 function minetest_field_helper(lentype, name, abbr)
49         local f_textlen = ProtoField[lentype](name .. "len", abbr .. " (length)", base.DEC)
50         local f_text = ProtoField.string(name, abbr)
51         return f_textlen, f_text
52 end
53
54
55
56
57 --------------------------------------------
58 -- Part 2                                 --
59 -- Client command dissectors (TOSERVER_*) --
60 --------------------------------------------
61
62 minetest_client_commands = {}
63 minetest_client_obsolete = {}
64
65 -- TOSERVER_INIT
66
67 do
68         local abbr = "minetest.client.init_"
69
70         local f_ser_fmt = ProtoField.uint8(abbr.."ser_version",
71                 "Maximum serialization format version", base.DEC)
72         local f_comp_modes = ProtoField.uint16(abbr.."compression",
73                 "Supported compression modes", base.DEC, { [0] = "No compression" })
74         local f_proto_min = ProtoField.uint16(abbr.."proto_min", "Minimum protocol version", base.DEC)
75         local f_proto_max = ProtoField.uint16(abbr.."_proto_max", "Maximum protocol version", base.DEC)
76         local f_player_namelen, f_player_name =
77                 minetest_field_helper("uint16", abbr.."player_name", "Player Name")
78
79         minetest_client_commands[0x02] = {
80                 "INIT",                            -- Command name
81                 11,                                -- Minimum message length including code
82                 { f_ser_fmt,                       -- List of fields [optional]
83                   f_comp_modes,
84                   f_proto_min,
85                   f_proto_max,
86                   f_player_namelen,
87                   f_player_name },
88                 function(buffer, pinfo, tree, t)   -- Dissector function [optional]
89                         t:add(f_ser_fmt, buffer(2,1))
90                         t:add(f_comp_modes, buffer(3,2))
91                         t:add(f_proto_min, buffer(5,2))
92                         t:add(f_proto_max, buffer(7,2))
93                         minetest_decode_helper_ascii(buffer, t, "uint16", 9, f_player_namelen, f_player_name)
94                 end
95         }
96 end
97
98 -- TOSERVER_INIT_LEGACY (obsolete)
99
100 minetest_client_commands[0x10] = { "INIT_LEGACY", 53 }
101 minetest_client_obsolete[0x10] = true
102
103 -- TOSERVER_INIT2
104
105 do
106         local f_langlen, f_lang =
107                 minetest_field_helper("uint16", "minetest.client.init2_language", "Language Code")
108
109         minetest_client_commands[0x11] = {
110                 "INIT2",
111                 2,
112                 { f_langlen,
113                   f_lang },
114                 function(buffer, pinfo, tree, t)
115                         minetest_decode_helper_ascii(buffer, t, "uint16", 2, f_langlen, f_lang)
116                 end
117         }
118 end
119
120 -- TOSERVER_MODCHANNEL_JOIN
121
122 minetest_client_commands[0x17] = { "MODCHANNEL_JOIN", 2 }
123
124 -- TOSERVER_MODCHANNEL_LEAVE
125
126 minetest_client_commands[0x18] = { "MODCHANNEL_LEAVE", 2 }
127
128 -- TOSERVER_MODCHANNEL_MSG
129
130 minetest_client_commands[0x19] = { "MODCHANNEL_MSG", 2 }
131
132 -- TOSERVER_GETBLOCK (obsolete)
133
134 minetest_client_commands[0x20] = { "GETBLOCK", 2 }
135 minetest_client_obsolete[0x20] = true
136
137 -- TOSERVER_ADDNODE (obsolete)
138
139 minetest_client_commands[0x21] = { "ADDNODE", 2 }
140 minetest_client_obsolete[0x21] = true
141
142 -- TOSERVER_REMOVENODE (obsolete)
143
144 minetest_client_commands[0x22] = { "REMOVENODE", 2 }
145 minetest_client_obsolete[0x22] = true
146
147 -- TOSERVER_PLAYERPOS
148
149 do
150         local abbr = "minetest.client.playerpos_"
151
152         local f_x = ProtoField.int32(abbr.."x", "Position X", base.DEC)
153         local f_y = ProtoField.int32(abbr.."y", "Position Y", base.DEC)
154         local f_z = ProtoField.int32(abbr.."z", "Position Z", base.DEC)
155         local f_speed_x = ProtoField.int32(abbr.."speed_x", "Speed X", base.DEC)
156         local f_speed_y = ProtoField.int32(abbr.."speed_y", "Speed Y", base.DEC)
157         local f_speed_z = ProtoField.int32(abbr.."speed_z", "Speed Z", base.DEC)
158         local f_pitch = ProtoField.int32(abbr.."pitch", "Pitch", base.DEC)
159         local f_yaw = ProtoField.int32(abbr.."yaw", "Yaw", base.DEC)
160         local f_key_pressed = ProtoField.bytes(abbr.."key_pressed", "Pressed keys")
161         local f_fov = ProtoField.uint8(abbr.."fov", "FOV", base.DEC)
162         local f_wanted_range = ProtoField.uint8(abbr.."wanted_range", "Requested view range", base.DEC)
163
164         minetest_client_commands[0x23] = {
165                 "PLAYERPOS", 34,
166                 { f_x, f_y, f_z, f_speed_x, f_speed_y, f_speed_z, f_pitch, f_yaw,
167                   f_key_pressed, f_fov, f_wanted_range },
168                 function(buffer, pinfo, tree, t)
169                         t:add(f_x, buffer(2,4))
170                         t:add(f_y, buffer(6,4))
171                         t:add(f_z, buffer(10,4))
172                         t:add(f_speed_x, buffer(14,4))
173                         t:add(f_speed_y, buffer(18,4))
174                         t:add(f_speed_z, buffer(22,4))
175                         t:add(f_pitch, buffer(26,4))
176                         t:add(f_yaw, buffer(30,4))
177                         t:add(f_key_pressed, buffer(34,4))
178                         t:add(f_fov, buffer(38,1))
179                         t:add(f_wanted_range, buffer(39,1))
180                 end
181         }
182 end
183
184 -- TOSERVER_GOTBLOCKS
185
186 do
187         local f_count = ProtoField.uint8("minetest.client.gotblocks_count", "Count", base.DEC)
188         local f_block = ProtoField.bytes("minetest.client.gotblocks_block", "Block", base.NONE)
189         local f_x = ProtoField.int16("minetest.client.gotblocks_x", "Block position X", base.DEC)
190         local f_y = ProtoField.int16("minetest.client.gotblocks_y", "Block position Y", base.DEC)
191         local f_z = ProtoField.int16("minetest.client.gotblocks_z", "Block position Z", base.DEC)
192
193         minetest_client_commands[0x24] = {
194                 "GOTBLOCKS", 3,
195                 { f_count, f_block, f_x, f_y, f_z },
196                 function(buffer, pinfo, tree, t)
197                         t:add(f_count, buffer(2,1))
198                         local count = buffer(2,1):uint()
199                         if minetest_check_length(buffer, 3 + 6*count, t) then
200                                 pinfo.cols.info:append(" * " .. count)
201                                 local index
202                                 for index = 0, count - 1 do
203                                         local pos = 3 + 6*index
204                                         local t2 = t:add(f_block, buffer(pos, 6))
205                                         t2:set_text("Block, X: " .. buffer(pos, 2):int()
206                                                 .. ", Y: " .. buffer(pos + 2, 2):int()
207                                                 .. ", Z: " .. buffer(pos + 4, 2):int())
208                                         t2:add(f_x, buffer(pos, 2))
209                                         t2:add(f_y, buffer(pos + 2, 2))
210                                         t2:add(f_z, buffer(pos + 4, 2))
211                                 end
212                         end
213                 end
214         }
215 end
216
217 -- TOSERVER_DELETEDBLOCKS
218
219 do
220         local f_count = ProtoField.uint8("minetest.client.deletedblocks_count", "Count", base.DEC)
221         local f_block = ProtoField.bytes("minetest.client.deletedblocks_block", "Block", base.NONE)
222         local f_x = ProtoField.int16("minetest.client.deletedblocks_x", "Block position X", base.DEC)
223         local f_y = ProtoField.int16("minetest.client.deletedblocks_y", "Block position Y", base.DEC)
224         local f_z = ProtoField.int16("minetest.client.deletedblocks_z", "Block position Z", base.DEC)
225
226         minetest_client_commands[0x25] = {
227                 "DELETEDBLOCKS", 3,
228                 { f_count, f_block, f_x, f_y, f_z },
229                 function(buffer, pinfo, tree, t)
230                         t:add(f_count, buffer(2,1))
231                         local count = buffer(2,1):uint()
232                         if minetest_check_length(buffer, 3 + 6*count, t) then
233                                 pinfo.cols.info:append(" * " .. count)
234                                 local index
235                                 for index = 0, count - 1 do
236                                         local pos = 3 + 6*index
237                                         local t2 = t:add(f_block, buffer(pos, 6))
238                                         t2:set_text("Block, X: " .. buffer(pos, 2):int()
239                                                 .. ", Y: " .. buffer(pos + 2, 2):int()
240                                                 .. ", Z: " .. buffer(pos + 4, 2):int())
241                                         t2:add(f_x, buffer(pos, 2))
242                                         t2:add(f_y, buffer(pos + 2, 2))
243                                         t2:add(f_z, buffer(pos + 4, 2))
244                                 end
245                         end
246                 end
247         }
248 end
249
250 -- TOSERVER_ADDNODE_FROM_INVENTORY (obsolete)
251
252 minetest_client_commands[0x26] = { "ADDNODE_FROM_INVENTORY", 2 }
253 minetest_client_obsolete[0x26] = true
254
255 -- TOSERVER_CLICK_OBJECT (obsolete)
256
257 minetest_client_commands[0x27] = { "CLICK_OBJECT", 2 }
258 minetest_client_obsolete[0x27] = true
259
260 -- TOSERVER_GROUND_ACTION (obsolete)
261
262 minetest_client_commands[0x28] = { "GROUND_ACTION", 2 }
263 minetest_client_obsolete[0x28] = true
264
265 -- TOSERVER_RELEASE (obsolete)
266
267 minetest_client_commands[0x29] = { "RELEASE", 2 }
268 minetest_client_obsolete[0x29] = true
269
270 -- TOSERVER_SIGNTEXT (obsolete)
271
272 minetest_client_commands[0x30] = { "SIGNTEXT", 2 }
273 minetest_client_obsolete[0x30] = true
274
275 -- TOSERVER_INVENTORY_ACTION
276
277 do
278         local f_action = ProtoField.string("minetest.client.inventory_action", "Action")
279
280         minetest_client_commands[0x31] = {
281                 "INVENTORY_ACTION", 2,
282                 { f_action },
283                 function(buffer, pinfo, tree, t)
284                         t:add(f_action, buffer(2, buffer:len() - 2))
285                 end
286         }
287 end
288
289 -- TOSERVER_CHAT_MESSAGE
290
291 do
292         local f_length = ProtoField.uint16("minetest.client.chat_message_length", "Length", base.DEC)
293         local f_message = ProtoField.string("minetest.client.chat_message", "Message")
294
295         minetest_client_commands[0x32] = {
296                 "CHAT_MESSAGE", 4,
297                 { f_length, f_message },
298                 function(buffer, pinfo, tree, t)
299                         t:add(f_length, buffer(2,2))
300                         local textlen = buffer(2,2):uint()
301                         if minetest_check_length(buffer, 4 + textlen*2, t) then
302                                 t:add(f_message, buffer(4, textlen*2), buffer(4, textlen*2):ustring())
303                         end
304                 end
305         }
306 end
307
308 -- TOSERVER_SIGNNODETEXT (obsolete)
309
310 minetest_client_commands[0x33] = { "SIGNNODETEXT", 2 }
311 minetest_client_obsolete[0x33] = true
312
313
314 -- TOSERVER_CLICK_ACTIVEOBJECT (obsolete)
315
316 minetest_client_commands[0x34] = { "CLICK_ACTIVEOBJECT", 2 }
317 minetest_client_obsolete[0x34] = true
318
319 -- TOSERVER_DAMAGE
320
321 do
322         local f_amount = ProtoField.uint8("minetest.client.damage_amount", "Amount", base.DEC)
323
324         minetest_client_commands[0x35] = {
325                 "DAMAGE", 3,
326                 { f_amount },
327                 function(buffer, pinfo, tree, t)
328                         t:add(f_amount, buffer(2,1))
329                 end
330         }
331 end
332
333 -- TOSERVER_PASSWORD (obsolete)
334
335 minetest_client_commands[0x36] = { "CLICK_ACTIVEOBJECT", 2 }
336 minetest_client_obsolete[0x36] = true
337
338 -- TOSERVER_PLAYERITEM
339
340 do
341         local f_item = ProtoField.uint16("minetest.client.playeritem_item", "Wielded item")
342
343         minetest_client_commands[0x37] = {
344                 "PLAYERITEM", 4,
345                 { f_item },
346                 function(buffer, pinfo, tree, t)
347                         t:add(f_item, buffer(2,2))
348                 end
349         }
350 end
351
352 -- TOSERVER_RESPAWN
353
354 minetest_client_commands[0x38] = { "RESPAWN", 2 }
355
356 -- TOSERVER_INTERACT
357
358 do
359         local abbr = "minetest.client.interact_"
360         local vs_action = {
361                 [0] = "Start digging",
362                 [1] = "Stop digging",
363                 [2] = "Digging completed",
364                 [3] = "Place block or item",
365                 [4] = "Use item",
366                 [5] = "Activate held item",
367         }
368         local vs_pointed_type = {
369                 [0] = "Nothing",
370                 [1] = "Node",
371                 [2] = "Object",
372         }
373
374         local f_action = ProtoField.uint8(abbr.."action", "Action", base.DEC, vs_action)
375         local f_item = ProtoField.uint16(abbr.."item", "Item Index", base.DEC)
376         local f_plen = ProtoField.uint32(abbr.."plen", "Length of pointed thing", base.DEC)
377         local f_pointed_version = ProtoField.uint8(abbr.."pointed_version",
378                 "Pointed Thing Version", base.DEC)
379         local f_pointed_type = ProtoField.uint8(abbr.."pointed_version",
380                 "Pointed Thing Type", base.DEC, vs_pointed_type)
381         local f_pointed_under_x = ProtoField.int16(abbr.."pointed_under_x",
382                 "Node position (under surface) X")
383         local f_pointed_under_y = ProtoField.int16(abbr.."pointed_under_y",
384                 "Node position (under surface) Y")
385         local f_pointed_under_z = ProtoField.int16(abbr.."pointed_under_z",
386                 "Node position (under surface) Z")
387         local f_pointed_above_x = ProtoField.int16(abbr.."pointed_above_x",
388                 "Node position (above surface) X")
389         local f_pointed_above_y = ProtoField.int16(abbr.."pointed_above_y",
390                 "Node position (above surface) Y")
391         local f_pointed_above_z = ProtoField.int16(abbr.."pointed_above_z",
392                 "Node position (above surface) Z")
393         local f_pointed_object_id = ProtoField.int16(abbr.."pointed_object_id",
394                 "Object ID")
395         -- mising: additional playerpos data just like in TOSERVER_PLAYERPOS
396
397         minetest_client_commands[0x39] = {
398                 "INTERACT", 11,
399                 { f_action,
400                   f_item,
401                   f_plen,
402                   f_pointed_version,
403                   f_pointed_type,
404                   f_pointed_under_x,
405                   f_pointed_under_y,
406                   f_pointed_under_z,
407                   f_pointed_above_x,
408                   f_pointed_above_y,
409                   f_pointed_above_z,
410                   f_pointed_object_id },
411                 function(buffer, pinfo, tree, t)
412                         t:add(f_action, buffer(2,1))
413                         t:add(f_item, buffer(3,2))
414                         t:add(f_plen, buffer(5,4))
415                         local plen = buffer(5,4):uint()
416                         if minetest_check_length(buffer, 9 + plen, t) then
417                                 t:add(f_pointed_version, buffer(9,1))
418                                 t:add(f_pointed_type, buffer(10,1))
419                                 local ptype = buffer(10,1):uint()
420                                 if ptype == 1 then -- Node
421                                         t:add(f_pointed_under_x, buffer(11,2))
422                                         t:add(f_pointed_under_y, buffer(13,2))
423                                         t:add(f_pointed_under_z, buffer(15,2))
424                                         t:add(f_pointed_above_x, buffer(17,2))
425                                         t:add(f_pointed_above_y, buffer(19,2))
426                                         t:add(f_pointed_above_z, buffer(21,2))
427                                 elseif ptype == 2 then -- Object
428                                         t:add(f_pointed_object_id, buffer(11,2))
429                                 end
430                         end
431                 end
432         }
433 end
434
435 -- ...
436
437 minetest_client_commands[0x3a] = { "REMOVED_SOUNDS", 2 }
438 minetest_client_commands[0x3b] = { "NODEMETA_FIELDS", 2 }
439 minetest_client_commands[0x3c] = { "INVENTORY_FIELDS", 2 }
440 minetest_client_commands[0x40] = { "REQUEST_MEDIA", 2 }
441 minetest_client_commands[0x41] = { "RECEIVED_MEDIA", 2 }
442
443 -- TOSERVER_BREATH (obsolete)
444
445 minetest_client_commands[0x42] = { "BREATH", 2 }
446 minetest_client_obsolete[0x42] = true
447
448 -- TOSERVER_CLIENT_READY
449
450 do
451         local abbr = "minetest.client.client_ready_"
452         local f_major = ProtoField.uint8(abbr.."major","Version Major")
453         local f_minor = ProtoField.uint8(abbr.."minor","Version Minor")
454         local f_patch = ProtoField.uint8(abbr.."patch","Version Patch")
455         local f_reserved = ProtoField.uint8(abbr.."reserved","Reserved")
456         local f_versionlen, f_version =
457                 minetest_field_helper("uint16", abbr.."version", "Full Version String")
458         local f_formspec_ver = ProtoField.uint16(abbr.."formspec_version",
459                 "Formspec API version")
460
461         minetest_client_commands[0x43] = {
462                 "CLIENT_READY",
463                 8,
464                 { f_major, f_minor, f_patch, f_reserved, f_versionlen,
465                   f_version, f_formspec_ver },
466                 function(buffer, pinfo, tree, t)
467                         t:add(f_major, buffer(2,1))
468                         t:add(f_minor, buffer(3,1))
469                         t:add(f_patch, buffer(4,1))
470                         t:add(f_reserved, buffer(5,1))
471                         local off = minetest_decode_helper_ascii(buffer, t, "uint16", 6,
472                                 f_versionlen, f_version)
473                         if off and minetest_check_length(buffer, off + 2, t) then
474                                 t:add(f_formspec_ver, buffer(off,2))
475                         end
476                 end
477         }
478 end
479
480 -- ...
481
482 minetest_client_commands[0x50] = { "FIRST_SRP", 2 }
483 minetest_client_commands[0x51] = { "SRP_BYTES_A", 2 }
484 minetest_client_commands[0x52] = { "SRP_BYTES_M", 2 }
485
486
487
488 --------------------------------------------
489 -- Part 3                                 --
490 -- Server command dissectors (TOCLIENT_*) --
491 --------------------------------------------
492
493 minetest_server_commands = {}
494 minetest_server_obsolete = {}
495
496 -- TOCLIENT_HELLO
497
498 do
499         local abbr = "minetest.server.hello_"
500
501         local f_ser_fmt = ProtoField.uint8(abbr.."ser_version",
502                 "Deployed serialization format version", base.DEC)
503         local f_comp_mode = ProtoField.uint16(abbr.."compression",
504                 "Deployed compression mode", base.DEC, { [0] = "No compression" })
505         local f_proto = ProtoField.uint16(abbr.."proto",
506                 "Deployed protocol version", base.DEC)
507         local f_auth_methods = ProtoField.bytes(abbr.."auth_modes",
508                 "Supported authentication modes")
509         local f_legacy_namelen, f_legacy_name = minetest_field_helper("uint16",
510                 abbr.."legacy_name", "Legacy player name for hashing")
511
512         minetest_server_commands[0x02] = {
513                 "HELLO",
514                 13,
515                 { f_ser_fmt, f_comp_mode, f_proto, f_auth_methods,
516                   f_legacy_namelen, f_legacy_name },
517                 function(buffer, pinfo, tree, t)
518                         t:add(f_ser_fmt, buffer(2,1))
519                         t:add(f_comp_mode, buffer(3,2))
520                         t:add(f_proto, buffer(5,2))
521                         t:add(f_auth_methods, buffer(7,4))
522                         minetest_decode_helper_ascii(buffer, t, "uint16", 11, f_legacy_namelen, f_legacy_name)
523                 end
524         }
525 end
526
527 -- TOCLIENT_AUTH_ACCEPT
528
529 do
530         local abbr = "minetest.server.auth_accept_"
531
532         local f_player_x = ProtoField.float(abbr.."player_x", "Player position X")
533         local f_player_y = ProtoField.float(abbr.."player_y", "Player position Y")
534         local f_player_z = ProtoField.float(abbr.."player_z", "Player position Z")
535         local f_map_seed = ProtoField.uint64(abbr.."map_seed", "Map seed")
536         local f_send_interval = ProtoField.float(abbr.."send_interval",
537                 "Recommended send interval")
538         local f_sudo_auth_methods = ProtoField.bytes(abbr.."sudo_auth_methods",
539                 "Supported auth methods for sudo mode")
540
541         minetest_server_commands[0x03] = {
542                 "AUTH_ACCEPT",
543                 30,
544                 { f_player_x, f_player_y, f_player_z, f_map_seed,
545                   f_send_interval, f_sudo_auth_methods },
546                 function(buffer, pinfo, tree, t)
547                         t:add(f_player_x, buffer(2,4))
548                         t:add(f_player_y, buffer(6,4))
549                         t:add(f_player_z, buffer(10,4))
550                         t:add(f_map_seed, buffer(14,8))
551                         t:add(f_send_interval, buffer(22,4))
552                         t:add(f_sudo_auth_methods, buffer(26,4))
553                 end
554         }
555 end
556
557 -- ...
558
559 minetest_server_commands[0x04] = {"ACCEPT_SUDO_MODE", 2}
560 minetest_server_commands[0x05] = {"DENY_SUDO_MODE", 2}
561 minetest_server_commands[0x0A] = {"ACCESS_DENIED", 2}
562
563 -- TOCLIENT_INIT (obsolete)
564
565 minetest_server_commands[0x10] = { "INIT", 2 }
566 minetest_server_obsolete[0x10] = true
567
568 -- TOCLIENT_BLOCKDATA
569
570 do
571         local f_x = ProtoField.int16("minetest.server.blockdata_x", "Block position X", base.DEC)
572         local f_y = ProtoField.int16("minetest.server.blockdata_y", "Block position Y", base.DEC)
573         local f_z = ProtoField.int16("minetest.server.blockdata_z", "Block position Z", base.DEC)
574         local f_data = ProtoField.bytes("minetest.server.blockdata_block", "Serialized MapBlock")
575
576         minetest_server_commands[0x20] = {
577                 "BLOCKDATA", 8,
578                 { f_x, f_y, f_z, f_data },
579                 function(buffer, pinfo, tree, t)
580                         t:add(f_x, buffer(2,2))
581                         t:add(f_y, buffer(4,2))
582                         t:add(f_z, buffer(6,2))
583                         t:add(f_data, buffer(8, buffer:len() - 8))
584                 end
585         }
586 end
587
588 -- TOCLIENT_ADDNODE
589
590 do
591         local f_x = ProtoField.int16("minetest.server.addnode_x", "Position X", base.DEC)
592         local f_y = ProtoField.int16("minetest.server.addnode_y", "Position Y", base.DEC)
593         local f_z = ProtoField.int16("minetest.server.addnode_z", "Position Z", base.DEC)
594         local f_data = ProtoField.bytes("minetest.server.addnode_node", "Serialized MapNode")
595
596         minetest_server_commands[0x21] = {
597                 "ADDNODE", 8,
598                 { f_x, f_y, f_z, f_data },
599                 function(buffer, pinfo, tree, t)
600                         t:add(f_x, buffer(2,2))
601                         t:add(f_y, buffer(4,2))
602                         t:add(f_z, buffer(6,2))
603                         t:add(f_data, buffer(8, buffer:len() - 8))
604                 end
605         }
606 end
607
608 -- TOCLIENT_REMOVENODE
609
610 do
611         local f_x = ProtoField.int16("minetest.server.removenode_x", "Position X", base.DEC)
612         local f_y = ProtoField.int16("minetest.server.removenode_y", "Position Y", base.DEC)
613         local f_z = ProtoField.int16("minetest.server.removenode_z", "Position Z", base.DEC)
614
615         minetest_server_commands[0x22] = {
616                 "REMOVENODE", 8,
617                 { f_x, f_y, f_z },
618                 function(buffer, pinfo, tree, t)
619                         t:add(f_x, buffer(2,2))
620                         t:add(f_y, buffer(4,2))
621                         t:add(f_z, buffer(6,2))
622                 end
623         }
624 end
625
626 -- TOCLIENT_PLAYERPOS (obsolete)
627
628 minetest_server_commands[0x23] = { "PLAYERPOS", 2 }
629 minetest_server_obsolete[0x23] = true
630
631 -- TOCLIENT_PLAYERINFO (obsolete)
632
633 minetest_server_commands[0x24] = { "PLAYERINFO", 2 }
634 minetest_server_obsolete[0x24] = true
635
636 -- TOCLIENT_OPT_BLOCK_NOT_FOUND (obsolete)
637
638 minetest_server_commands[0x25] = { "OPT_BLOCK_NOT_FOUND", 2 }
639 minetest_server_obsolete[0x25] = true
640
641 -- TOCLIENT_SECTORMETA (obsolete)
642
643 minetest_server_commands[0x26] = { "SECTORMETA", 2 }
644 minetest_server_obsolete[0x26] = true
645
646 -- TOCLIENT_INVENTORY
647
648 do
649         local f_inventory = ProtoField.string("minetest.server.inventory", "Inventory")
650
651         minetest_server_commands[0x27] = {
652                 "INVENTORY", 2,
653                 { f_inventory },
654                 function(buffer, pinfo, tree, t)
655                         t:add(f_inventory, buffer(2, buffer:len() - 2))
656                 end
657         }
658 end
659
660 -- TOCLIENT_OBJECTDATA (obsolete)
661
662 minetest_server_commands[0x28] = { "OBJECTDATA", 2 }
663 minetest_server_obsolete[0x28] = true
664
665 -- TOCLIENT_TIME_OF_DAY
666
667 do
668         local f_time = ProtoField.uint16("minetest.server.time_of_day", "Time", base.DEC)
669         local f_time_speed = ProtoField.float("minetest.server.time_speed", "Time Speed", base.DEC)
670
671         minetest_server_commands[0x29] = {
672                 "TIME_OF_DAY", 4,
673                 { f_time, f_time_speed },
674                 function(buffer, pinfo, tree, t)
675                         t:add(f_time, buffer(2,2))
676                         t:add(f_time_speed, buffer(4,4))
677                 end
678         }
679 end
680
681 -- TOCLIENT_CSM_RESTRICTION_FLAGS
682
683 minetest_server_commands[0x2a] = { "CSM_RESTRICTION_FLAGS", 2 }
684
685 -- TOCLIENT_PLAYER_SPEED
686
687 minetest_server_commands[0x2b] = { "PLAYER_SPEED", 2 }
688
689 -- TOCLIENT_CHAT_MESSAGE
690
691 do
692         local abbr = "minetest.server.chat_message_"
693         local vs_type = {
694                 [0] = "Raw",
695                 [1] = "Normal",
696                 [2] = "Announce",
697                 [3] = "System",
698         }
699
700         local f_version = ProtoField.uint8(abbr.."version", "Version")
701         local f_type = ProtoField.uint8(abbr.."type", "Message Type", base.DEC, vs_type)
702         local f_senderlen, f_sender = minetest_field_helper("uint16", abbr.."sender",
703                 "Message sender")
704         local f_messagelen, f_message = minetest_field_helper("uint16", abbr:sub(1,-2),
705                 "Message")
706
707         minetest_server_commands[0x2f] = {
708                 "CHAT_MESSAGE", 8,
709                 { f_version, f_type, f_senderlen, f_sender,
710                   f_messagelen, f_message },
711                 function(buffer, pinfo, tree, t)
712                         t:add(f_version, buffer(2,1))
713                         t:add(f_type, buffer(3,1))
714                         local off = 4
715                         off = minetest_decode_helper_utf16(buffer, t, "uint16", off, f_senderlen, f_sender)
716                         if off then
717                                 off = minetest_decode_helper_utf16(buffer, t, "uint16", off, f_messagelen, f_message)
718                         end
719                 end
720         }
721 end
722
723 -- TOCLIENT_CHAT_MESSAGE_OLD (obsolete)
724
725 minetest_server_commands[0x30] = { "CHAT_MESSAGE_OLD", 2 }
726 minetest_server_obsolete[0x30] = true
727
728 -- TOCLIENT_ACTIVE_OBJECT_REMOVE_ADD
729
730 do
731         local f_removed_count = ProtoField.uint16(
732                 "minetest.server.active_object_remove_add_removed_count",
733                 "Count of removed objects", base.DEC)
734         local f_removed = ProtoField.bytes(
735                 "minetest.server.active_object_remove_add_removed",
736                 "Removed object")
737         local f_removed_id = ProtoField.uint16(
738                 "minetest.server.active_object_remove_add_removed_id",
739                 "ID", base.DEC)
740
741         local f_added_count = ProtoField.uint16(
742                 "minetest.server.active_object_remove_add_added_count",
743                 "Count of added objects", base.DEC)
744         local f_added = ProtoField.bytes(
745                 "minetest.server.active_object_remove_add_added",
746                 "Added object")
747         local f_added_id = ProtoField.uint16(
748                 "minetest.server.active_object_remove_add_added_id",
749                 "ID", base.DEC)
750         local f_added_type = ProtoField.uint8(
751                 "minetest.server.active_object_remove_add_added_type",
752                 "Type", base.DEC)
753         local f_added_init_length = ProtoField.uint32(
754                 "minetest.server.active_object_remove_add_added_init_length",
755                 "Initialization data length", base.DEC)
756         local f_added_init_data = ProtoField.bytes(
757                 "minetest.server.active_object_remove_add_added_init_data",
758                 "Initialization data")
759
760         minetest_server_commands[0x31] = {
761                 "ACTIVE_OBJECT_REMOVE_ADD", 6,
762                 { f_removed_count, f_removed, f_removed_id,
763                   f_added_count, f_added, f_added_id,
764                   f_added_type, f_added_init_length, f_added_init_data },
765                 function(buffer, pinfo, tree, t)
766                         local t2, index, pos
767
768                         local removed_count_pos = 2
769                         local removed_count = buffer(removed_count_pos, 2):uint()
770                         t:add(f_removed_count, buffer(removed_count_pos, 2))
771
772                         local added_count_pos = removed_count_pos + 2 + 2 * removed_count
773                         if not minetest_check_length(buffer, added_count_pos + 2, t) then
774                                 return
775                         end
776
777                         -- Loop through removed active objects
778                         for index = 0, removed_count - 1 do
779                                 pos = removed_count_pos + 2 + 2 * index
780                                 t2 = t:add(f_removed, buffer(pos, 2))
781                                 t2:set_text("Removed object, ID = " ..  buffer(pos, 2):uint())
782                                 t2:add(f_removed_id, buffer(pos, 2))
783                         end
784
785                         local added_count = buffer(added_count_pos, 2):uint()
786                         t:add(f_added_count, buffer(added_count_pos, 2))
787
788                         -- Loop through added active objects
789                         pos = added_count_pos + 2
790                         for index = 0, added_count - 1 do
791                                 if not minetest_check_length(buffer, pos + 7, t) then
792                                         return
793                                 end
794
795                                 local init_length = buffer(pos + 3, 4):uint()
796                                 if not minetest_check_length(buffer, pos + 7 + init_length, t) then
797                                         return
798                                 end
799
800                                 t2 = t:add(f_added, buffer(pos, 7 + init_length))
801                                 t2:set_text("Added object, ID = " .. buffer(pos, 2):uint())
802                                 t2:add(f_added_id, buffer(pos, 2))
803                                 t2:add(f_added_type, buffer(pos + 2, 1))
804                                 t2:add(f_added_init_length, buffer(pos + 3, 4))
805                                 t2:add(f_added_init_data, buffer(pos + 7, init_length))
806
807                                 pos = pos + 7 + init_length
808                         end
809
810                         pinfo.cols.info:append(" * " .. (removed_count + added_count))
811                 end
812         }
813 end
814
815 -- TOCLIENT_ACTIVE_OBJECT_MESSAGES
816
817 do
818         local f_object_count = ProtoField.uint16(
819                 "minetest.server.active_object_messages_object_count",
820                 "Count of objects", base.DEC)
821         local f_object = ProtoField.bytes(
822                 "minetest.server.active_object_messages_object",
823                 "Object")
824         local f_object_id = ProtoField.uint16(
825                 "minetest.server.active_object_messages_id",
826                 "ID", base.DEC)
827         local f_message_length = ProtoField.uint16(
828                 "minetest.server.active_object_messages_message_length",
829                 "Message length", base.DEC)
830         local f_message = ProtoField.bytes(
831                 "minetest.server.active_object_messages_message",
832                 "Message")
833
834         minetest_server_commands[0x32] = {
835                 "ACTIVE_OBJECT_MESSAGES", 2,
836                 { f_object_count, f_object, f_object_id, f_message_length, f_message },
837                 function(buffer, pinfo, tree, t)
838                         local t2, count, pos, message_length
839
840                         count = 0
841                         pos = 2
842                         while pos < buffer:len() do
843                                 if not minetest_check_length(buffer, pos + 4, t) then
844                                         return
845                                 end
846                                 message_length = buffer(pos + 2, 2):uint()
847                                 if not minetest_check_length(buffer, pos + 4 + message_length, t) then
848                                         return
849                                 end
850                                 count = count + 1
851                                 pos = pos + 4 + message_length
852                         end
853
854                         pinfo.cols.info:append(" * " .. count)
855                         t:add(f_object_count, count):set_generated()
856
857                         pos = 2
858                         while pos < buffer:len() do
859                                 message_length = buffer(pos + 2, 2):uint()
860
861                                 t2 = t:add(f_object, buffer(pos, 4 + message_length))
862                                 t2:set_text("Object, ID = " ..  buffer(pos, 2):uint())
863                                 t2:add(f_object_id, buffer(pos, 2))
864                                 t2:add(f_message_length, buffer(pos + 2, 2))
865                                 t2:add(f_message, buffer(pos + 4, message_length))
866
867                                 pos = pos + 4 + message_length
868                         end
869                 end
870         }
871 end
872
873 -- TOCLIENT_HP
874
875 do
876         local f_hp = ProtoField.uint16("minetest.server.hp", "Health points", base.DEC)
877
878         minetest_server_commands[0x33] = {
879                 "HP", 4,
880                 { f_hp },
881                 function(buffer, pinfo, tree, t)
882                         t:add(f_hp, buffer(2,2))
883                 end
884         }
885 end
886
887 -- TOCLIENT_MOVE_PLAYER
888
889 do
890         local abbr = "minetest.server.move_player_"
891
892         local f_x = ProtoField.float(abbr.."x", "Position X")
893         local f_y = ProtoField.float(abbr.."y", "Position Y")
894         local f_z = ProtoField.float(abbr.."z", "Position Z")
895         local f_pitch = ProtoField.float(abbr.."_pitch", "Pitch")
896         local f_yaw = ProtoField.float(abbr.."yaw", "Yaw")
897
898         minetest_server_commands[0x34] = {
899                 "MOVE_PLAYER", 22,
900                 { f_x, f_y, f_z, f_pitch, f_yaw, f_garbage },
901                 function(buffer, pinfo, tree, t)
902                         t:add(f_x, buffer(2, 4))
903                         t:add(f_y, buffer(6, 4))
904                         t:add(f_z, buffer(10, 4))
905                         t:add(f_pitch, buffer(14, 4))
906                         t:add(f_yaw, buffer(18, 4))
907                 end
908         }
909 end
910
911 -- TOCLIENT_ACCESS_DENIED_LEGACY
912
913 do
914         local f_reason_length = ProtoField.uint16("minetest.server.access_denied_reason_length", "Reason length", base.DEC)
915         local f_reason = ProtoField.string("minetest.server.access_denied_reason", "Reason")
916
917         minetest_server_commands[0x35] = {
918                 "ACCESS_DENIED_LEGACY", 4,
919                 { f_reason_length, f_reason },
920                 function(buffer, pinfo, tree, t)
921                         t:add(f_reason_length, buffer(2,2))
922                         local reason_length = buffer(2,2):uint()
923                         if minetest_check_length(buffer, 4 + reason_length * 2, t) then
924                                 t:add(f_reason, minetest_convert_utf16(buffer(4, reason_length * 2), "Converted reason message"))
925                         end
926                 end
927         }
928 end
929
930 -- TOCLIENT_FOV
931
932 minetest_server_commands[0x36] = { "FOV", 2 }
933
934 -- TOCLIENT_DEATHSCREEN
935
936 do
937         local f_set_camera_point_target = ProtoField.bool(
938                 "minetest.server.deathscreen_set_camera_point_target",
939                 "Set camera point target")
940         local f_camera_point_target_x = ProtoField.int32(
941                 "minetest.server.deathscreen_camera_point_target_x",
942                 "Camera point target X", base.DEC)
943         local f_camera_point_target_y = ProtoField.int32(
944                 "minetest.server.deathscreen_camera_point_target_y",
945                 "Camera point target Y", base.DEC)
946         local f_camera_point_target_z = ProtoField.int32(
947                 "minetest.server.deathscreen_camera_point_target_z",
948                 "Camera point target Z", base.DEC)
949
950         minetest_server_commands[0x37] = {
951                 "DEATHSCREEN", 15,
952                 { f_set_camera_point_target, f_camera_point_target_x,
953                   f_camera_point_target_y, f_camera_point_target_z},
954                 function(buffer, pinfo, tree, t)
955                         t:add(f_set_camera_point_target, buffer(2,1))
956                         t:add(f_camera_point_target_x, buffer(3,4))
957                         t:add(f_camera_point_target_y, buffer(7,4))
958                         t:add(f_camera_point_target_z, buffer(11,4))
959                 end
960         }
961 end
962
963 -- TOCLIENT_MEDIA
964
965 minetest_server_commands[0x38] = {"MEDIA", 2}
966
967 -- TOCLIENT_TOOLDEF (obsolete)
968
969 minetest_server_commands[0x39] = {"TOOLDEF", 2}
970 minetest_server_obsolete[0x39] = true
971
972 -- TOCLIENT_NODEDEF
973
974 minetest_server_commands[0x3a] = {"NODEDEF", 2}
975
976 -- TOCLIENT_CRAFTITEMDEF (obsolete)
977
978 minetest_server_commands[0x3b] = {"CRAFTITEMDEF", 2}
979 minetest_server_obsolete[0x3b] = true
980
981 -- ...
982
983 minetest_server_commands[0x3c] = {"ANNOUNCE_MEDIA", 2}
984 minetest_server_commands[0x3d] = {"ITEMDEF", 2}
985 minetest_server_commands[0x3f] = {"PLAY_SOUND", 2}
986 minetest_server_commands[0x40] = {"STOP_SOUND", 2}
987 minetest_server_commands[0x41] = {"PRIVILEGES", 2}
988 minetest_server_commands[0x42] = {"INVENTORY_FORMSPEC", 2}
989 minetest_server_commands[0x43] = {"DETACHED_INVENTORY", 2}
990 minetest_server_commands[0x44] = {"SHOW_FORMSPEC", 2}
991 minetest_server_commands[0x45] = {"MOVEMENT", 2}
992 minetest_server_commands[0x46] = {"SPAWN_PARTICLE", 2}
993 minetest_server_commands[0x47] = {"ADD_PARTICLE_SPAWNER", 2}
994
995 -- TOCLIENT_DELETE_PARTICLESPAWNER_LEGACY (obsolete)
996
997 minetest_server_commands[0x48] = {"DELETE_PARTICLESPAWNER_LEGACY", 2}
998 minetest_server_obsolete[0x48] = true
999
1000 -- ...
1001
1002 minetest_server_commands[0x49] = {"HUDADD", 2}
1003 minetest_server_commands[0x4a] = {"HUDRM", 2}
1004 minetest_server_commands[0x4b] = {"HUDCHANGE", 2}
1005 minetest_server_commands[0x4c] = {"HUD_SET_FLAGS", 2}
1006 minetest_server_commands[0x4d] = {"HUD_SET_PARAM", 2}
1007 minetest_server_commands[0x4e] = {"BREATH", 2}
1008 minetest_server_commands[0x4f] = {"SET_SKY", 2}
1009 minetest_server_commands[0x50] = {"OVERRIDE_DAY_NIGHT_RATIO", 2}
1010 minetest_server_commands[0x51] = {"LOCAL_PLAYER_ANIMATIONS", 2}
1011 minetest_server_commands[0x52] = {"EYE_OFFSET", 2}
1012 minetest_server_commands[0x53] = {"DELETE_PARTICLESPAWNER", 2}
1013 minetest_server_commands[0x54] = {"CLOUD_PARAMS", 2}
1014 minetest_server_commands[0x55] = {"FADE_SOUND", 2}
1015
1016 -- TOCLIENT_UPDATE_PLAYER_LIST
1017
1018 do
1019         local abbr = "minetest.server.update_player_list_"
1020         local vs_type = {
1021                 [0] = "Init",
1022                 [1] = "Add",
1023                 [2] = "Remove",
1024         }
1025
1026         local f_type = ProtoField.uint8(abbr.."type", "Type", base.DEC, vs_type)
1027         local f_count = ProtoField.uint16(abbr.."count", "Number of players", base.DEC)
1028         local f_name = ProtoField.string(abbr.."name", "Name")
1029
1030         minetest_server_commands[0x56] = {
1031                 "UPDATE_PLAYER_LIST",
1032                 5,
1033                 { f_type, f_count, f_name },
1034                 function(buffer, pinfo, tree, t)
1035                         t:add(f_type, buffer(2,1))
1036                         t:add(f_count, buffer(3,2))
1037                         local count = buffer(3,2):uint()
1038                         local off = 5
1039                         for i = 1, count do
1040                                 if not minetest_check_length(buffer, off + 2, t) then
1041                                         return
1042                                 end
1043                                 off = minetest_decode_helper_ascii(buffer, t, "uint16", off, nil, f_name)
1044                                 if not off then
1045                                         return
1046                                 end
1047                         end
1048                 end
1049         }
1050 end
1051
1052 -- ...
1053
1054 minetest_server_commands[0x57] = {"MODCHANNEL_MSG", 2}
1055 minetest_server_commands[0x58] = {"MODCHANNEL_SIGNAL", 2}
1056 minetest_server_commands[0x59] = {"NODEMETA_CHANGED", 2}
1057 minetest_server_commands[0x5a] = {"SET_SUN", 2}
1058 minetest_server_commands[0x5b] = {"SET_MOON", 2}
1059 minetest_server_commands[0x5c] = {"SET_STARS", 2}
1060 minetest_server_commands[0x60] = {"SRP_BYTES_S_B", 2}
1061 minetest_server_commands[0x61] = {"FORMSPEC_PREPEND", 2}
1062
1063
1064 ------------------------------------
1065 -- Part 4                         --
1066 -- Wrapper protocol subdissectors --
1067 ------------------------------------
1068
1069 -- minetest.control dissector
1070
1071 do
1072         local p_control = Proto("minetest.control", "Minetest Control")
1073
1074         local vs_control_type = {
1075                 [0] = "Ack",
1076                 [1] = "Set Peer ID",
1077                 [2] = "Ping",
1078                 [3] = "Disco"
1079         }
1080
1081         local f_control_type = ProtoField.uint8("minetest.control.type", "Control Type", base.DEC, vs_control_type)
1082         local f_control_ack = ProtoField.uint16("minetest.control.ack", "ACK sequence number", base.DEC)
1083         local f_control_peerid = ProtoField.uint8("minetest.control.peerid", "New peer ID", base.DEC)
1084         p_control.fields = { f_control_type, f_control_ack, f_control_peerid }
1085
1086         local data_dissector = Dissector.get("data")
1087
1088         function p_control.dissector(buffer, pinfo, tree)
1089                 local t = tree:add(p_control, buffer(0,1))
1090                 t:add(f_control_type, buffer(0,1))
1091
1092                 pinfo.cols.info = "Control message"
1093
1094                 local pos = 1
1095                 if buffer(0,1):uint() == 0 then
1096                         pos = 3
1097                         t:set_len(3)
1098                         t:add(f_control_ack, buffer(1,2))
1099                         pinfo.cols.info = "Ack " .. buffer(1,2):uint()
1100                 elseif buffer(0,1):uint() == 1 then
1101                         pos = 3
1102                         t:set_len(3)
1103                         t:add(f_control_peerid, buffer(1,2))
1104                         pinfo.cols.info = "Set peer ID " .. buffer(1,2):uint()
1105                 elseif buffer(0,1):uint() == 2 then
1106                         pinfo.cols.info = "Ping"
1107                 elseif buffer(0,1):uint() == 3 then
1108                         pinfo.cols.info = "Disco"
1109                 end
1110
1111                 data_dissector:call(buffer(pos):tvb(), pinfo, tree)
1112         end
1113 end
1114
1115 -- minetest.client dissector
1116 -- minetest.server dissector
1117
1118 -- Defines the minetest.client or minetest.server Proto. These two protocols
1119 -- are created by the same function because they are so similar.
1120 -- Parameter: proto: the Proto object
1121 -- Parameter: this_peer: "Client" or "Server"
1122 -- Parameter: other_peer: "Server" or "Client"
1123 -- Parameter: commands: table of command information, built above
1124 -- Parameter: obsolete: table of obsolete commands, built above
1125 function minetest_define_client_or_server_proto(is_client)
1126         -- Differences between minetest.client and minetest.server
1127         local proto_name, this_peer, other_peer, empty_message_info
1128         local commands, obsolete
1129         if is_client then
1130                 proto_name = "minetest.client"
1131                 this_peer = "Client"
1132                 other_peer = "Server"
1133                 empty_message_info = "Empty message / Connect"
1134                 commands = minetest_client_commands  -- defined in Part 2
1135                 obsolete = minetest_client_obsolete  -- defined in Part 2
1136         else
1137                 proto_name = "minetest.server"
1138                 this_peer = "Server"
1139                 other_peer = "Client"
1140                 empty_message_info = "Empty message"
1141                 commands = minetest_server_commands  -- defined in Part 3
1142                 obsolete = minetest_server_obsolete  -- defined in Part 3
1143         end
1144
1145         -- Create the protocol object.
1146         local proto = Proto(proto_name, "Minetest " .. this_peer .. " to " .. other_peer)
1147
1148         -- Create a table vs_command that maps command codes to command names.
1149         local vs_command = {}
1150         local code, command_info
1151         for code, command_info in pairs(commands) do
1152                 local command_name = command_info[1]
1153                 vs_command[code] = "TO" .. other_peer:upper() .. "_" .. command_name
1154         end
1155
1156         -- Field definitions
1157         local f_command = ProtoField.uint16(proto_name .. ".command", "Command", base.HEX, vs_command)
1158         local f_empty = ProtoField.bool(proto_name .. ".empty", "Is empty", BASE_NONE)
1159         proto.fields = { f_command, f_empty }
1160
1161         -- Add command-specific fields to the protocol
1162         for code, command_info in pairs(commands) do
1163                 local command_fields = command_info[3]
1164                 if command_fields ~= nil then
1165                         for index, field in ipairs(command_fields) do
1166                                 assert(field ~= nil)
1167                                 table.insert(proto.fields, field)
1168                         end
1169                 end
1170         end
1171
1172         -- minetest.client or minetest.server dissector function
1173         function proto.dissector(buffer, pinfo, tree)
1174                 local t = tree:add(proto, buffer)
1175
1176                 pinfo.cols.info = this_peer
1177
1178                 if buffer:len() == 0 then
1179                         -- Empty message.
1180                         t:add(f_empty, 1):set_generated()
1181                         pinfo.cols.info:append(": " .. empty_message_info)
1182
1183                 elseif minetest_check_length(buffer, 2, t) then
1184                         -- Get the command code.
1185                         t:add(f_command, buffer(0,2))
1186                         local code = buffer(0,2):uint()
1187                         local command_info = commands[code]
1188                         if command_info == nil then
1189                                 -- Error: Unknown command.
1190                                 pinfo.cols.info:append(": Unknown command")
1191                                 t:add_expert_info(PI_UNDECODED, PI_WARN, "Unknown " .. this_peer .. " to " .. other_peer .. " command")
1192                         else
1193                                 -- Process a known command
1194                                 local command_name = command_info[1]
1195                                 local command_min_length = command_info[2]
1196                                 local command_fields = command_info[3]
1197                                 local command_dissector = command_info[4]
1198                                 if minetest_check_length(buffer, command_min_length, t) then
1199                                         pinfo.cols.info:append(": " .. command_name)
1200                                         if command_dissector ~= nil then
1201                                                 command_dissector(buffer, pinfo, tree, t)
1202                                         end
1203                                 end
1204                                 if obsolete[code] then
1205                                         t:add_expert_info(PI_REQUEST_CODE, PI_WARN, "Obsolete command.")
1206                                 end
1207                         end
1208                 end
1209         end
1210 end
1211
1212 minetest_define_client_or_server_proto(true)  -- minetest.client
1213 minetest_define_client_or_server_proto(false) -- minetest.server
1214
1215 -- minetest.split dissector
1216
1217 do
1218         local p_split = Proto("minetest.split", "Minetest Split Message")
1219
1220         local f_split_seq = ProtoField.uint16("minetest.split.seq", "Sequence number", base.DEC)
1221         local f_split_chunkcount = ProtoField.uint16("minetest.split.chunkcount", "Chunk count", base.DEC)
1222         local f_split_chunknum = ProtoField.uint16("minetest.split.chunknum", "Chunk number", base.DEC)
1223         local f_split_data = ProtoField.bytes("minetest.split.data", "Split message data")
1224         p_split.fields = { f_split_seq, f_split_chunkcount, f_split_chunknum, f_split_data }
1225
1226         function p_split.dissector(buffer, pinfo, tree)
1227                 local t = tree:add(p_split, buffer(0,6))
1228                 t:add(f_split_seq, buffer(0,2))
1229                 t:add(f_split_chunkcount, buffer(2,2))
1230                 t:add(f_split_chunknum, buffer(4,2))
1231                 t:add(f_split_data, buffer(6))
1232                 pinfo.cols.info:append(" " .. buffer(0,2):uint() .. " chunk " .. buffer(4,2):uint() .. "/" .. buffer(2,2):uint())
1233         end
1234 end
1235
1236
1237
1238
1239 -------------------------------------
1240 -- Part 5                          --
1241 -- Wrapper protocol main dissector --
1242 -------------------------------------
1243
1244 -- minetest dissector
1245
1246 do
1247         local p_minetest = Proto("minetest", "Minetest")
1248
1249         local minetest_id = 0x4f457403
1250         local vs_id = {
1251                 [minetest_id] = "Valid"
1252         }
1253
1254         local vs_peer = {
1255                 [0] = "Inexistent",
1256                 [1] = "Server"
1257         }
1258
1259         local vs_type = {
1260                 [0] = "Control",
1261                 [1] = "Original",
1262                 [2] = "Split",
1263                 [3] = "Reliable"
1264         }
1265
1266         local f_id = ProtoField.uint32("minetest.id", "ID", base.HEX, vs_id)
1267         local f_peer = ProtoField.uint16("minetest.peer", "Peer", base.DEC, vs_peer)
1268         local f_channel = ProtoField.uint8("minetest.channel", "Channel", base.DEC)
1269         local f_type = ProtoField.uint8("minetest.type", "Type", base.DEC, vs_type)
1270         local f_seq = ProtoField.uint16("minetest.seq", "Sequence number", base.DEC)
1271         local f_subtype = ProtoField.uint8("minetest.subtype", "Subtype", base.DEC, vs_type)
1272
1273         p_minetest.fields = { f_id, f_peer, f_channel, f_type, f_seq, f_subtype }
1274
1275         local data_dissector = Dissector.get("data")
1276         local control_dissector = Dissector.get("minetest.control")
1277         local client_dissector = Dissector.get("minetest.client")
1278         local server_dissector = Dissector.get("minetest.server")
1279         local split_dissector = Dissector.get("minetest.split")
1280
1281         function p_minetest.dissector(buffer, pinfo, tree)
1282
1283                 -- Add Minetest tree item and verify the ID
1284                 local t = tree:add(p_minetest, buffer(0,8))
1285                 t:add(f_id, buffer(0,4))
1286                 if buffer(0,4):uint() ~= minetest_id then
1287                         t:add_expert_info(PI_UNDECODED, PI_WARN, "Invalid ID, this is not a Minetest packet")
1288                         return
1289                 end
1290
1291                 -- ID is valid, so replace packet's shown protocol
1292                 pinfo.cols.protocol = "Minetest"
1293                 pinfo.cols.info = "Minetest"
1294
1295                 -- Set the other header fields
1296                 t:add(f_peer, buffer(4,2))
1297                 t:add(f_channel, buffer(6,1))
1298                 t:add(f_type, buffer(7,1))
1299                 t:set_text("Minetest, Peer: " .. buffer(4,2):uint() .. ", Channel: " .. buffer(6,1):uint())
1300
1301                 local reliability_info
1302                 if buffer(7,1):uint() == 3 then
1303                         -- Reliable message
1304                         reliability_info = "Seq=" .. buffer(8,2):uint()
1305                         t:set_len(11)
1306                         t:add(f_seq, buffer(8,2))
1307                         t:add(f_subtype, buffer(10,1))
1308                         pos = 10
1309                 else
1310                         -- Unreliable message
1311                         reliability_info = "Unrel"
1312                         pos = 7
1313                 end
1314
1315                 if buffer(pos,1):uint() == 0 then
1316                         -- Control message, possibly reliable
1317                         control_dissector:call(buffer(pos+1):tvb(), pinfo, tree)
1318                 elseif buffer(pos,1):uint() == 1 then
1319                         -- Original message, possibly reliable
1320                         if buffer(4,2):uint() == 1 then
1321                                 server_dissector:call(buffer(pos+1):tvb(), pinfo, tree)
1322                         else
1323                                 client_dissector:call(buffer(pos+1):tvb(), pinfo, tree)
1324                         end
1325                 elseif buffer(pos,1):uint() == 2 then
1326                         -- Split message, possibly reliable
1327                         if buffer(4,2):uint() == 1 then
1328                                 pinfo.cols.info = "Server: Split message"
1329                         else
1330                                 pinfo.cols.info = "Client: Split message"
1331                         end
1332                         split_dissector:call(buffer(pos+1):tvb(), pinfo, tree)
1333                 elseif buffer(pos,1):uint() == 3 then
1334                         -- Doubly reliable message??
1335                         t:add_expert_info(PI_MALFORMED, PI_ERROR, "Reliable message wrapped in reliable message")
1336                 else
1337                         data_dissector:call(buffer(pos+1):tvb(), pinfo, tree)
1338                 end
1339
1340                 pinfo.cols.info:append(" (" .. reliability_info .. ")")
1341
1342         end
1343
1344         -- FIXME Is there a way to let the dissector table check if the first payload bytes are 0x4f457403?
1345         DissectorTable.get("udp.port"):add(30000, p_minetest)
1346         DissectorTable.get("udp.port"):add(30001, p_minetest)
1347 end
1348
1349
1350
1351
1352 ------------------------------
1353 -- Part 6                   --
1354 -- Utility functions part 2 --
1355 ------------------------------
1356
1357 -- Checks if a (sub-)Tvb is long enough to be further dissected.
1358 -- If it is long enough, sets the dissector tree item length to min_len
1359 -- and returns true. If it is not long enough, adds expert info to the
1360 -- dissector tree and returns false.
1361 -- Parameter: tvb: the Tvb
1362 -- Parameter: min_len: required minimum length
1363 -- Parameter: t: dissector tree item
1364 -- Returns: true if tvb:len() >= min_len, false otherwise
1365 function minetest_check_length(tvb, min_len, t)
1366         if tvb:len() >= min_len then
1367                 t:set_len(min_len)
1368                 return true
1369
1370         -- TODO: check if other parts of
1371         -- the dissector could benefit from reported_length_remaining
1372         elseif tvb:reported_length_remaining() >= min_len then
1373                 t:add_expert_info(PI_UNDECODED, PI_INFO, "Only part of this packet was captured, unable to decode.")
1374                 return false
1375
1376         else
1377                 t:add_expert_info(PI_MALFORMED, PI_ERROR, "Message is too short")
1378                 return false
1379         end
1380 end
1381
1382 -- Decodes a variable-length string as ASCII text
1383 -- t_textlen, t_text should be the ProtoFields created by minetest_field_helper
1384 --   alternatively t_text can be a ProtoField.string and t_textlen can be nil
1385 -- lentype must be the type of the length field (as passed to minetest_field_helper)
1386 -- returns nil if length check failed
1387 function minetest_decode_helper_ascii(tvb, t, lentype, offset, f_textlen, f_text)
1388         local n = ({uint16 = 2, uint32 = 4})[lentype]
1389         assert(n)
1390
1391         if f_textlen then
1392                 t:add(f_textlen, tvb(offset, n))
1393         end
1394         local textlen = tvb(offset, n):uint()
1395         if minetest_check_length(tvb, offset + n + textlen, t) then
1396                 t:add(f_text, tvb(offset + n, textlen))
1397                 return offset + n + textlen
1398         end
1399 end
1400
1401 -- Decodes a variable-length string as UTF-16 text
1402 -- (see minetest_decode_helper_ascii)
1403 function minetest_decode_helper_utf16(tvb, t, lentype, offset, f_textlen, f_text)
1404         local n = ({uint16 = 2, uint32 = 4})[lentype]
1405         assert(n)
1406
1407         if f_textlen then
1408                 t:add(f_textlen, tvb(offset, n))
1409         end
1410         local textlen = tvb(offset, n):uint() * 2
1411         if minetest_check_length(tvb, offset + n + textlen, t) then
1412                 t:add(f_text, tvb(offset + n, textlen), tvb(offset + n, textlen):ustring())
1413                 return offset + n + textlen
1414         end
1415 end