1require ("bit")
2
3p_astron = Proto ("astron", "Astron (MD)")
4
5local f_length = ProtoField.uint16("astron.length", "Message length", base.DEC)
6local f_recipient = ProtoField.uint64("astron.recipient", "Recipient channel", base.HEX)
7local f_sender = ProtoField.uint64("astron.sender", "Sender channel", base.HEX)
8local f_msgtype = ProtoField.uint16("astron.msgtype", "Message type", base.DEC)
9
10local f_doid = ProtoField.uint32("astron.doid", "DistributedObject ID", base.DEC)
11local f_field = ProtoField.uint16("astron.field", "Field ID", base.DEC)
12local f_field_count = ProtoField.uint16("astron.field_count", "Field count", base.DEC)
13
14local f_object_count = ProtoField.uint32("astron.object_count", "Object count", base.DEC)
15
16local f_parent = ProtoField.uint32("astron.parent", "Parent ID", base.DEC)
17local f_zone = ProtoField.uint32("astron.zone", "Zone ID", base.DEC)
18local f_zone_count = ProtoField.uint16("astron.zone_count", "Zone count", base.DEC)
19
20local f_context = ProtoField.uint32("astron.context", "Request context", base.DEC)
21
22p_astron.fields = {
23	f_length, f_recipient, f_sender, f_msgtype,
24
25	f_doid, f_field, f_field_count,
26
27	f_object_count,
28
29	f_parent, f_zone, f_zone_count,
30
31	f_context,
32}
33
34-- Helpers that, maybe someday, will actually get info from a .dc file?
35function dclass_by_field (field)
36	return "DistributedObject"
37end
38
39function dclass_by_index (index)
40	return "DistributedObject"
41end
42
43function field_by_index (index)
44	return "FIELD_" .. index
45end
46
47function decode_field (index, buf)
48	return "<" .. buf .. ">"
49end
50
51local message_table = {
52	[1011] = {
53		name="CLIENTAGENT_UNDECLARE_OBJECT",
54		dissector=function(buf, root)
55			local doid = buf(0,4):le_uint()
56			root:add_le(f_doid, buf(0,4))
57
58			return tostring(doid)
59		end
60	},
61	[2020] = {
62		name="STATESERVER_OBJECT_SET_FIELD",
63		dissector=function(buf, root)
64			local doid = buf(0,4):le_uint()
65			root:add_le(f_doid, buf(0,4))
66
67			local field = buf(4,2):le_uint()
68			root:add_le(f_field, buf(0,2), field,
69			            "Field: " .. field_by_index(field).."("..field..")")
70
71			local decoded = ""
72			if buf:len() > 6 then
73				local data = buf(6)
74				decoded = decode_field(field, data)
75				root:add(data, "Field data: "..decoded)
76			end
77
78			return string.format("%s(%d).%s(%s)",
79			                     dclass_by_field(field), doid,
80			                     field_by_index(field), decoded)
81		end
82	},
83	[2100] = {
84		name="STATESERVER_OBJECT_GET_ZONE_OBJECTS",
85		dissector=function(buf, root)
86			local context = buf(0,4):le_uint()
87			root:add_le(f_context, buf(0,4))
88
89			local parent = buf(4,4):le_uint()
90			root:add_le(f_parent, buf(4,4))
91
92			local zone = buf(8,4):le_uint()
93			root:add_le(f_zone, buf(8,4))
94
95			return string.format("%d: %d, %d", context, parent, zone)
96		end
97	},
98	[2102] = {
99		name="STATESERVER_OBJECT_GET_ZONES_OBJECTS",
100		dissector=function(buf, root)
101			local context = buf(0,4):le_uint()
102			root:add_le(f_context, buf(0,4))
103
104			local parent = buf(4,4):le_uint()
105			root:add_le(f_parent, buf(4,4))
106
107			local zone_count = buf(8,2):le_uint()
108			local count_item = root:add_le(f_zone_count, buf(8,2))
109
110			local zones = buf(10)
111			local zone_strings = {}
112			for i = 0, zone_count-1 do
113				table.insert(zone_strings, tostring(zones(i*4,4):le_uint()))
114				count_item:add_le(f_zone, zones(i*4,4))
115			end
116
117			return string.format("%d: %d(%s)", context, parent,
118			                     table.concat(zone_strings, "&"))
119		end
120	},
121	[2110] = {
122		name="STATESERVER_OBJECT_GET_ZONE_COUNT",
123		dissector=function(buf, root)
124			local context = buf(0,4):le_uint()
125			root:add_le(f_context, buf(0,4))
126
127			local parent = buf(4,4):le_uint()
128			root:add_le(f_parent, buf(4,4))
129
130			local zone = buf(8,4):le_uint()
131			root:add_le(f_zone, buf(8,4))
132
133			return string.format("%d: %d, %d", context, parent, zone)
134		end
135	},
136	[2111] = {
137		name="STATESERVER_OBJECT_GET_ZONE_COUNT_RESP",
138		dissector=function(buf, root)
139			local context = buf(0,4):le_uint()
140			root:add_le(f_context, buf(0,4))
141
142			local object_count = buf(4,4):le_uint()
143			root:add_le(f_object_count, buf(4,4))
144
145			return string.format("%d: %d", context, object_count)
146		end
147	},
148	[2112] = {
149		name="STATESERVER_OBJECT_GET_ZONES_COUNT",
150		dissector=function(buf, root)
151			local context = buf(0,4):le_uint()
152			root:add_le(f_context, buf(0,4))
153
154			local parent = buf(4,4):le_uint()
155			root:add_le(f_parent, buf(4,4))
156
157			local zone_count = buf(8,2):le_uint()
158			local count_item = root:add_le(f_zone_count, buf(8,2))
159
160			local zones = buf(10)
161			local zone_strings = {}
162			for i = 0, zone_count-1 do
163				table.insert(zone_strings, tostring(zones(i*4,4):le_uint()))
164				count_item:add_le(f_zone, zones(i*4,4))
165			end
166
167			return string.format("%d: %d(%s)", context, parent,
168			                     table.concat(zone_strings, "&"))
169		end
170	},
171	[2113] = {
172		name="STATESERVER_OBJECT_GET_ZONES_COUNT_RESP",
173		dissector=function(buf, root)
174			local context = buf(0,4):le_uint()
175			root:add_le(f_context, buf(0,4))
176
177			local object_count = buf(4,4):le_uint()
178			root:add_le(f_object_count, buf(4,4))
179
180			return string.format("%d: %d", context, object_count)
181		end
182	},
183	[3012] = {
184		name="DBSERVER_OBJECT_GET_FIELDS",
185		dissector=function(buf, root)
186			local context = buf(0,4):le_uint()
187			root:add_le(f_context, buf(0,4))
188
189			local doid = buf(4,4):le_uint()
190			root:add_le(f_doid, buf(4,4))
191
192			local field_count = buf(8,2):le_uint()
193			local count_item = root:add_le(f_field_count, buf(8,2))
194
195			local fields = buf(10)
196			local field_names = {}
197			for i = 0, field_count-1 do
198				local field_name = field_by_index(fields(i*2,2):le_uint())
199				table.insert(field_names, field_name)
200
201				local field_item = count_item:add_le(f_field, fields(i*2,2))
202				field_item:set_text(field_name)
203			end
204
205			return string.format("%d[%s]", doid,
206			                     table.concat(field_names, "&"))
207		end
208	},
209}
210
211function pretty_msgtype (msgtype)
212	local msg_entry = message_table[msgtype] or {}
213
214	local msgname = msg_entry.name or "UNKNOWN"
215	return string.format("%s(%d)", msgname, msgtype)
216end
217
218function pretty_channel (channel)
219	local hex = channel:tohex()
220
221	local high = tostring(channel:rshift(32):band(0xFFFFFFFF))
222	local low = tostring(channel:band(0xFFFFFFFF))
223
224	return string.format("0x%s(%s, %s)", hex, high, low)
225end
226
227function dissect_one (buf, root, packet_descriptions)
228	local length = buf(0,2):le_uint()
229	local recip_count = buf(2, 1):uint()
230
231	-- Perform several sanity checks to see if we can even begin decode:
232	if length < 11 then return 0 end
233	if recip_count*8 > buf:len() then return 0 end
234	if recip_count == 0 then return 0 end
235	if length > 16384 then return 0 end -- Remove this if you have huge messages
236	if recip_count > 16 then return 0 end -- Remove this if you have many recipients
237
238	-- Make sure we have enough bytes, ask for more if not:
239	if buf:len() < length+2 then return buf:len() - length-2 end
240	buf = buf(0, length+2)
241
242	local subtree = root:add(buf, "Message")
243
244	local header_offset = buf:offset()
245	local header = subtree:add(buf, "Header")
246	header:add_le(f_length, buf(0, 2))
247	buf = buf(2)
248
249	local recipients = header:add(buf, "Recipient(s): ")
250	local recipient_channel
251	for i=0,recip_count-1 do
252		local channel_tvb = buf(1+i*8, 8)
253		recipients:add_le(f_recipient, channel_tvb)
254		if i ~= 0 then recipients:append_text(", ") end
255		recipient_channel = channel_tvb:le_uint64()
256		recipients:append_text(pretty_channel(recipient_channel))
257	end
258	recipients:set_len(1 + recip_count*8)
259	buf = buf(1 + recip_count*8)
260
261	if(recip_count ~= 1 or tostring(recipient_channel) ~= "1") then
262		local sender_channel = buf(0, 8):le_uint64()
263		header:add_le(f_sender, buf(0, 8), sender_channel,
264		              "Sender channel: " .. pretty_channel(sender_channel))
265		buf = buf(8)
266	end
267
268	local msgtype = buf(0, 2):le_uint()
269	header:add_le(f_msgtype, buf(0, 2), msgtype, "Message type: " .. pretty_msgtype(msgtype))
270	if buf:len() > 2 then
271		buf = buf(2)
272	end
273
274	header:set_len(buf:offset() - header_offset)
275
276	-- Invoke the sub-dissector, if available.
277	local full_description = pretty_msgtype(msgtype)
278	local dissector = (message_table[msgtype] or {}).dissector
279	if dissector ~= nil then
280		local description = dissector(buf, subtree)
281		if description ~= nil then
282			full_description = full_description .. ": " .. description
283		end
284	else
285		subtree:add(buf, "Payload")
286	end
287
288	subtree:set_text(full_description)
289	table.insert(packet_descriptions, full_description)
290
291	return length + 2
292end
293
294function p_astron.dissector (buf, pinfo, root)
295	if buf:len() < 2 then return end
296
297	local root = root:add(p_astron, buf)
298
299	local descriptions = {}
300	local message_count = 0
301	local offset = 0
302	while offset < buf:len() do
303		local len = dissect_one(buf(offset), root, descriptions)
304
305		if len < 0 then
306			pinfo.desegment_offset = offset
307			pinfo.desegment_len = -len
308			return
309		elseif len == 0 then
310			return 0
311		else
312			offset = offset + len
313			message_count = message_count + 1
314		end
315	end
316
317	if message_count > 0 then
318		pinfo.cols.protocol = p_astron.name
319		pinfo.cols.info = table.concat(descriptions, "; ")
320	end
321
322	return offset
323end
324
325function p_astron.init()
326	local tcp_dissector_table = DissectorTable.get("tcp.port")
327
328	tcp_dissector_table:add(7199, p_astron)
329end
330