mavgen_wlua.py 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500
  1. #!/usr/bin/env python
  2. '''
  3. parse a MAVLink protocol XML file and generate a Wireshark LUA dissector
  4. Copyright Holger Steinhaus 2012
  5. Released under GNU GPL version 3 or later
  6. Instructions for use:
  7. 1. python -m pymavlink.tools.mavgen --lang=WLua mymavlink.xml -o ~/.wireshark/plugins/mymavlink.lua
  8. 2. convert binary stream int .pcap file format (see ../examples/mav2pcap.py)
  9. 3. open the pcap file in Wireshark
  10. '''
  11. from __future__ import print_function
  12. from builtins import range
  13. import os
  14. import re
  15. from . import mavparse, mavtemplate
  16. t = mavtemplate.MAVTemplate()
  17. def lua_type(mavlink_type):
  18. # qnd typename conversion
  19. if (mavlink_type=='char'):
  20. lua_t = 'uint8'
  21. else:
  22. lua_t = mavlink_type.replace('_t', '')
  23. return lua_t
  24. def type_size(mavlink_type):
  25. # infer size of mavlink types
  26. re_int = re.compile('^(u?)int(8|16|32|64)_t$')
  27. int_parts = re_int.findall(mavlink_type)
  28. if len(int_parts):
  29. return (int(int_parts[0][1]) // 8)
  30. elif mavlink_type == 'float':
  31. return 4
  32. elif mavlink_type == 'double':
  33. return 8
  34. elif mavlink_type == 'char':
  35. return 1
  36. else:
  37. raise Exception('unsupported MAVLink type - please fix me')
  38. def mavfmt(field):
  39. '''work out the struct format for a type'''
  40. map = {
  41. 'float' : 'f',
  42. 'double' : 'd',
  43. 'char' : 'c',
  44. 'int8_t' : 'b',
  45. 'uint8_t' : 'B',
  46. 'uint8_t_mavlink_version' : 'B',
  47. 'int16_t' : 'h',
  48. 'uint16_t' : 'H',
  49. 'int32_t' : 'i',
  50. 'uint32_t' : 'I',
  51. 'int64_t' : 'q',
  52. 'uint64_t' : 'Q',
  53. }
  54. if field.array_length:
  55. if field.type in ['char', 'int8_t', 'uint8_t']:
  56. return str(field.array_length)+'s'
  57. return str(field.array_length)+map[field.type]
  58. return map[field.type]
  59. def generate_preamble(outf):
  60. print("Generating preamble")
  61. t.write(outf,
  62. """
  63. -- Wireshark dissector for the MAVLink protocol (please see http://qgroundcontrol.org/mavlink/start for details)
  64. unknownFrameBeginOffset = 0
  65. local bit = require "bit32"
  66. mavlink_proto = Proto("mavlink_proto", "MAVLink protocol")
  67. f = mavlink_proto.fields
  68. -- from http://lua-users.org/wiki/TimeZone
  69. local function get_timezone()
  70. local now = os.time()
  71. return os.difftime(now, os.time(os.date("!*t", now)))
  72. end
  73. local signature_time_ref = get_timezone() + os.time{year=2015, month=1, day=1, hour=0}
  74. payload_fns = {}
  75. """ )
  76. def generate_body_fields(outf):
  77. t.write(outf,
  78. """
  79. f.magic = ProtoField.uint8("mavlink_proto.magic", "Magic value / version", base.HEX)
  80. f.length = ProtoField.uint8("mavlink_proto.length", "Payload length")
  81. f.incompatibility_flag = ProtoField.uint8("mavlink_proto.incompatibility_flag", "Incompatibility flag")
  82. f.compatibility_flag = ProtoField.uint8("mavlink_proto.compatibility_flag", "Compatibility flag")
  83. f.sequence = ProtoField.uint8("mavlink_proto.sequence", "Packet sequence")
  84. f.sysid = ProtoField.uint8("mavlink_proto.sysid", "System id", base.HEX)
  85. f.compid = ProtoField.uint8("mavlink_proto.compid", "Component id", base.HEX)
  86. f.msgid = ProtoField.uint24("mavlink_proto.msgid", "Message id", base.HEX)
  87. f.payload = ProtoField.uint8("mavlink_proto.payload", "Payload", base.DEC, messageName)
  88. f.crc = ProtoField.uint16("mavlink_proto.crc", "Message CRC", base.HEX)
  89. f.signature_link = ProtoField.uint8("mavlink_proto.signature_link", "Link id", base.DEC)
  90. f.signature_time = ProtoField.absolute_time("mavlink_proto.signature_time", "Time")
  91. f.signature_signature = ProtoField.bytes("mavlink_proto.signature_signature", "Signature")
  92. f.rawheader = ProtoField.bytes("mavlink_proto.rawheader", "Unparsable header fragment")
  93. f.rawpayload = ProtoField.bytes("mavlink_proto.rawpayload", "Unparsable payload")
  94. """)
  95. def generate_msg_table(outf, msgs):
  96. t.write(outf, """
  97. messageName = {
  98. """)
  99. for msg in msgs:
  100. assert isinstance(msg, mavparse.MAVType)
  101. t.write(outf, """
  102. [${msgid}] = '${msgname}',
  103. """, {'msgid':msg.id, 'msgname':msg.name})
  104. t.write(outf, """
  105. }
  106. """)
  107. def generate_msg_fields(outf, msg):
  108. assert isinstance(msg, mavparse.MAVType)
  109. for f in msg.fields:
  110. assert isinstance(f, mavparse.MAVField)
  111. mtype = f.type
  112. ltype = lua_type(mtype)
  113. count = f.array_length if f.array_length>0 else 1
  114. # string is no array, but string of chars
  115. if mtype == 'char' and count > 1:
  116. count = 1
  117. ltype = 'string'
  118. for i in range(0,count):
  119. if count>1:
  120. array_text = '[' + str(i) + ']'
  121. index_text = '_' + str(i)
  122. else:
  123. array_text = ''
  124. index_text = ''
  125. t.write(outf,
  126. """
  127. f.${fmsg}_${fname}${findex} = ProtoField.${ftype}("mavlink_proto.${fmsg}_${fname}${findex}", "${fname}${farray} (${ftype})")
  128. """, {'fmsg':msg.name, 'ftype':ltype, 'fname':f.name, 'findex':index_text, 'farray':array_text})
  129. t.write(outf, '\n\n')
  130. def generate_field_dissector(outf, msg, field):
  131. assert isinstance(field, mavparse.MAVField)
  132. mtype = field.type
  133. size = type_size(mtype)
  134. ltype = lua_type(mtype)
  135. count = field.array_length if field.array_length>0 else 1
  136. # string is no array but string of chars
  137. if mtype == 'char':
  138. size = count
  139. count = 1
  140. # handle arrays, but not strings
  141. for i in range(0,count):
  142. if count>1:
  143. index_text = '_' + str(i)
  144. else:
  145. index_text = ''
  146. t.write(outf,
  147. """
  148. if (truncated) then
  149. tree:add_le(f.${fmsg}_${fname}${findex}, 0)
  150. elseif (offset + ${fbytes} <= limit) then
  151. tree:add_le(f.${fmsg}_${fname}${findex}, buffer(offset, ${fbytes}))
  152. offset = offset + ${fbytes}
  153. elseif (offset < limit) then
  154. tree:add_le(f.${fmsg}_${fname}${findex}, buffer(offset, limit - offset))
  155. offset = limit
  156. truncated = true
  157. else
  158. tree:add_le(f.${fmsg}_${fname}${findex}, 0)
  159. truncated = true
  160. end
  161. """, {'fname':field.name, 'ftype':mtype, 'fmsg': msg.name, 'fbytes':size, 'findex':index_text})
  162. def generate_payload_dissector(outf, msg):
  163. assert isinstance(msg, mavparse.MAVType)
  164. t.write(outf,
  165. """
  166. -- dissect payload of message type ${msgname}
  167. function payload_fns.payload_${msgid}(buffer, tree, msgid, offset, limit)
  168. local truncated = false
  169. """, {'msgid':msg.id, 'msgname':msg.name})
  170. for f in msg.ordered_fields:
  171. generate_field_dissector(outf, msg, f)
  172. t.write(outf,
  173. """
  174. return offset
  175. end
  176. """)
  177. def generate_packet_dis(outf):
  178. t.write(outf,
  179. """
  180. -- dissector function
  181. function mavlink_proto.dissector(buffer,pinfo,tree)
  182. local offset = 0
  183. local msgCount = 0
  184. -- loop through the buffer to extract all the messages in the buffer
  185. while (offset < buffer:len())
  186. do
  187. msgCount = msgCount + 1
  188. local subtree = tree:add (mavlink_proto, buffer(), "MAVLink Protocol ("..buffer:len()..")")
  189. -- decode protocol version first
  190. local version = buffer(offset,1):uint()
  191. local protocolString = ""
  192. while (true)
  193. do
  194. if (version == 0xfe) then
  195. protocolString = "MAVLink 1.0"
  196. break
  197. elseif (version == 0xfd) then
  198. protocolString = "MAVLink 2.0"
  199. break
  200. elseif (version == 0x55) then
  201. protocolString = "MAVLink 0.9"
  202. break
  203. else
  204. protocolString = "unknown"
  205. -- some unknown data found, record the begin offset
  206. if (unknownFrameBeginOffset == 0) then
  207. unknownFrameBeginOffset = offset
  208. end
  209. offset = offset + 1
  210. if (offset < buffer:len()) then
  211. version = buffer(offset,1):uint()
  212. else
  213. -- no magic value found in the whole buffer. print the raw data and exit
  214. if (unknownFrameBeginOffset ~= 0) then
  215. if (msgCount == 1) then
  216. pinfo.cols.info:set("Unknown message")
  217. else
  218. pinfo.cols.info:append(" Unknown message")
  219. end
  220. size = offset - unknownFrameBeginOffset
  221. subtree:add(f.rawpayload, buffer(unknownFrameBeginOffset,size))
  222. unknownFrameBeginOffset = 0
  223. end
  224. return
  225. end
  226. end
  227. end
  228. if (unknownFrameBeginOffset ~= 0) then
  229. pinfo.cols.info:append("Unknown message")
  230. size = offset - unknownFrameBeginOffset
  231. subtree:add(f.rawpayload, buffer(unknownFrameBeginOffset,size))
  232. unknownFrameBeginOffset = 0
  233. -- jump to next loop
  234. break
  235. end
  236. -- some Wireshark decoration
  237. pinfo.cols.protocol = protocolString
  238. -- HEADER ----------------------------------------
  239. local msgid
  240. local length
  241. local incompatibility_flag
  242. if (version == 0xfe) then
  243. if (buffer:len() - 2 - offset > 6) then
  244. -- normal header
  245. local header = subtree:add("Header")
  246. header:add(f.magic, buffer(offset,1), version)
  247. offset = offset + 1
  248. length = buffer(offset,1)
  249. header:add(f.length, length)
  250. offset = offset + 1
  251. local sequence = buffer(offset,1)
  252. header:add(f.sequence, sequence)
  253. offset = offset + 1
  254. local sysid = buffer(offset,1)
  255. header:add(f.sysid, sysid)
  256. offset = offset + 1
  257. local compid = buffer(offset,1)
  258. header:add(f.compid, compid)
  259. offset = offset + 1
  260. pinfo.cols.src = "System: "..tostring(sysid:uint())..', Component: '..tostring(compid:uint())
  261. msgid = buffer(offset,1):uint()
  262. header:add(f.msgid, buffer(offset,1), msgid)
  263. offset = offset + 1
  264. else
  265. -- handle truncated header
  266. local hsize = buffer:len() - 2 - offset
  267. subtree:add(f.rawheader, buffer(offset, hsize))
  268. offset = offset + hsize
  269. end
  270. elseif (version == 0xfd) then
  271. if (buffer:len() - 2 - offset > 10) then
  272. -- normal header
  273. local header = subtree:add("Header")
  274. header:add(f.magic, buffer(offset,1), version)
  275. offset = offset + 1
  276. length = buffer(offset,1)
  277. header:add(f.length, length)
  278. offset = offset + 1
  279. incompatibility_flag = buffer(offset,1):uint()
  280. header:add(f.incompatibility_flag, buffer(offset,1), incompatibility_flag)
  281. offset = offset + 1
  282. local compatibility_flag = buffer(offset,1)
  283. header:add(f.compatibility_flag, compatibility_flag)
  284. offset = offset + 1
  285. local sequence = buffer(offset,1)
  286. header:add(f.sequence, sequence)
  287. offset = offset + 1
  288. local sysid = buffer(offset,1)
  289. header:add(f.sysid, sysid)
  290. offset = offset + 1
  291. local compid = buffer(offset,1)
  292. header:add(f.compid, compid)
  293. offset = offset + 1
  294. pinfo.cols.src = "System: "..tostring(sysid:uint())..', Component: '..tostring(compid:uint())
  295. msgid = buffer(offset,3):le_uint()
  296. header:add(f.msgid, buffer(offset,3), msgid)
  297. offset = offset + 3
  298. else
  299. -- handle truncated header
  300. local hsize = buffer:len() - 2 - offset
  301. subtree:add(f.rawheader, buffer(offset, hsize))
  302. offset = offset + hsize
  303. end
  304. end
  305. -- BODY ----------------------------------------
  306. -- dynamically call the type-specific payload dissector
  307. local msgnr = msgid
  308. local dissect_payload_fn = "payload_"..tostring(msgnr)
  309. local fn = payload_fns[dissect_payload_fn]
  310. local limit = buffer:len() - 2
  311. if (length) then
  312. length = length:uint()
  313. else
  314. length = 0
  315. end
  316. if (offset + length < limit) then
  317. limit = offset + length
  318. end
  319. if (fn == nil) then
  320. pinfo.cols.info:append ("Unknown message type ")
  321. subtree:add_expert_info(PI_MALFORMED, PI_ERROR, "Unknown message type")
  322. size = buffer:len() - 2 - offset
  323. subtree:add(f.rawpayload, buffer(offset,size))
  324. offset = offset + size
  325. else
  326. local payload = subtree:add(f.payload, msgid)
  327. pinfo.cols.dst:set(messageName[msgid])
  328. if (msgCount == 1) then
  329. -- first message should over write the TCP/UDP info
  330. pinfo.cols.info = messageName[msgid]
  331. else
  332. pinfo.cols.info:append(" "..messageName[msgid])
  333. end
  334. fn(buffer, payload, msgid, offset, limit)
  335. offset = limit
  336. end
  337. -- CRC ----------------------------------------
  338. local crc = buffer(offset,2)
  339. subtree:add_le(f.crc, crc)
  340. offset = offset + 2
  341. -- SIGNATURE ----------------------------------
  342. if (version == 0xfd and incompatibility_flag == 0x01) then
  343. local signature = subtree:add("Signature")
  344. local link = buffer(offset,1)
  345. signature:add(f.signature_link, link)
  346. offset = offset + 1
  347. local signature_time = buffer(offset,6):le_uint64()
  348. local time_secs = signature_time / 100000
  349. local time_nsecs = (signature_time - (time_secs * 100000)) * 10000
  350. signature:add(f.signature_time, buffer(offset,6), NSTime.new(signature_time_ref + time_secs:tonumber(), time_nsecs:tonumber()))
  351. offset = offset + 6
  352. local signature_signature = buffer(offset,6)
  353. signature:add(f.signature_signature, signature_signature)
  354. offset = offset + 6
  355. end
  356. end
  357. end
  358. """)
  359. def generate_epilog(outf):
  360. print("Generating epilog")
  361. t.write(outf,
  362. """
  363. -- bind protocol dissector to USER0 linktype
  364. wtap_encap = DissectorTable.get("wtap_encap")
  365. wtap_encap:add(wtap.USER0, mavlink_proto)
  366. -- bind protocol dissector to port 14550
  367. local udp_dissector_table = DissectorTable.get("udp.port")
  368. udp_dissector_table:add(14550, mavlink_proto)
  369. """)
  370. def generate(basename, xml):
  371. '''generate complete python implemenation'''
  372. if basename.endswith('.lua'):
  373. filename = basename
  374. else:
  375. filename = basename + '.lua'
  376. msgs = []
  377. enums = []
  378. filelist = []
  379. for x in xml:
  380. msgs.extend(x.message)
  381. enums.extend(x.enum)
  382. filelist.append(os.path.basename(x.filename))
  383. for m in msgs:
  384. if xml[0].little_endian:
  385. m.fmtstr = '<'
  386. else:
  387. m.fmtstr = '>'
  388. for f in m.ordered_fields:
  389. m.fmtstr += mavfmt(f)
  390. m.order_map = [ 0 ] * len(m.fieldnames)
  391. for i in range(0, len(m.fieldnames)):
  392. m.order_map[i] = m.ordered_fieldnames.index(m.fieldnames[i])
  393. print("Generating %s" % filename)
  394. outf = open(filename, "w")
  395. generate_preamble(outf)
  396. generate_msg_table(outf, msgs)
  397. generate_body_fields(outf)
  398. for m in msgs:
  399. generate_msg_fields(outf, m)
  400. for m in msgs:
  401. generate_payload_dissector(outf, m)
  402. generate_packet_dis(outf)
  403. # generate_enums(outf, enums)
  404. # generate_message_ids(outf, msgs)
  405. # generate_classes(outf, msgs)
  406. # generate_mavlink_class(outf, msgs, xml[0])
  407. # generate_methods(outf, msgs)
  408. generate_epilog(outf)
  409. outf.close()
  410. print("Generated %s OK" % filename)