mav2pcap.py 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232
  1. #!/usr/bin/env python
  2. # Copyright 2012, Holger Steinhaus
  3. # Released under the GNU GPL version 3 or later
  4. # This program packetizes a binary MAVLink stream. The resulting packets are stored into a PCAP file, which is
  5. # compatible to tools like Wireshark.
  6. # The program tries to synchronize to the packet structure in a robust way, using the SOF magic, the potential
  7. # packet length information and the next SOF magic. Additionally the CRC is verified.
  8. # Hint: A MAVLink protocol dissector (parser) for Wireshark may be generated by mavgen.py.
  9. # dependency: Python construct library (python-construct on Debian/Ubuntu), "easy_install construct" elsewhere
  10. from __future__ import print_function
  11. from future import standard_library
  12. standard_library.install_aliases()
  13. from builtins import object
  14. from builtins import open
  15. import sys
  16. import os
  17. from pymavlink import mavutil
  18. from construct import ULInt16, Struct, Byte, Bytes, Const
  19. from construct.core import FieldError
  20. from argparse import ArgumentParser, FileType
  21. MAVLINK_MAGIC = 0xfe
  22. write_junk = True
  23. # copied from ardupilotmega.h (git changeset 694536afb882068f50da1fc296944087aa207f9f, Dec 02 2012
  24. MAVLINK_MESSAGE_CRCS = (50, 124, 137, 0, 237, 217, 104, 119, 0, 0, 0, 89, 0, 0, 0, 0, 0, 0, 0, 0, 214, 159, 220, 168, 24, 23, 170, 144, 67, 115, 39, 246, 185, 104, 237, 244, 242, 212, 9, 254, 230, 28, 28, 132, 221, 232, 11, 153, 41, 39, 214, 223, 141, 33, 15, 3, 100, 24, 239, 238, 30, 240, 183, 130, 130, 0, 148, 21, 0, 243, 124, 0, 0, 0, 20, 0, 152, 143, 0, 0, 127, 106, 0, 0, 0, 0, 0, 0, 0, 231, 183, 63, 54, 0, 0, 0, 0, 0, 0, 0, 175, 102, 158, 208, 56, 93, 0, 0, 0, 0, 235, 93, 124, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 42, 241, 15, 134, 219, 208, 188, 84, 22, 19, 21, 134, 0, 78, 68, 189, 127, 111, 21, 21, 144, 1, 234, 73, 181, 22, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 204, 49, 170, 44, 83, 46, 0)
  25. import struct
  26. # Helper class for writing pcap files
  27. class pcap(object):
  28. """
  29. Used under the terms of GNU GPL v3.
  30. Original author: Neale Pickett
  31. see http://dirtbags.net/py-pcap.html
  32. """
  33. _MAGIC = 0xA1B2C3D4
  34. def __init__(self, stream, mode='rb', snaplen=65535, linktype=1):
  35. try:
  36. self.stream = open(stream, mode)
  37. except TypeError:
  38. self.stream = stream
  39. try:
  40. # Try reading
  41. hdr = self.stream.read(24)
  42. except IOError:
  43. hdr = None
  44. if hdr:
  45. # We're in read mode
  46. self._endian = None
  47. for endian in '<>':
  48. (self.magic,) = struct.unpack(endian + 'I', hdr[:4])
  49. if self.magic == pcap._MAGIC:
  50. self._endian = endian
  51. break
  52. if not self._endian:
  53. raise IOError('Not a pcap file')
  54. (self.magic, version_major, version_minor,
  55. self.thiszone, self.sigfigs,
  56. self.snaplen, self.linktype) = struct.unpack(self._endian + 'IHHIIII', hdr)
  57. if (version_major, version_minor) != (2, 4):
  58. raise IOError('Cannot handle file version %d.%d' % (version_major,
  59. version_minor))
  60. else:
  61. # We're in write mode
  62. self._endian = '='
  63. self.magic = pcap._MAGIC
  64. version_major = 2
  65. version_minor = 4
  66. self.thiszone = 0
  67. self.sigfigs = 0
  68. self.snaplen = snaplen
  69. self.linktype = linktype
  70. hdr = struct.pack(self._endian + 'IHHIIII',
  71. self.magic, version_major, version_minor,
  72. self.thiszone, self.sigfigs,
  73. self.snaplen, self.linktype)
  74. self.stream.write(hdr)
  75. self.version = (version_major, version_minor)
  76. def read(self):
  77. hdr = self.stream.read(16)
  78. if not hdr:
  79. return
  80. (tv_sec, tv_usec, caplen, length) = struct.unpack(self._endian + 'IIII', hdr)
  81. datum = self.stream.read(caplen)
  82. return ((tv_sec, tv_usec, length), datum)
  83. def write(self, packet):
  84. (header, datum) = packet
  85. (tv_sec, tv_usec, length) = header
  86. hdr = struct.pack(self._endian + 'IIII', tv_sec, tv_usec, length, len(datum))
  87. self.stream.write(hdr)
  88. self.stream.write(datum)
  89. def __iter__(self):
  90. while True:
  91. r = self.read()
  92. if not r:
  93. break
  94. yield r
  95. def find_next_frame(data):
  96. """
  97. find a potential start of frame
  98. """
  99. return data.find('\xfe')
  100. def parse_header(data):
  101. """
  102. split up header information (using construct)
  103. """
  104. mavlink_header = Struct('header',
  105. Const(Byte('magic'), MAVLINK_MAGIC),
  106. Byte('plength'),
  107. Byte('sequence'),
  108. Byte('sysid'),
  109. Byte('compid'),
  110. Byte('msgid'),
  111. )
  112. return mavlink_header.parse(data[0:6])
  113. def write_packet(number, data, flags, pkt_length):
  114. pcap_header = (number, flags, pkt_length)
  115. pcap_file.write((pcap_header, data))
  116. def convert_file(mavlink_file, pcap_file):
  117. # the whole file is read in a bunch - room for improvement...
  118. data = mavlink_file.read()
  119. i=0
  120. done = False
  121. skipped_char = None
  122. junk = ''
  123. cnt_ok = 0
  124. cnt_junk = 0
  125. cnt_crc = 0
  126. while not done:
  127. i+=1
  128. # look for potential start of frame
  129. next_sof = find_next_frame(data)
  130. if next_sof > 0:
  131. print("skipped " + str(next_sof) + " bytes")
  132. if write_junk:
  133. if skipped_char is not None:
  134. junk = skipped_char + data[:next_sof]
  135. skipped_char = None
  136. write_packet(i, junk, 0x03, len(junk))
  137. data = data[next_sof:]
  138. data[:6]
  139. cnt_junk += 1
  140. # assume, our 0xFE was the start of a packet
  141. header = parse_header(data)
  142. payload_len = header['plength']
  143. pkt_length = 6 + payload_len + 2
  144. try:
  145. pkt_crc = ULInt16('crc').parse(data[pkt_length-2:pkt_length])
  146. except FieldError:
  147. # ups, no more data
  148. done = True
  149. continue
  150. # peek for the next SOF
  151. try:
  152. cc = mavutil.x25crc(data[1:6+payload_len])
  153. cc.accumulate(chr(MAVLINK_MESSAGE_CRCS[header['msgid']]))
  154. x25_crc = cc.crc
  155. if x25_crc != pkt_crc:
  156. crc_flag = 0x1
  157. else:
  158. crc_flag = 0
  159. next_magic = data[pkt_length]
  160. if chr(MAVLINK_MAGIC) != next_magic:
  161. # damn, retry
  162. print("packet %d has invalid length, crc error: %d" % (i, crc_flag))
  163. # skip one char to look for a new SOF next round, stow away skipped char
  164. skipped_char = data[0]
  165. data = data[1:]
  166. continue
  167. # we can consider it a packet now
  168. pkt = data[:pkt_length]
  169. write_packet(i, pkt, crc_flag, len(pkt))
  170. print("packet %d ok, crc error: %d" % (i, crc_flag))
  171. data = data[pkt_length:]
  172. if crc_flag:
  173. cnt_crc += 1
  174. else:
  175. cnt_ok += 1
  176. except IndexError:
  177. # ups, no more packets
  178. done = True
  179. print("converted %d valid packets, %d crc errors, %d junk fragments (total %f%% of junk)" % (cnt_ok, cnt_crc, cnt_junk, 100.*float(cnt_junk+cnt_crc)/(cnt_junk+cnt_ok+cnt_crc)))
  180. ###############################################################################
  181. parser = ArgumentParser()
  182. parser.add_argument("input_file", type=FileType('rb'))
  183. parser.add_argument("output_file", type=FileType('wb'))
  184. args = parser.parse_args()
  185. mavlink_file = args.input_file
  186. args.output_file.close()
  187. pcap_file = pcap(args.output_file.name, args.output_file.mode, linktype=147) # special trick: linktype USER0
  188. convert_file(mavlink_file, pcap_file)
  189. mavlink_file.close()
  190. #pcap_file.close()