xref: /openbmc/openbmc-tools/dbus-pcap/dbus-pcap (revision 96ebc4e78061da857cdb7278446b993fc483623d)
1cf3c1e67SAndrew Jeffery#!/usr/bin/python3
2cf3c1e67SAndrew Jeffery
3cf3c1e67SAndrew Jeffery# SPDX-License-Identifier: Apache-2.0
4cf3c1e67SAndrew Jeffery# Copyright 2019 IBM Corp.
5cf3c1e67SAndrew Jeffery
6a3db66b3SPatrick Williamsimport json
7a3db66b3SPatrick Williamsimport struct
8a3db66b3SPatrick Williamsimport sys
9cf3c1e67SAndrew Jefferyfrom argparse import ArgumentParser
10cf3c1e67SAndrew Jefferyfrom collections import namedtuple
11cf3c1e67SAndrew Jefferyfrom enum import Enum
12a3db66b3SPatrick Williamsfrom itertools import islice
13a3db66b3SPatrick Williams
14cf3c1e67SAndrew Jefferyfrom scapy.all import rdpcap
15cf3c1e67SAndrew Jeffery
16cf3c1e67SAndrew JefferyRawMessage = namedtuple("RawMessage", "endian, header, data")
17a3db66b3SPatrick WilliamsFixedHeader = namedtuple(
18a3db66b3SPatrick Williams    "FixedHeader", "endian, type, flags, version, length, cookie"
19a3db66b3SPatrick Williams)
20cf3c1e67SAndrew JefferyCookedHeader = namedtuple("CookedHeader", "fixed, fields")
21cf3c1e67SAndrew JefferyCookedMessage = namedtuple("CookedMessage", "header, body")
22cf3c1e67SAndrew JefferyTypeProperty = namedtuple("TypeProperty", "field, type, nature")
23cf3c1e67SAndrew JefferyTypeContainer = namedtuple("TypeContainer", "type, members")
24cf3c1e67SAndrew JefferyField = namedtuple("Field", "type, data")
25cf3c1e67SAndrew Jeffery
26a3db66b3SPatrick Williams
27cf3c1e67SAndrew Jefferyclass MessageEndian(Enum):
28a3db66b3SPatrick Williams    LITTLE = ord("l")
29a3db66b3SPatrick Williams    BIG = ord("B")
30a3db66b3SPatrick Williams
31cf3c1e67SAndrew Jeffery
32cf3c1e67SAndrew JefferyStructEndianLookup = {
33cf3c1e67SAndrew Jeffery    MessageEndian.LITTLE.value: "<",
34a3db66b3SPatrick Williams    MessageEndian.BIG.value: ">",
35cf3c1e67SAndrew Jeffery}
36cf3c1e67SAndrew Jeffery
37a3db66b3SPatrick Williams
38cf3c1e67SAndrew Jefferyclass MessageType(Enum):
39cf3c1e67SAndrew Jeffery    INVALID = 0
40cf3c1e67SAndrew Jeffery    METHOD_CALL = 1
41cf3c1e67SAndrew Jeffery    METHOD_RETURN = 2
42cf3c1e67SAndrew Jeffery    ERROR = 3
43cf3c1e67SAndrew Jeffery    SIGNAL = 4
44cf3c1e67SAndrew Jeffery
45a3db66b3SPatrick Williams
46cf3c1e67SAndrew Jefferyclass MessageFlags(Enum):
47cf3c1e67SAndrew Jeffery    NO_REPLY_EXPECTED = 0x01
48cf3c1e67SAndrew Jeffery    NO_AUTO_START = 0x02
49cf3c1e67SAndrew Jeffery    ALLOW_INTERACTIVE_AUTHORIZATION = 0x04
50cf3c1e67SAndrew Jeffery
51a3db66b3SPatrick Williams
52cf3c1e67SAndrew Jefferyclass MessageFieldType(Enum):
53cf3c1e67SAndrew Jeffery    INVALID = 0
54cf3c1e67SAndrew Jeffery    PATH = 1
55cf3c1e67SAndrew Jeffery    INTERFACE = 2
56cf3c1e67SAndrew Jeffery    MEMBER = 3
57cf3c1e67SAndrew Jeffery    ERROR_NAME = 4
58cf3c1e67SAndrew Jeffery    REPLY_SERIAL = 5
59cf3c1e67SAndrew Jeffery    DESTINATION = 6
60cf3c1e67SAndrew Jeffery    SENDER = 7
61cf3c1e67SAndrew Jeffery    SIGNATURE = 8
62cf3c1e67SAndrew Jeffery    UNIX_FDS = 9
63cf3c1e67SAndrew Jeffery
64a3db66b3SPatrick Williams
65cf3c1e67SAndrew Jefferyclass DBusType(Enum):
66cf3c1e67SAndrew Jeffery    INVALID = 0
67a3db66b3SPatrick Williams    BYTE = ord("y")
68a3db66b3SPatrick Williams    BOOLEAN = ord("b")
69a3db66b3SPatrick Williams    INT16 = ord("n")
70a3db66b3SPatrick Williams    UINT16 = ord("q")
71a3db66b3SPatrick Williams    INT32 = ord("i")
72a3db66b3SPatrick Williams    UINT32 = ord("u")
73a3db66b3SPatrick Williams    INT64 = ord("x")
74a3db66b3SPatrick Williams    UINT64 = ord("t")
75a3db66b3SPatrick Williams    DOUBLE = ord("d")
76a3db66b3SPatrick Williams    STRING = ord("s")
77a3db66b3SPatrick Williams    OBJECT_PATH = ord("o")
78a3db66b3SPatrick Williams    SIGNATURE = ord("g")
79a3db66b3SPatrick Williams    ARRAY = ord("a")
80a3db66b3SPatrick Williams    STRUCT = ord("(")
81a3db66b3SPatrick Williams    VARIANT = ord("v")
82a3db66b3SPatrick Williams    DICT_ENTRY = ord("{")
83a3db66b3SPatrick Williams    UNIX_FD = ord("h")
84a3db66b3SPatrick Williams
85cf3c1e67SAndrew Jeffery
86cf3c1e67SAndrew JefferyDBusContainerTerminatorLookup = {
87a3db66b3SPatrick Williams    chr(DBusType.STRUCT.value): ")",
88a3db66b3SPatrick Williams    chr(DBusType.DICT_ENTRY.value): "}",
89cf3c1e67SAndrew Jeffery}
90cf3c1e67SAndrew Jeffery
91a3db66b3SPatrick Williams
92cf3c1e67SAndrew Jefferyclass DBusTypeCategory(Enum):
93cf3c1e67SAndrew Jeffery    FIXED = {
94cf3c1e67SAndrew Jeffery        DBusType.BYTE.value,
95cf3c1e67SAndrew Jeffery        DBusType.BOOLEAN.value,
96cf3c1e67SAndrew Jeffery        DBusType.INT16.value,
97cf3c1e67SAndrew Jeffery        DBusType.UINT16.value,
98cf3c1e67SAndrew Jeffery        DBusType.INT32.value,
99cf3c1e67SAndrew Jeffery        DBusType.UINT32.value,
100cf3c1e67SAndrew Jeffery        DBusType.INT64.value,
101cf3c1e67SAndrew Jeffery        DBusType.UINT64.value,
102cf3c1e67SAndrew Jeffery        DBusType.DOUBLE.value,
103a3db66b3SPatrick Williams        DBusType.UNIX_FD.value,
104cf3c1e67SAndrew Jeffery    }
105cf3c1e67SAndrew Jeffery    STRING = {
106cf3c1e67SAndrew Jeffery        DBusType.STRING.value,
107cf3c1e67SAndrew Jeffery        DBusType.OBJECT_PATH.value,
108cf3c1e67SAndrew Jeffery        DBusType.SIGNATURE.value,
109cf3c1e67SAndrew Jeffery    }
110cf3c1e67SAndrew Jeffery    CONTAINER = {
111cf3c1e67SAndrew Jeffery        DBusType.ARRAY.value,
112cf3c1e67SAndrew Jeffery        DBusType.STRUCT.value,
113cf3c1e67SAndrew Jeffery        DBusType.VARIANT.value,
114cf3c1e67SAndrew Jeffery        DBusType.DICT_ENTRY.value,
115cf3c1e67SAndrew Jeffery    }
116cf3c1e67SAndrew Jeffery    RESERVED = {
117cf3c1e67SAndrew Jeffery        DBusType.INVALID.value,
118cf3c1e67SAndrew Jeffery    }
119cf3c1e67SAndrew Jeffery
120a3db66b3SPatrick Williams
121cf3c1e67SAndrew JefferyTypePropertyLookup = {
122a3db66b3SPatrick Williams    DBusType.BYTE.value: TypeProperty(DBusType.BYTE, "B", 1),
123cf3c1e67SAndrew Jeffery    # DBus booleans are 32 bit, with only the LSB used. Extract as 'I'.
124a3db66b3SPatrick Williams    DBusType.BOOLEAN.value: TypeProperty(DBusType.BOOLEAN, "I", 4),
125a3db66b3SPatrick Williams    DBusType.INT16.value: TypeProperty(DBusType.INT16, "h", 2),
126a3db66b3SPatrick Williams    DBusType.UINT16.value: TypeProperty(DBusType.UINT16, "H", 2),
127a3db66b3SPatrick Williams    DBusType.INT32.value: TypeProperty(DBusType.INT32, "i", 4),
128a3db66b3SPatrick Williams    DBusType.UINT32.value: TypeProperty(DBusType.UINT32, "I", 4),
129a3db66b3SPatrick Williams    DBusType.INT64.value: TypeProperty(DBusType.INT64, "q", 8),
130a3db66b3SPatrick Williams    DBusType.UINT64.value: TypeProperty(DBusType.UINT64, "Q", 8),
131a3db66b3SPatrick Williams    DBusType.DOUBLE.value: TypeProperty(DBusType.DOUBLE, "d", 8),
132a3db66b3SPatrick Williams    DBusType.STRING.value: TypeProperty(DBusType.STRING, "s", DBusType.UINT32),
133a3db66b3SPatrick Williams    DBusType.OBJECT_PATH.value: TypeProperty(
134a3db66b3SPatrick Williams        DBusType.OBJECT_PATH, "s", DBusType.UINT32
135a3db66b3SPatrick Williams    ),
136a3db66b3SPatrick Williams    DBusType.SIGNATURE.value: TypeProperty(
137a3db66b3SPatrick Williams        DBusType.SIGNATURE, "s", DBusType.BYTE
138a3db66b3SPatrick Williams    ),
139cf3c1e67SAndrew Jeffery    DBusType.ARRAY.value: TypeProperty(DBusType.ARRAY, None, DBusType.UINT32),
140cf3c1e67SAndrew Jeffery    DBusType.STRUCT.value: TypeProperty(DBusType.STRUCT, None, 8),
141cf3c1e67SAndrew Jeffery    DBusType.VARIANT.value: TypeProperty(DBusType.VARIANT, None, 1),
142cf3c1e67SAndrew Jeffery    DBusType.DICT_ENTRY.value: TypeProperty(DBusType.DICT_ENTRY, None, 8),
143b25389e5SSui Chen    DBusType.UNIX_FD.value: TypeProperty(DBusType.UINT32, None, 8),
144cf3c1e67SAndrew Jeffery}
145cf3c1e67SAndrew Jeffery
146a3db66b3SPatrick Williams
147cf3c1e67SAndrew Jefferydef parse_signature(sigstream):
148cf3c1e67SAndrew Jeffery    sig = ord(next(sigstream))
149cf3c1e67SAndrew Jeffery    assert sig not in DBusTypeCategory.RESERVED.value
150cf3c1e67SAndrew Jeffery    if sig in DBusTypeCategory.FIXED.value:
151cf3c1e67SAndrew Jeffery        ty = TypePropertyLookup[sig].field, None
152cf3c1e67SAndrew Jeffery    elif sig in DBusTypeCategory.STRING.value:
153cf3c1e67SAndrew Jeffery        ty = TypePropertyLookup[sig].field, None
154cf3c1e67SAndrew Jeffery    elif sig in DBusTypeCategory.CONTAINER.value:
155cf3c1e67SAndrew Jeffery        if sig == DBusType.ARRAY.value:
156cf3c1e67SAndrew Jeffery            ty = DBusType.ARRAY, parse_signature(sigstream)
157cf3c1e67SAndrew Jeffery        elif sig == DBusType.STRUCT.value or sig == DBusType.DICT_ENTRY.value:
158cf3c1e67SAndrew Jeffery            collected = list()
159cf3c1e67SAndrew Jeffery            ty = parse_signature(sigstream)
160cf3c1e67SAndrew Jeffery            while ty is not StopIteration:
161cf3c1e67SAndrew Jeffery                collected.append(ty)
162cf3c1e67SAndrew Jeffery                ty = parse_signature(sigstream)
163cf3c1e67SAndrew Jeffery            ty = DBusType.STRUCT, collected
164cf3c1e67SAndrew Jeffery        elif sig == DBusType.VARIANT.value:
165cf3c1e67SAndrew Jeffery            ty = TypePropertyLookup[sig].field, None
166cf3c1e67SAndrew Jeffery        else:
167cf3c1e67SAndrew Jeffery            assert False
168cf3c1e67SAndrew Jeffery    else:
169cf3c1e67SAndrew Jeffery        assert chr(sig) in DBusContainerTerminatorLookup.values()
170cf3c1e67SAndrew Jeffery        return StopIteration
171cf3c1e67SAndrew Jeffery
172cf3c1e67SAndrew Jeffery    return TypeContainer._make(ty)
173cf3c1e67SAndrew Jeffery
174a3db66b3SPatrick Williams
175cf3c1e67SAndrew Jefferyclass AlignedStream(object):
176cf3c1e67SAndrew Jeffery    def __init__(self, buf, offset=0):
177cf3c1e67SAndrew Jeffery        self.stash = (buf, offset)
178cf3c1e67SAndrew Jeffery        self.stream = iter(buf)
179cf3c1e67SAndrew Jeffery        self.offset = offset
180cf3c1e67SAndrew Jeffery
181cf3c1e67SAndrew Jeffery    def align(self, tc):
182cf3c1e67SAndrew Jeffery        assert tc.type.value in TypePropertyLookup
183cf3c1e67SAndrew Jeffery        prop = TypePropertyLookup[tc.type.value]
184cf3c1e67SAndrew Jeffery        if prop.field.value in DBusTypeCategory.STRING.value:
185cf3c1e67SAndrew Jeffery            prop = TypePropertyLookup[prop.nature.value]
186b25389e5SSui Chen        if prop.nature == DBusType.UINT32:
187b25389e5SSui Chen            prop = TypePropertyLookup[prop.nature.value]
188a3db66b3SPatrick Williams        advance = (
189a3db66b3SPatrick Williams            prop.nature - (self.offset & (prop.nature - 1))
190a3db66b3SPatrick Williams        ) % prop.nature
191cf3c1e67SAndrew Jeffery        _ = bytes(islice(self.stream, advance))
19208ce0a5bSAndrew Jeffery        self.offset += len(_)
193cf3c1e67SAndrew Jeffery
194cf3c1e67SAndrew Jeffery    def take(self, size):
195cf3c1e67SAndrew Jeffery        val = islice(self.stream, size)
196cf3c1e67SAndrew Jeffery        self.offset += size
197cf3c1e67SAndrew Jeffery        return val
198cf3c1e67SAndrew Jeffery
199cf3c1e67SAndrew Jeffery    def autotake(self, tc):
200cf3c1e67SAndrew Jeffery        assert tc.type.value in DBusTypeCategory.FIXED.value
201cf3c1e67SAndrew Jeffery        assert tc.type.value in TypePropertyLookup
202cf3c1e67SAndrew Jeffery        self.align(tc)
203cf3c1e67SAndrew Jeffery        prop = TypePropertyLookup[tc.type.value]
204cf3c1e67SAndrew Jeffery        return self.take(prop.nature)
205cf3c1e67SAndrew Jeffery
206cf3c1e67SAndrew Jeffery    def drain(self):
207cf3c1e67SAndrew Jeffery        remaining = bytes(self.stream)
208cf3c1e67SAndrew Jeffery        offset = self.offset
209cf3c1e67SAndrew Jeffery        self.offset += len(remaining)
210a03cdf2dSAndrew Jeffery        if self.offset - self.stash[1] != len(self.stash[0]):
211a3db66b3SPatrick Williams            print(
212a3db66b3SPatrick Williams                "(self.offset - self.stash[1]): %d, len(self.stash[0]): %d"
213a3db66b3SPatrick Williams                % (self.offset - self.stash[1], len(self.stash[0])),
214a3db66b3SPatrick Williams                file=sys.stderr,
215a3db66b3SPatrick Williams            )
216a03cdf2dSAndrew Jeffery            raise MalformedPacketError
217cf3c1e67SAndrew Jeffery        return remaining, offset
218cf3c1e67SAndrew Jeffery
219cf3c1e67SAndrew Jeffery    def dump(self):
220a3db66b3SPatrick Williams        print(
221a3db66b3SPatrick Williams            "AlignedStream: absolute offset: {}".format(self.offset),
222a3db66b3SPatrick Williams            file=sys.stderr,
223a3db66b3SPatrick Williams        )
224a3db66b3SPatrick Williams        print(
225a3db66b3SPatrick Williams            "AlignedStream: relative offset: {}".format(
226a3db66b3SPatrick Williams                self.offset - self.stash[1]
227a3db66b3SPatrick Williams            ),
228a3db66b3SPatrick Williams            file=sys.stderr,
229a3db66b3SPatrick Williams        )
230a3db66b3SPatrick Williams        print(
231a3db66b3SPatrick Williams            "AlignedStream: remaining buffer:\n{}".format(self.drain()[0]),
232a3db66b3SPatrick Williams            file=sys.stderr,
233a3db66b3SPatrick Williams        )
234a3db66b3SPatrick Williams        print(
235a3db66b3SPatrick Williams            "AlignedStream: provided buffer:\n{}".format(self.stash[0]),
236a3db66b3SPatrick Williams            file=sys.stderr,
237a3db66b3SPatrick Williams        )
238cf3c1e67SAndrew Jeffery
239cf3c1e67SAndrew Jeffery    def dump_assert(self, condition):
240cf3c1e67SAndrew Jeffery        if condition:
241cf3c1e67SAndrew Jeffery            return
242cf3c1e67SAndrew Jeffery        self.dump()
243cf3c1e67SAndrew Jeffery        assert condition
244cf3c1e67SAndrew Jeffery
245a3db66b3SPatrick Williams
246cf3c1e67SAndrew Jefferydef parse_fixed(endian, stream, tc):
247cf3c1e67SAndrew Jeffery    assert tc.type.value in TypePropertyLookup
248cf3c1e67SAndrew Jeffery    prop = TypePropertyLookup[tc.type.value]
249cf3c1e67SAndrew Jeffery    val = bytes(stream.autotake(tc))
250cf3c1e67SAndrew Jeffery    try:
251cf3c1e67SAndrew Jeffery        val = struct.unpack("{}{}".format(endian, prop.type), val)[0]
252cf3c1e67SAndrew Jeffery        return bool(val) if prop.type == DBusType.BOOLEAN else val
253cf3c1e67SAndrew Jeffery    except struct.error as e:
254855a2af4SAndrew Jeffery        print(e, file=sys.stderr)
255855a2af4SAndrew Jeffery        print("parse_fixed: Error unpacking {}".format(val), file=sys.stderr)
256a3db66b3SPatrick Williams        print(
257a3db66b3SPatrick Williams            (
258a3db66b3SPatrick Williams                "parse_fixed: Attempting to unpack type {} "
259a3db66b3SPatrick Williams                + "with properties {}"
260a3db66b3SPatrick Williams            ).format(tc.type, prop),
261a3db66b3SPatrick Williams            file=sys.stderr,
262a3db66b3SPatrick Williams        )
263cf3c1e67SAndrew Jeffery        stream.dump_assert(False)
264cf3c1e67SAndrew Jeffery
265a3db66b3SPatrick Williams
266cf3c1e67SAndrew Jefferydef parse_string(endian, stream, tc):
267cf3c1e67SAndrew Jeffery    assert tc.type.value in TypePropertyLookup
268cf3c1e67SAndrew Jeffery    prop = TypePropertyLookup[tc.type.value]
269cf3c1e67SAndrew Jeffery    size = parse_fixed(endian, stream, TypeContainer(prop.nature, None))
270cf3c1e67SAndrew Jeffery    # Empty DBus strings have no NUL-terminator
271cf3c1e67SAndrew Jeffery    if size == 0:
272cf3c1e67SAndrew Jeffery        return ""
273cf3c1e67SAndrew Jeffery    # stream.dump_assert(size > 0)
274cf3c1e67SAndrew Jeffery    val = bytes(stream.take(size + 1))
275cf3c1e67SAndrew Jeffery    try:
276cf3c1e67SAndrew Jeffery        stream.dump_assert(len(val) == size + 1)
277cf3c1e67SAndrew Jeffery        try:
278a3db66b3SPatrick Williams            return struct.unpack("{}{}".format(size, prop.type), val[:size])[
279a3db66b3SPatrick Williams                0
280a3db66b3SPatrick Williams            ].decode()
281cf3c1e67SAndrew Jeffery        except struct.error as e:
282cf3c1e67SAndrew Jeffery            stream.dump()
283cf3c1e67SAndrew Jeffery            raise AssertionError(e)
284cf3c1e67SAndrew Jeffery    except AssertionError as e:
285a3db66b3SPatrick Williams        print(
286a3db66b3SPatrick Williams            "parse_string: Error unpacking string of length {} from {}".format(
287a3db66b3SPatrick Williams                size, val
288a3db66b3SPatrick Williams            ),
289a3db66b3SPatrick Williams            file=sys.stderr,
290a3db66b3SPatrick Williams        )
291cf3c1e67SAndrew Jeffery        raise e
292cf3c1e67SAndrew Jeffery
293a3db66b3SPatrick Williams
294cf3c1e67SAndrew Jefferydef parse_type(endian, stream, tc):
295cf3c1e67SAndrew Jeffery    if tc.type.value in DBusTypeCategory.FIXED.value:
296cf3c1e67SAndrew Jeffery        val = parse_fixed(endian, stream, tc)
297cf3c1e67SAndrew Jeffery    elif tc.type.value in DBusTypeCategory.STRING.value:
298cf3c1e67SAndrew Jeffery        val = parse_string(endian, stream, tc)
299cf3c1e67SAndrew Jeffery    elif tc.type.value in DBusTypeCategory.CONTAINER.value:
300cf3c1e67SAndrew Jeffery        val = parse_container(endian, stream, tc)
301cf3c1e67SAndrew Jeffery    else:
302cf3c1e67SAndrew Jeffery        stream.dump_assert(False)
303cf3c1e67SAndrew Jeffery
304cf3c1e67SAndrew Jeffery    return val
305cf3c1e67SAndrew Jeffery
306a3db66b3SPatrick Williams
307cf3c1e67SAndrew Jefferydef parse_array(endian, stream, tc):
308cf3c1e67SAndrew Jeffery    arr = list()
309cf3c1e67SAndrew Jeffery    length = parse_fixed(endian, stream, TypeContainer(DBusType.UINT32, None))
310cf3c1e67SAndrew Jeffery    stream.align(tc)
311cf3c1e67SAndrew Jeffery    offset = stream.offset
312cf3c1e67SAndrew Jeffery    while (stream.offset - offset) < length:
313cf3c1e67SAndrew Jeffery        elem = parse_type(endian, stream, tc)
314cf3c1e67SAndrew Jeffery        arr.append(elem)
315cf3c1e67SAndrew Jeffery        if (stream.offset - offset) < length:
316cf3c1e67SAndrew Jeffery            stream.align(tc)
317cf3c1e67SAndrew Jeffery    return arr
318cf3c1e67SAndrew Jeffery
319a3db66b3SPatrick Williams
320cf3c1e67SAndrew Jefferydef parse_struct(endian, stream, tcs):
321cf3c1e67SAndrew Jeffery    arr = list()
322cf3c1e67SAndrew Jeffery    stream.align(TypeContainer(DBusType.STRUCT, None))
323cf3c1e67SAndrew Jeffery    for tc in tcs:
324cf3c1e67SAndrew Jeffery        arr.append(parse_type(endian, stream, tc))
325cf3c1e67SAndrew Jeffery    return arr
326cf3c1e67SAndrew Jeffery
327a3db66b3SPatrick Williams
328cf3c1e67SAndrew Jefferydef parse_variant(endian, stream):
329cf3c1e67SAndrew Jeffery    sig = parse_string(endian, stream, TypeContainer(DBusType.SIGNATURE, None))
330cf3c1e67SAndrew Jeffery    tc = parse_signature(iter(sig))
331cf3c1e67SAndrew Jeffery    return parse_type(endian, stream, tc)
332cf3c1e67SAndrew Jeffery
333a3db66b3SPatrick Williams
334cf3c1e67SAndrew Jefferydef parse_container(endian, stream, tc):
335cf3c1e67SAndrew Jeffery    if tc.type == DBusType.ARRAY:
336cf3c1e67SAndrew Jeffery        return parse_array(endian, stream, tc.members)
337cf3c1e67SAndrew Jeffery    elif tc.type in (DBusType.STRUCT, DBusType.DICT_ENTRY):
338cf3c1e67SAndrew Jeffery        return parse_struct(endian, stream, tc.members)
339cf3c1e67SAndrew Jeffery    elif tc.type == DBusType.VARIANT:
340cf3c1e67SAndrew Jeffery        return parse_variant(endian, stream)
341cf3c1e67SAndrew Jeffery    else:
342cf3c1e67SAndrew Jeffery        stream.dump_assert(False)
343cf3c1e67SAndrew Jeffery
344a3db66b3SPatrick Williams
345cf3c1e67SAndrew Jefferydef parse_fields(endian, stream):
346cf3c1e67SAndrew Jeffery    sig = parse_signature(iter("a(yv)"))
347cf3c1e67SAndrew Jeffery    fields = parse_container(endian, stream, sig)
348cf3c1e67SAndrew Jeffery    # The header ends after its alignment padding to an 8-boundary.
349cf3c1e67SAndrew Jeffery    # https://dbus.freedesktop.org/doc/dbus-specification.html#message-protocol-messages
350cf3c1e67SAndrew Jeffery    stream.align(TypeContainer(DBusType.STRUCT, None))
351cf3c1e67SAndrew Jeffery    return list(map(lambda v: Field(MessageFieldType(v[0]), v[1]), fields))
352cf3c1e67SAndrew Jeffery
353a3db66b3SPatrick Williams
354cf3c1e67SAndrew Jefferyclass MalformedPacketError(Exception):
355cf3c1e67SAndrew Jeffery    pass
356cf3c1e67SAndrew Jeffery
357a3db66b3SPatrick Williams
35854e0c9aeSSui Chendef parse_header(raw, ignore_error):
359cf3c1e67SAndrew Jeffery    assert raw.endian in StructEndianLookup.keys()
360cf3c1e67SAndrew Jeffery    endian = StructEndianLookup[raw.endian]
361a3db66b3SPatrick Williams    fixed = FixedHeader._make(
362a3db66b3SPatrick Williams        struct.unpack("{}BBBBLL".format(endian), raw.header)
363a3db66b3SPatrick Williams    )
364cf3c1e67SAndrew Jeffery    astream = AlignedStream(raw.data, len(raw.header))
365cf3c1e67SAndrew Jeffery    fields = parse_fields(endian, astream)
366cf3c1e67SAndrew Jeffery    data, offset = astream.drain()
367a3db66b3SPatrick Williams    if (not ignore_error) and fixed.length > len(data):
368cf3c1e67SAndrew Jeffery        raise MalformedPacketError
369cf3c1e67SAndrew Jeffery    return CookedHeader(fixed, fields), AlignedStream(data, offset)
370cf3c1e67SAndrew Jeffery
371a3db66b3SPatrick Williams
372cf3c1e67SAndrew Jefferydef parse_body(header, stream):
373cf3c1e67SAndrew Jeffery    assert header.fixed.endian in StructEndianLookup
374cf3c1e67SAndrew Jeffery    endian = StructEndianLookup[header.fixed.endian]
375cf3c1e67SAndrew Jeffery    body = list()
376cf3c1e67SAndrew Jeffery    for field in header.fields:
377cf3c1e67SAndrew Jeffery        if field.type == MessageFieldType.SIGNATURE:
378cf3c1e67SAndrew Jeffery            sigstream = iter(field.data)
379cf3c1e67SAndrew Jeffery            try:
380cf3c1e67SAndrew Jeffery                while True:
381cf3c1e67SAndrew Jeffery                    tc = parse_signature(sigstream)
382cf3c1e67SAndrew Jeffery                    val = parse_type(endian, stream, tc)
383cf3c1e67SAndrew Jeffery                    body.append(val)
384cf3c1e67SAndrew Jeffery            except StopIteration:
385cf3c1e67SAndrew Jeffery                pass
386cf3c1e67SAndrew Jeffery            break
387cf3c1e67SAndrew Jeffery    return body
388cf3c1e67SAndrew Jeffery
389a3db66b3SPatrick Williams
390cf3c1e67SAndrew Jefferydef parse_message(raw):
391cf3c1e67SAndrew Jeffery    try:
39254e0c9aeSSui Chen        header, data = parse_header(raw, False)
393cf3c1e67SAndrew Jeffery        try:
394cf3c1e67SAndrew Jeffery            body = parse_body(header, data)
395cf3c1e67SAndrew Jeffery            return CookedMessage(header, body)
396cf3c1e67SAndrew Jeffery        except AssertionError as e:
397855a2af4SAndrew Jeffery            print(header, file=sys.stderr)
398cf3c1e67SAndrew Jeffery            raise e
399cf3c1e67SAndrew Jeffery    except AssertionError as e:
400855a2af4SAndrew Jeffery        print(raw, file=sys.stderr)
401cf3c1e67SAndrew Jeffery        raise e
402cf3c1e67SAndrew Jeffery
403a3db66b3SPatrick Williams
404cf3c1e67SAndrew Jefferydef parse_packet(packet):
405cf3c1e67SAndrew Jeffery    data = bytes(packet)
40654e0c9aeSSui Chen    raw = RawMessage(data[0], data[:12], data[12:])
40754e0c9aeSSui Chen    try:
40854e0c9aeSSui Chen        msg = parse_message(raw)
409a3db66b3SPatrick Williams    except MalformedPacketError:
410855a2af4SAndrew Jeffery        print("Got malformed packet: {}".format(raw), file=sys.stderr)
411a3db66b3SPatrick Williams        # For a message that is so large that its payload data could not be
412a3db66b3SPatrick Williams        # parsed, just parse its header, then set its data field to empty.
41354e0c9aeSSui Chen        header, data = parse_header(raw, True)
41454e0c9aeSSui Chen        msg = CookedMessage(header, [])
415cf3c1e67SAndrew Jeffery    return msg
416cf3c1e67SAndrew Jeffery
417a3db66b3SPatrick Williams
418cf3c1e67SAndrew JefferyCallEnvelope = namedtuple("CallEnvelope", "cookie, origin")
419a3db66b3SPatrick Williams
420a3db66b3SPatrick Williams
421cf3c1e67SAndrew Jefferydef parse_session(session, matchers, track_calls):
422cf3c1e67SAndrew Jeffery    calls = set()
423cf3c1e67SAndrew Jeffery    for packet in session:
424cf3c1e67SAndrew Jeffery        try:
425cf3c1e67SAndrew Jeffery            cooked = parse_packet(packet)
426cf3c1e67SAndrew Jeffery            if not matchers:
427cf3c1e67SAndrew Jeffery                yield packet.time, cooked
428cf3c1e67SAndrew Jeffery            elif any(all(r(cooked) for r in m) for m in matchers):
429cf3c1e67SAndrew Jeffery                if cooked.header.fixed.type == MessageType.METHOD_CALL.value:
430a3db66b3SPatrick Williams                    s = [
431a3db66b3SPatrick Williams                        f
432a3db66b3SPatrick Williams                        for f in cooked.header.fields
433a3db66b3SPatrick Williams                        if f.type == MessageFieldType.SENDER
434a3db66b3SPatrick Williams                    ][0]
435cf3c1e67SAndrew Jeffery                    calls.add(CallEnvelope(cooked.header.fixed.cookie, s.data))
436cf3c1e67SAndrew Jeffery                yield packet.time, cooked
437cf3c1e67SAndrew Jeffery            elif track_calls:
438*96ebc4e7SAndrew Jeffery                responseTypes = {
439*96ebc4e7SAndrew Jeffery                    MessageType.METHOD_RETURN.value,
440*96ebc4e7SAndrew Jeffery                    MessageType.ERROR.value,
441*96ebc4e7SAndrew Jeffery                }
442*96ebc4e7SAndrew Jeffery                if cooked.header.fixed.type not in responseTypes:
443cf3c1e67SAndrew Jeffery                    continue
444a3db66b3SPatrick Williams                rs = [
445a3db66b3SPatrick Williams                    f
446a3db66b3SPatrick Williams                    for f in cooked.header.fields
447a3db66b3SPatrick Williams                    if f.type == MessageFieldType.REPLY_SERIAL
448a3db66b3SPatrick Williams                ][0]
449a3db66b3SPatrick Williams                d = [
450a3db66b3SPatrick Williams                    f
451a3db66b3SPatrick Williams                    for f in cooked.header.fields
452a3db66b3SPatrick Williams                    if f.type == MessageFieldType.DESTINATION
453a3db66b3SPatrick Williams                ][0]
454cf3c1e67SAndrew Jeffery                ce = CallEnvelope(rs.data, d.data)
455cf3c1e67SAndrew Jeffery                if ce in calls:
456cf3c1e67SAndrew Jeffery                    calls.remove(ce)
457cf3c1e67SAndrew Jeffery                    yield packet.time, cooked
458a3db66b3SPatrick Williams        except MalformedPacketError:
459cf3c1e67SAndrew Jeffery            pass
460cf3c1e67SAndrew Jeffery
461a3db66b3SPatrick Williams
462cf3c1e67SAndrew Jefferydef gen_match_type(rule):
463cf3c1e67SAndrew Jeffery    mt = MessageType.__members__[rule.value.upper()]
464cf3c1e67SAndrew Jeffery    return lambda p: p.header.fixed.type == mt.value
465cf3c1e67SAndrew Jeffery
466a3db66b3SPatrick Williams
467cf3c1e67SAndrew Jefferydef gen_match_sender(rule):
468cf3c1e67SAndrew Jeffery    mf = Field(MessageFieldType.SENDER, rule.value)
469cf3c1e67SAndrew Jeffery    return lambda p: any(f == mf for f in p.header.fields)
470cf3c1e67SAndrew Jeffery
471a3db66b3SPatrick Williams
472cf3c1e67SAndrew Jefferydef gen_match_interface(rule):
473cf3c1e67SAndrew Jeffery    mf = Field(MessageFieldType.INTERFACE, rule.value)
474cf3c1e67SAndrew Jeffery    return lambda p: any(f == mf for f in p.header.fields)
475cf3c1e67SAndrew Jeffery
476a3db66b3SPatrick Williams
477cf3c1e67SAndrew Jefferydef gen_match_member(rule):
478cf3c1e67SAndrew Jeffery    mf = Field(MessageFieldType.MEMBER, rule.value)
479cf3c1e67SAndrew Jeffery    return lambda p: any(f == mf for f in p.header.fields)
480cf3c1e67SAndrew Jeffery
481a3db66b3SPatrick Williams
482cf3c1e67SAndrew Jefferydef gen_match_path(rule):
483cf3c1e67SAndrew Jeffery    mf = Field(MessageFieldType.PATH, rule.value)
484cf3c1e67SAndrew Jeffery    return lambda p: any(f == mf for f in p.header.fields)
485cf3c1e67SAndrew Jeffery
486a3db66b3SPatrick Williams
487cf3c1e67SAndrew Jefferydef gen_match_destination(rule):
488cf3c1e67SAndrew Jeffery    mf = Field(MessageFieldType.DESTINATION, rule.value)
489cf3c1e67SAndrew Jeffery    return lambda p: any(f == mf for f in p.header.fields)
490cf3c1e67SAndrew Jeffery
491a3db66b3SPatrick Williams
492cf3c1e67SAndrew JefferyValidMatchKeys = {
493a3db66b3SPatrick Williams    "type",
494a3db66b3SPatrick Williams    "sender",
495a3db66b3SPatrick Williams    "interface",
496a3db66b3SPatrick Williams    "member",
497a3db66b3SPatrick Williams    "path",
498a3db66b3SPatrick Williams    "destination",
499cf3c1e67SAndrew Jeffery}
500cf3c1e67SAndrew JefferyMatchRule = namedtuple("MatchExpression", "key, value")
501cf3c1e67SAndrew Jeffery
502a3db66b3SPatrick Williams
503cf3c1e67SAndrew Jeffery# https://dbus.freedesktop.org/doc/dbus-specification.html#message-bus-routing-match-rules
504cf3c1e67SAndrew Jefferydef parse_match_rules(exprs):
505cf3c1e67SAndrew Jeffery    matchers = list()
506cf3c1e67SAndrew Jeffery    for mexpr in exprs:
507cf3c1e67SAndrew Jeffery        rules = list()
508cf3c1e67SAndrew Jeffery        for rexpr in mexpr.split(","):
509a3db66b3SPatrick Williams            rule = MatchRule._make(
510a3db66b3SPatrick Williams                map(lambda s: str.strip(s, "'"), rexpr.split("="))
511a3db66b3SPatrick Williams            )
512a3db66b3SPatrick Williams            assert rule.key in ValidMatchKeys, f"Invalid expression: {rule}"
513cf3c1e67SAndrew Jeffery            rules.append(globals()["gen_match_{}".format(rule.key)](rule))
514cf3c1e67SAndrew Jeffery        matchers.append(rules)
515cf3c1e67SAndrew Jeffery    return matchers
516cf3c1e67SAndrew Jeffery
517a3db66b3SPatrick Williams
518cf3c1e67SAndrew Jefferydef packetconv(obj):
519cf3c1e67SAndrew Jeffery    if isinstance(obj, Enum):
520cf3c1e67SAndrew Jeffery        return obj.value
521cf3c1e67SAndrew Jeffery    raise TypeError
522cf3c1e67SAndrew Jeffery
523a3db66b3SPatrick Williams
524cf3c1e67SAndrew Jefferydef main():
525cf3c1e67SAndrew Jeffery    parser = ArgumentParser()
526a3db66b3SPatrick Williams    parser.add_argument(
527a3db66b3SPatrick Williams        "--json",
528a3db66b3SPatrick Williams        action="store_true",
529a3db66b3SPatrick Williams        help="Emit a JSON representation of the messages",
530a3db66b3SPatrick Williams    )
531a3db66b3SPatrick Williams    parser.add_argument(
532a3db66b3SPatrick Williams        "--no-track-calls",
533a3db66b3SPatrick Williams        action="store_true",
534a3db66b3SPatrick Williams        default=False,
535a3db66b3SPatrick Williams        help="Make a call response pass filters",
536a3db66b3SPatrick Williams    )
537cf3c1e67SAndrew Jeffery    parser.add_argument("file", help="The pcap file")
538a3db66b3SPatrick Williams    parser.add_argument(
539a3db66b3SPatrick Williams        "expressions", nargs="*", help="DBus message match expressions"
540a3db66b3SPatrick Williams    )
541cf3c1e67SAndrew Jeffery    args = parser.parse_args()
542cf3c1e67SAndrew Jeffery    stream = rdpcap(args.file)
543cf3c1e67SAndrew Jeffery    matchers = parse_match_rules(args.expressions)
544cf3c1e67SAndrew Jeffery    try:
545cf3c1e67SAndrew Jeffery        if args.json:
546e310dd91SPatrick Williams            for _, msg in parse_session(
547a3db66b3SPatrick Williams                stream, matchers, not args.no_track_calls
548a3db66b3SPatrick Williams            ):
549cf3c1e67SAndrew Jeffery                print("{}".format(json.dumps(msg, default=packetconv)))
550cf3c1e67SAndrew Jeffery        else:
551e310dd91SPatrick Williams            for time, msg in parse_session(
552a3db66b3SPatrick Williams                stream, matchers, not args.no_track_calls
553a3db66b3SPatrick Williams            ):
554cf3c1e67SAndrew Jeffery                print("{}: {}".format(time, msg))
555cf3c1e67SAndrew Jeffery                print()
556cf3c1e67SAndrew Jeffery    except BrokenPipeError:
557cf3c1e67SAndrew Jeffery        pass
558cf3c1e67SAndrew Jeffery
559a3db66b3SPatrick Williams
560cf3c1e67SAndrew Jefferyif __name__ == "__main__":
561cf3c1e67SAndrew Jeffery    main()
562