-
Notifications
You must be signed in to change notification settings - Fork 6
/
thrift-generic.lua
363 lines (313 loc) · 11.9 KB
/
thrift-generic.lua
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
-------------------------------------------------------------------------------
-- wireshark-thrift-dissector
-- This code is licensed under MIT license (see LICENSE for details)
--
-------------------------------------------------------------------------------
--- configuration
local default_settings = {
port = 9090,
}
-------------------------------------------------------------------------------
--- protocols
local theader_protocol = Proto("theader", "Thrift Header Protocol")
local tbinary_protocol = Proto("tbinary", "Thrift Binary Protocol")
-------------------------------------------------------------------------------
--- lookup tables
local msgtype_valstr = {}
msgtype_valstr[1] = "CALL"
msgtype_valstr[2] = "REPLY"
msgtype_valstr[3] = "EXCEPTION"
msgtype_valstr[4] = "ONEWAY"
local fieldtype_valstr = {}
fieldtype_valstr[0] = "STOP"
fieldtype_valstr[1] = "VOID"
fieldtype_valstr[2] = "BOOL"
fieldtype_valstr[3] = "BYTE"
fieldtype_valstr[4] = "DOUBLE"
fieldtype_valstr[6] = "I16"
fieldtype_valstr[8] = "I32"
fieldtype_valstr[10] = "I64"
fieldtype_valstr[11] = "STRING"
fieldtype_valstr[12] = "STRUCT"
fieldtype_valstr[13] = "MAP"
fieldtype_valstr[14] = "SET"
fieldtype_valstr[15] = "LIST"
fieldtype_valstr[16] = "UTF8"
fieldtype_valstr[17] = "UTF16"
-------------------------------------------------------------------------------
--- protocol constants
THRIFT_VERSION_MASK = -65536
THRIFT_VERSION_1 = -2147418112
THRIFT_HEADER_MAGIC = 0x0FFF
THRIFT_HEADER_TYPE_KV = 0x01
THRIFT_TYPE_MASK = 0x000000ff
-------------------------------------------------------------------------------
--- fields
local tbinary_fields = {
msg_type = ProtoField.uint8("tbinary.msgtype", "Message Type", base.DEC, msgtype_valstr),
msg_type = ProtoField.uint8("tbinary.msgtype", "Message Type", base.DEC, msgtype_valstr),
msg_name = ProtoField.string("tbinary.msgname", "Message Name"),
msg_seq = ProtoField.uint32("tbinary.msgseq", "Message Sequence", base.DEC),
}
tbinary_protocol.fields = tbinary_fields
-------------------------------------------------------------------------------
--- ThriftBuffer is a stateful buffer helper
ThriftBuffer = {}
function ThriftBuffer:new(buf)
o = {
pos = 0,
buf = buf
}
setmetatable(o, self)
self.__index = self
return o
end
function ThriftBuffer:seek(pos)
self.pos = pos
end
function ThriftBuffer:skip(num)
self.pos = self.pos + num
end
function ThriftBuffer:__call(len)
rv = self.buf(self.pos, len)
self.pos = self.pos + len
return rv
end
function ThriftBuffer:bool()
local byte = self(1):int()
return byte ~= 0
end
function ThriftBuffer:byte()
return self(1):int()
end
function ThriftBuffer:double()
return self(8):float()
end
function ThriftBuffer:i16()
return self(2):int()
end
function ThriftBuffer:i32()
return self(4):int()
end
function ThriftBuffer:i64()
return self(8):int64()
end
function ThriftBuffer:varint()
local res = 0
local pos = 0
while true do
local b = self.buf(self.pos + pos, 1):int()
res = bit32.bor(res, bit32.lshift(bit32.band(b, 0x7f), pos * 7))
pos = pos + 1
if bit32.rshift(b, 7) == 0 then
self.pos = self.pos + pos
return res
end
end
end
function ThriftBuffer:varstring()
local size = self:varint()
local rv = self.buf(self.pos, size):string()
self.pos = self.pos + size
return rv
end
function ThriftBuffer:string()
local size = self(4):int()
local val = self(size):string()
return val
end
local fieldtype_readers = {
BOOL = ThriftBuffer.bool,
BYTE = ThriftBuffer.byte,
DOUBLE = ThriftBuffer.double,
I16 = ThriftBuffer.i16,
I32 = ThriftBuffer.i32,
I64 = ThriftBuffer.i64,
STRING = ThriftBuffer.string,
}
-------------------------------------------------------------------------------
--- decodes a series of thrift fields until the STOP sentinel is reached
function decode_tfields(buf, tree)
if buf:len() == 0 then
return 0
end
local tbuf = ThriftBuffer:new(buf)
local type = fieldtype_valstr[tbuf(1):int()]
while type ~= nil and type ~= "STOP" do
id = tbuf(2):int()
local pos = tbuf.pos
if type == "VOID" then
tree:add(id, "Type: VOID")
elseif type == "BOOL" then
local val = tbuf:bool()
tree:add(buf(pos, 1), id, "Type: BOOL", string.format("%s", val))
elseif type == "BYTE" then
local val = tbuf:byte()
tree:add(buf(pos, 1), id, "Type: BYTE", val)
elseif type == "DOUBLE" then
local val = tbuf:double()
tree:add(buf(pos, 8), id, "Type: DOUBLE", val)
elseif type == "I16" then
local val = tbuf:i16()
tree:add(buf(pos, 2), id, "Type: I16", val)
elseif type == "I32" then
local val = tbuf:i32()
tree:add(buf(pos, 4), id, "Type: I32", val)
elseif type == "I64" then
local val = tbuf:i64()
tree:add(buf(pos, 8), id, "Type: I64", string.format("%s", val))
elseif type == "STRING" then
local size = tbuf(4):int()
local val = tbuf(size):string()
tree:add(buf(pos, 4+size), id, "Type: STRING", val)
elseif type == "STRUCT" then
local child_tree = tree:add(id, "Type: STRUCT")
local len = decode_tfields(buf(pos, buf:len() - pos), child_tree)
tbuf:skip(len)
elseif type == "MAP" then
local ktype = tbuf(1):int()
local vtype = tbuf(1):int()
local size = tbuf(4):int()
local ktype_str = fieldtype_valstr[ktype]
local vtype_str = fieldtype_valstr[vtype]
if ktype_str ~= nil and vtype_str ~= nil then
local child_tree = tree:add(id, "Type: MAP" .. string.format("<%s, %s>", ktype_str, vtype_str))
local kreader = fieldtype_readers[ktype_str]
for i = 1, size do
fieldpos = tbuf.pos
key = kreader(tbuf)
-- TODO(eac): make handling non-scalars more elegant
if vtype_str == "STRUCT" then
local elem_tree = child_tree:add(i, key)
child_buf = tbuf.buf(tbuf.pos)
local len = decode_tfields(child_buf, elem_tree)
tbuf:skip(len)
else
local vreader = fieldtype_readers[vtype_str]
val = vreader(tbuf)
child_tree:add(buf(fieldpos, tbuf.pos-fieldpos), key, val)
end
end
end
elseif type == "SET" or type == "LIST" then
local etype = tbuf(1):int()
local size = tbuf(4):int()
local etype_str = fieldtype_valstr[etype]
local child_tree = tree:add(id, "Type: " .. string.format("%s<%s>", type, etype_str))
if etype_str ~= nil then
local ereader = fieldtype_readers[etype_str]
for i = 1, size do
local fieldpos = tbuf.pos
-- TODO(eac): make handling non-scalars more elegant
if etype_str == "STRUCT" then
local elem_tree = child_tree:add(string.format("%s", i))
child_buf = tbuf.buf(tbuf.pos)
local len = decode_tfields(child_buf, elem_tree)
tbuf:skip(len)
else
elem = ereader(tbuf)
child_tree:add(buf(fieldpos, tbuf.pos-fieldpos), i, elem)
end
end
end
else
print(type .. " not implemented")
end
type = fieldtype_valstr[tbuf(1):int()]
end
if type == nil then
return 0
end
return tbuf.pos
end
-------------------------------------------------------------------------------
--- root tbinary dissector. will dissect a unframed tbinary message
function tbinary_protocol.dissector(buffer, pinfo, tree)
local tbuf = ThriftBuffer:new(buffer)
local sz = tbuf(4):int()
if sz < 0 then
local version = bit32.band(sz, THRIFT_VERSION_MASK)
if not bit32.btest(version, THRIFT_VERSION_1) then
return 0
end
local type = bit32.band(sz, THRIFT_TYPE_MASK)
tree:add(tbinary_fields.msg_type, type)
local name_pos = tbuf.pos
local name = tbuf:string()
if name:len() > 0 then
tree:add(tbinary_fields.msg_name, buffer(name_pos, tbuf.pos-name_pos), name)
end
local seq_pos = tbuf.pos
local seqid = tbuf(4):int()
tree:add(tbinary_fields.msg_seq, buffer(seq_pos, 4), seqid)
else
-- TODO(eac): implement me
print("non-versioned tbinary protocol unimplemented")
end
decode_tfields(buffer(tbuf.pos, buffer:len()-tbuf.pos), tree)
end
-------------------------------------------------------------------------------
--- root theader dissector. will dissect a framed theader message, chaining
--- the payload into the tbinary dissector
function theader_protocol.dissector(buffer, pinfo, tree)
if buffer:len() == 0 then return end
pinfo.cols.protocol = theader_protocol.name
local subtree = tree:add(theader_protocol, buffer(), "Thrift Protocol Data")
local frame_size = buffer(0, 4):int()
if (buffer:len() - 4) < frame_size then
pinfo.desegment_len = frame_size - (buffer:len() - 4)
pinfo.desegment_offset = 0
return
end
local framebuf = buffer(4, frame_size):tvb()
local tb = ThriftBuffer:new(framebuf)
local version = framebuf(0, 4):int()
if bit32.rshift(version, 16) == THRIFT_HEADER_MAGIC then
local flags, seq_id, header_length, end_of_headers, protocol_id, transform_count = nil
tb:seek(2)
flags = tb(2):uint()
seq_id = tb(4):int()
header_length = tb(2):uint() * 4
end_of_headers = tb.pos + header_length
protocol_id = tb:varint()
transform_count = tb:varint()
-- TODO(eac): try to implement the gzip transform?
local transforms = {}
for i = 1, transform_count do
local transform_id = tb:varint()
table.insert(transforms, transform_id)
end
local headers_tree = subtree:add(framebuf(tb.pos, header_length), "Headers")
while tb.pos < end_of_headers do
local header_type = tb:varint()
if header_type == THRIFT_HEADER_TYPE_KV then
local count = tb:varint()
for i = 1, count do
local header_start = tb.pos
local key = tb:varstring()
local val_len = tb:varint()
if val_len > 0 then
local value_tree = headers_tree:add(framebuf(header_start, (tb.pos-header_start) + val_len), key)
-- attempt to read value as thrift
local val_range = framebuf(tb.pos, val_len)
local val_buf = val_range:tvb()
if decode_tfields(val_buf, value_tree) ~= 0 then
else
local value = val_range:string()
value_tree:add(framebuf(tb.pos, val_len), value)
end
tb:seek(tb.pos + val_len)
else
print("empty")
end
end
end
end
remaining_buf = framebuf(end_of_headers, framebuf:len()-end_of_headers)
local payload_tree = subtree:add(tbinary_protocol, remaining_buf, "Payload")
Dissector.get("tbinary"):call(remaining_buf:tvb(), pinfo, payload_tree)
end
end
-------------------------------------------------------------------------------
--- dissector registration
DissectorTable.get("tcp.port"):add(default_settings.port, theader_protocol)