1061da546Spatrick"""Module for supporting unit testing of the lldb-server debug monitor exe.
2061da546Spatrick"""
3061da546Spatrick
4061da546Spatrickfrom __future__ import division, print_function
5061da546Spatrick
6*be691f3bSpatrickimport binascii
7061da546Spatrickimport os
8061da546Spatrickimport os.path
9061da546Spatrickimport platform
10061da546Spatrickimport re
11061da546Spatrickimport six
12*be691f3bSpatrickimport socket
13061da546Spatrickimport subprocess
14*be691f3bSpatrickfrom lldbsuite.support import seven
15061da546Spatrickfrom lldbsuite.test.lldbtest import *
16*be691f3bSpatrickfrom lldbsuite.test import configuration
17*be691f3bSpatrickfrom textwrap import dedent
18*be691f3bSpatrickimport shutil
19061da546Spatrick
20*be691f3bSpatrickdef _get_support_exe(basename):
21*be691f3bSpatrick    support_dir = lldb.SBHostOS.GetLLDBPath(lldb.ePathTypeSupportExecutableDir)
22061da546Spatrick
23*be691f3bSpatrick    return shutil.which(basename, path=support_dir.GetDirectory())
24061da546Spatrick
25061da546Spatrick
26061da546Spatrickdef get_lldb_server_exe():
27061da546Spatrick    """Return the lldb-server exe path.
28061da546Spatrick
29061da546Spatrick    Returns:
30061da546Spatrick        A path to the lldb-server exe if it is found to exist; otherwise,
31061da546Spatrick        returns None.
32061da546Spatrick    """
33061da546Spatrick
34*be691f3bSpatrick    return _get_support_exe("lldb-server")
35061da546Spatrick
36061da546Spatrick
37061da546Spatrickdef get_debugserver_exe():
38061da546Spatrick    """Return the debugserver exe path.
39061da546Spatrick
40061da546Spatrick    Returns:
41061da546Spatrick        A path to the debugserver exe if it is found to exist; otherwise,
42061da546Spatrick        returns None.
43061da546Spatrick    """
44*be691f3bSpatrick    if configuration.arch and configuration.arch == "x86_64" and \
45*be691f3bSpatrick       platform.machine().startswith("arm64"):
46*be691f3bSpatrick        return '/Library/Apple/usr/libexec/oah/debugserver'
47061da546Spatrick
48*be691f3bSpatrick    return _get_support_exe("debugserver")
49061da546Spatrick
50061da546Spatrick_LOG_LINE_REGEX = re.compile(r'^(lldb-server|debugserver)\s+<\s*(\d+)>' +
51061da546Spatrick                             '\s+(read|send)\s+packet:\s+(.+)$')
52061da546Spatrick
53061da546Spatrick
54061da546Spatrickdef _is_packet_lldb_gdbserver_input(packet_type, llgs_input_is_read):
55061da546Spatrick    """Return whether a given packet is input for lldb-gdbserver.
56061da546Spatrick
57061da546Spatrick    Args:
58061da546Spatrick        packet_type: a string indicating 'send' or 'receive', from a
59061da546Spatrick            gdbremote packet protocol log.
60061da546Spatrick
61061da546Spatrick        llgs_input_is_read: true if lldb-gdbserver input (content sent to
62061da546Spatrick            lldb-gdbserver) is listed as 'read' or 'send' in the packet
63061da546Spatrick            log entry.
64061da546Spatrick
65061da546Spatrick    Returns:
66061da546Spatrick        True if the packet should be considered input for lldb-gdbserver; False
67061da546Spatrick        otherwise.
68061da546Spatrick    """
69061da546Spatrick    if packet_type == 'read':
70061da546Spatrick        # when llgs is the read side, then a read packet is meant for
71061da546Spatrick        # input to llgs (when captured from the llgs/debugserver exe).
72061da546Spatrick        return llgs_input_is_read
73061da546Spatrick    elif packet_type == 'send':
74061da546Spatrick        # when llgs is the send side, then a send packet is meant to
75061da546Spatrick        # be input to llgs (when captured from the lldb exe).
76061da546Spatrick        return not llgs_input_is_read
77061da546Spatrick    else:
78061da546Spatrick        # don't understand what type of packet this is
79061da546Spatrick        raise "Unknown packet type: {}".format(packet_type)
80061da546Spatrick
81061da546Spatrick
82061da546Spatrickdef handle_O_packet(context, packet_contents, logger):
83061da546Spatrick    """Handle O packets."""
84061da546Spatrick    if (not packet_contents) or (len(packet_contents) < 1):
85061da546Spatrick        return False
86061da546Spatrick    elif packet_contents[0] != "O":
87061da546Spatrick        return False
88061da546Spatrick    elif packet_contents == "OK":
89061da546Spatrick        return False
90061da546Spatrick
91061da546Spatrick    new_text = gdbremote_hex_decode_string(packet_contents[1:])
92061da546Spatrick    context["O_content"] += new_text
93061da546Spatrick    context["O_count"] += 1
94061da546Spatrick
95061da546Spatrick    if logger:
96061da546Spatrick        logger.debug(
97061da546Spatrick            "text: new \"{}\", cumulative: \"{}\"".format(
98061da546Spatrick                new_text, context["O_content"]))
99061da546Spatrick
100061da546Spatrick    return True
101061da546Spatrick
102061da546Spatrick_STRIP_CHECKSUM_REGEX = re.compile(r'#[0-9a-fA-F]{2}$')
103061da546Spatrick_STRIP_COMMAND_PREFIX_REGEX = re.compile(r"^\$")
104061da546Spatrick_STRIP_COMMAND_PREFIX_M_REGEX = re.compile(r"^\$m")
105061da546Spatrick
106061da546Spatrick
107061da546Spatrickdef assert_packets_equal(asserter, actual_packet, expected_packet):
108061da546Spatrick    # strip off the checksum digits of the packet.  When we're in
109061da546Spatrick    # no-ack mode, the # checksum is ignored, and should not be cause
110061da546Spatrick    # for a mismatched packet.
111061da546Spatrick    actual_stripped = _STRIP_CHECKSUM_REGEX.sub('', actual_packet)
112061da546Spatrick    expected_stripped = _STRIP_CHECKSUM_REGEX.sub('', expected_packet)
113061da546Spatrick    asserter.assertEqual(actual_stripped, expected_stripped)
114061da546Spatrick
115061da546Spatrick
116061da546Spatrickdef expect_lldb_gdbserver_replay(
117061da546Spatrick        asserter,
118*be691f3bSpatrick        server,
119061da546Spatrick        test_sequence,
120061da546Spatrick        timeout_seconds,
121061da546Spatrick        logger=None):
122061da546Spatrick    """Replay socket communication with lldb-gdbserver and verify responses.
123061da546Spatrick
124061da546Spatrick    Args:
125061da546Spatrick        asserter: the object providing assertEqual(first, second, msg=None), e.g. TestCase instance.
126061da546Spatrick
127061da546Spatrick        test_sequence: a GdbRemoteTestSequence instance that describes
128061da546Spatrick            the messages sent to the gdb remote and the responses
129061da546Spatrick            expected from it.
130061da546Spatrick
131061da546Spatrick        timeout_seconds: any response taking more than this number of
132061da546Spatrick           seconds will cause an exception to be raised.
133061da546Spatrick
134061da546Spatrick        logger: a Python logger instance.
135061da546Spatrick
136061da546Spatrick    Returns:
137061da546Spatrick        The context dictionary from running the given gdbremote
138061da546Spatrick        protocol sequence.  This will contain any of the capture
139061da546Spatrick        elements specified to any GdbRemoteEntry instances in
140061da546Spatrick        test_sequence.
141061da546Spatrick
142061da546Spatrick        The context will also contain an entry, context["O_content"]
143061da546Spatrick        which contains the text from the inferior received via $O
144061da546Spatrick        packets.  $O packets should not attempt to be matched
145061da546Spatrick        directly since they are not entirely deterministic as to
146061da546Spatrick        how many arrive and how much text is in each one.
147061da546Spatrick
148061da546Spatrick        context["O_count"] will contain an integer of the number of
149061da546Spatrick        O packets received.
150061da546Spatrick    """
151061da546Spatrick
152061da546Spatrick    # Ensure we have some work to do.
153061da546Spatrick    if len(test_sequence.entries) < 1:
154061da546Spatrick        return {}
155061da546Spatrick
156061da546Spatrick    context = {"O_count": 0, "O_content": ""}
157*be691f3bSpatrick
158061da546Spatrick    # Grab the first sequence entry.
159061da546Spatrick    sequence_entry = test_sequence.entries.pop(0)
160061da546Spatrick
161061da546Spatrick    # While we have an active sequence entry, send messages
162061da546Spatrick    # destined for the stub and collect/match/process responses
163061da546Spatrick    # expected from the stub.
164061da546Spatrick    while sequence_entry:
165061da546Spatrick        if sequence_entry.is_send_to_remote():
166061da546Spatrick            # This is an entry to send to the remote debug monitor.
167061da546Spatrick            send_packet = sequence_entry.get_send_packet()
168061da546Spatrick            if logger:
169061da546Spatrick                if len(send_packet) == 1 and send_packet[0] == chr(3):
170061da546Spatrick                    packet_desc = "^C"
171061da546Spatrick                else:
172061da546Spatrick                    packet_desc = send_packet
173061da546Spatrick                logger.info(
174061da546Spatrick                    "sending packet to remote: {}".format(packet_desc))
175*be691f3bSpatrick            server.send_raw(send_packet.encode())
176061da546Spatrick        else:
177061da546Spatrick            # This is an entry expecting to receive content from the remote
178061da546Spatrick            # debug monitor.
179061da546Spatrick
180061da546Spatrick            # We'll pull from (and wait on) the queue appropriate for the type of matcher.
181061da546Spatrick            # We keep separate queues for process output (coming from non-deterministic
182061da546Spatrick            # $O packet division) and for all other packets.
183*be691f3bSpatrick            try:
184061da546Spatrick                if sequence_entry.is_output_matcher():
185061da546Spatrick                    # Grab next entry from the output queue.
186*be691f3bSpatrick                    content = server.get_raw_output_packet()
187061da546Spatrick                else:
188*be691f3bSpatrick                    content = server.get_raw_normal_packet()
189*be691f3bSpatrick                content = seven.bitcast_to_string(content)
190*be691f3bSpatrick            except socket.timeout:
191*be691f3bSpatrick                asserter.fail(
192*be691f3bSpatrick                        "timed out while waiting for '{}':\n{}".format(sequence_entry, server))
193061da546Spatrick
194061da546Spatrick            # Give the sequence entry the opportunity to match the content.
195061da546Spatrick            # Output matchers might match or pass after more output accumulates.
196061da546Spatrick            # Other packet types generally must match.
197061da546Spatrick            asserter.assertIsNotNone(content)
198061da546Spatrick            context = sequence_entry.assert_match(
199061da546Spatrick                asserter, content, context=context)
200061da546Spatrick
201061da546Spatrick        # Move on to next sequence entry as needed.  Some sequence entries support executing multiple
202061da546Spatrick        # times in different states (for looping over query/response
203061da546Spatrick        # packets).
204061da546Spatrick        if sequence_entry.is_consumed():
205061da546Spatrick            if len(test_sequence.entries) > 0:
206061da546Spatrick                sequence_entry = test_sequence.entries.pop(0)
207061da546Spatrick            else:
208061da546Spatrick                sequence_entry = None
209061da546Spatrick
210061da546Spatrick    # Fill in the O_content entries.
211061da546Spatrick    context["O_count"] = 1
212*be691f3bSpatrick    context["O_content"] = server.consume_accumulated_output()
213061da546Spatrick
214061da546Spatrick    return context
215061da546Spatrick
216061da546Spatrick
217061da546Spatrickdef gdbremote_hex_encode_string(str):
218061da546Spatrick    output = ''
219061da546Spatrick    for c in str:
220061da546Spatrick        output += '{0:02x}'.format(ord(c))
221061da546Spatrick    return output
222061da546Spatrick
223061da546Spatrick
224061da546Spatrickdef gdbremote_hex_decode_string(str):
225061da546Spatrick    return str.decode("hex")
226061da546Spatrick
227061da546Spatrick
228061da546Spatrickdef gdbremote_packet_encode_string(str):
229061da546Spatrick    checksum = 0
230061da546Spatrick    for c in str:
231061da546Spatrick        checksum += ord(c)
232061da546Spatrick    return '$' + str + '#{0:02x}'.format(checksum % 256)
233061da546Spatrick
234061da546Spatrick
235061da546Spatrickdef build_gdbremote_A_packet(args_list):
236061da546Spatrick    """Given a list of args, create a properly-formed $A packet containing each arg.
237061da546Spatrick    """
238061da546Spatrick    payload = "A"
239061da546Spatrick
240061da546Spatrick    # build the arg content
241061da546Spatrick    arg_index = 0
242061da546Spatrick    for arg in args_list:
243061da546Spatrick        # Comma-separate the args.
244061da546Spatrick        if arg_index > 0:
245061da546Spatrick            payload += ','
246061da546Spatrick
247061da546Spatrick        # Hex-encode the arg.
248061da546Spatrick        hex_arg = gdbremote_hex_encode_string(arg)
249061da546Spatrick
250061da546Spatrick        # Build the A entry.
251061da546Spatrick        payload += "{},{},{}".format(len(hex_arg), arg_index, hex_arg)
252061da546Spatrick
253061da546Spatrick        # Next arg index, please.
254061da546Spatrick        arg_index += 1
255061da546Spatrick
256061da546Spatrick    # return the packetized payload
257061da546Spatrick    return gdbremote_packet_encode_string(payload)
258061da546Spatrick
259061da546Spatrick
260061da546Spatrickdef parse_reg_info_response(response_packet):
261061da546Spatrick    if not response_packet:
262061da546Spatrick        raise Exception("response_packet cannot be None")
263061da546Spatrick
264061da546Spatrick    # Strip off prefix $ and suffix #xx if present.
265061da546Spatrick    response_packet = _STRIP_COMMAND_PREFIX_REGEX.sub("", response_packet)
266061da546Spatrick    response_packet = _STRIP_CHECKSUM_REGEX.sub("", response_packet)
267061da546Spatrick
268061da546Spatrick    # Build keyval pairs
269061da546Spatrick    values = {}
270061da546Spatrick    for kv in response_packet.split(";"):
271061da546Spatrick        if len(kv) < 1:
272061da546Spatrick            continue
273061da546Spatrick        (key, val) = kv.split(':')
274061da546Spatrick        values[key] = val
275061da546Spatrick
276061da546Spatrick    return values
277061da546Spatrick
278061da546Spatrick
279061da546Spatrickdef parse_threadinfo_response(response_packet):
280061da546Spatrick    if not response_packet:
281061da546Spatrick        raise Exception("response_packet cannot be None")
282061da546Spatrick
283061da546Spatrick    # Strip off prefix $ and suffix #xx if present.
284061da546Spatrick    response_packet = _STRIP_COMMAND_PREFIX_M_REGEX.sub("", response_packet)
285061da546Spatrick    response_packet = _STRIP_CHECKSUM_REGEX.sub("", response_packet)
286061da546Spatrick
287061da546Spatrick    # Return list of thread ids
288061da546Spatrick    return [int(thread_id_hex, 16) for thread_id_hex in response_packet.split(
289061da546Spatrick        ",") if len(thread_id_hex) > 0]
290061da546Spatrick
291061da546Spatrick
292061da546Spatrickdef unpack_endian_binary_string(endian, value_string):
293061da546Spatrick    """Unpack a gdb-remote binary (post-unescaped, i.e. not escaped) response to an unsigned int given endianness of the inferior."""
294061da546Spatrick    if not endian:
295061da546Spatrick        raise Exception("endian cannot be None")
296061da546Spatrick    if not value_string or len(value_string) < 1:
297061da546Spatrick        raise Exception("value_string cannot be None or empty")
298061da546Spatrick
299061da546Spatrick    if endian == 'little':
300061da546Spatrick        value = 0
301061da546Spatrick        i = 0
302061da546Spatrick        while len(value_string) > 0:
303061da546Spatrick            value += (ord(value_string[0]) << i)
304061da546Spatrick            value_string = value_string[1:]
305061da546Spatrick            i += 8
306061da546Spatrick        return value
307061da546Spatrick    elif endian == 'big':
308061da546Spatrick        value = 0
309061da546Spatrick        while len(value_string) > 0:
310061da546Spatrick            value = (value << 8) + ord(value_string[0])
311061da546Spatrick            value_string = value_string[1:]
312061da546Spatrick        return value
313061da546Spatrick    else:
314061da546Spatrick        # pdp is valid but need to add parse code once needed.
315061da546Spatrick        raise Exception("unsupported endian:{}".format(endian))
316061da546Spatrick
317061da546Spatrick
318061da546Spatrickdef unpack_register_hex_unsigned(endian, value_string):
319061da546Spatrick    """Unpack a gdb-remote $p-style response to an unsigned int given endianness of inferior."""
320061da546Spatrick    if not endian:
321061da546Spatrick        raise Exception("endian cannot be None")
322061da546Spatrick    if not value_string or len(value_string) < 1:
323061da546Spatrick        raise Exception("value_string cannot be None or empty")
324061da546Spatrick
325061da546Spatrick    if endian == 'little':
326061da546Spatrick        value = 0
327061da546Spatrick        i = 0
328061da546Spatrick        while len(value_string) > 0:
329061da546Spatrick            value += (int(value_string[0:2], 16) << i)
330061da546Spatrick            value_string = value_string[2:]
331061da546Spatrick            i += 8
332061da546Spatrick        return value
333061da546Spatrick    elif endian == 'big':
334061da546Spatrick        return int(value_string, 16)
335061da546Spatrick    else:
336061da546Spatrick        # pdp is valid but need to add parse code once needed.
337061da546Spatrick        raise Exception("unsupported endian:{}".format(endian))
338061da546Spatrick
339061da546Spatrick
340061da546Spatrickdef pack_register_hex(endian, value, byte_size=None):
341061da546Spatrick    """Unpack a gdb-remote $p-style response to an unsigned int given endianness of inferior."""
342061da546Spatrick    if not endian:
343061da546Spatrick        raise Exception("endian cannot be None")
344061da546Spatrick
345061da546Spatrick    if endian == 'little':
346061da546Spatrick        # Create the litt-endian return value.
347061da546Spatrick        retval = ""
348061da546Spatrick        while value != 0:
349061da546Spatrick            retval = retval + "{:02x}".format(value & 0xff)
350061da546Spatrick            value = value >> 8
351061da546Spatrick        if byte_size:
352061da546Spatrick            # Add zero-fill to the right/end (MSB side) of the value.
353061da546Spatrick            retval += "00" * (byte_size - len(retval) // 2)
354061da546Spatrick        return retval
355061da546Spatrick
356061da546Spatrick    elif endian == 'big':
357061da546Spatrick        retval = ""
358061da546Spatrick        while value != 0:
359061da546Spatrick            retval = "{:02x}".format(value & 0xff) + retval
360061da546Spatrick            value = value >> 8
361061da546Spatrick        if byte_size:
362061da546Spatrick            # Add zero-fill to the left/front (MSB side) of the value.
363061da546Spatrick            retval = ("00" * (byte_size - len(retval) // 2)) + retval
364061da546Spatrick        return retval
365061da546Spatrick
366061da546Spatrick    else:
367061da546Spatrick        # pdp is valid but need to add parse code once needed.
368061da546Spatrick        raise Exception("unsupported endian:{}".format(endian))
369061da546Spatrick
370061da546Spatrick
371061da546Spatrickclass GdbRemoteEntryBase(object):
372061da546Spatrick
373061da546Spatrick    def is_output_matcher(self):
374061da546Spatrick        return False
375061da546Spatrick
376061da546Spatrick
377061da546Spatrickclass GdbRemoteEntry(GdbRemoteEntryBase):
378061da546Spatrick
379061da546Spatrick    def __init__(
380061da546Spatrick            self,
381061da546Spatrick            is_send_to_remote=True,
382061da546Spatrick            exact_payload=None,
383061da546Spatrick            regex=None,
384*be691f3bSpatrick            capture=None):
385061da546Spatrick        """Create an entry representing one piece of the I/O to/from a gdb remote debug monitor.
386061da546Spatrick
387061da546Spatrick        Args:
388061da546Spatrick
389061da546Spatrick            is_send_to_remote: True if this entry is a message to be
390061da546Spatrick                sent to the gdbremote debug monitor; False if this
391061da546Spatrick                entry represents text to be matched against the reply
392061da546Spatrick                from the gdbremote debug monitor.
393061da546Spatrick
394061da546Spatrick            exact_payload: if not None, then this packet is an exact
395061da546Spatrick                send (when sending to the remote) or an exact match of
396061da546Spatrick                the response from the gdbremote. The checksums are
397061da546Spatrick                ignored on exact match requests since negotiation of
398061da546Spatrick                no-ack makes the checksum content essentially
399061da546Spatrick                undefined.
400061da546Spatrick
401*be691f3bSpatrick            regex: currently only valid for receives from gdbremote.  When
402*be691f3bSpatrick                specified (and only if exact_payload is None), indicates the
403*be691f3bSpatrick                gdbremote response must match the given regex. Match groups in
404*be691f3bSpatrick                the regex can be used for the matching portion (see capture
405*be691f3bSpatrick                arg). It is perfectly valid to have just a regex arg without a
406*be691f3bSpatrick                capture arg. This arg only makes sense if exact_payload is not
407061da546Spatrick                specified.
408061da546Spatrick
409061da546Spatrick            capture: if specified, is a dictionary of regex match
410061da546Spatrick                group indices (should start with 1) to variable names
411061da546Spatrick                that will store the capture group indicated by the
412061da546Spatrick                index. For example, {1:"thread_id"} will store capture
413061da546Spatrick                group 1's content in the context dictionary where
414061da546Spatrick                "thread_id" is the key and the match group value is
415*be691f3bSpatrick                the value. This arg only makes sense when regex is specified.
416061da546Spatrick        """
417061da546Spatrick        self._is_send_to_remote = is_send_to_remote
418061da546Spatrick        self.exact_payload = exact_payload
419061da546Spatrick        self.regex = regex
420061da546Spatrick        self.capture = capture
421061da546Spatrick
422061da546Spatrick    def is_send_to_remote(self):
423061da546Spatrick        return self._is_send_to_remote
424061da546Spatrick
425061da546Spatrick    def is_consumed(self):
426061da546Spatrick        # For now, all packets are consumed after first use.
427061da546Spatrick        return True
428061da546Spatrick
429061da546Spatrick    def get_send_packet(self):
430061da546Spatrick        if not self.is_send_to_remote():
431061da546Spatrick            raise Exception(
432061da546Spatrick                "get_send_packet() called on GdbRemoteEntry that is not a send-to-remote packet")
433061da546Spatrick        if not self.exact_payload:
434061da546Spatrick            raise Exception(
435061da546Spatrick                "get_send_packet() called on GdbRemoteEntry but it doesn't have an exact payload")
436061da546Spatrick        return self.exact_payload
437061da546Spatrick
438061da546Spatrick    def _assert_exact_payload_match(self, asserter, actual_packet):
439061da546Spatrick        assert_packets_equal(asserter, actual_packet, self.exact_payload)
440061da546Spatrick        return None
441061da546Spatrick
442061da546Spatrick    def _assert_regex_match(self, asserter, actual_packet, context):
443061da546Spatrick        # Ensure the actual packet matches from the start of the actual packet.
444061da546Spatrick        match = self.regex.match(actual_packet)
445061da546Spatrick        if not match:
446061da546Spatrick            asserter.fail(
447061da546Spatrick                "regex '{}' failed to match against content '{}'".format(
448061da546Spatrick                    self.regex.pattern, actual_packet))
449061da546Spatrick
450061da546Spatrick        if self.capture:
451061da546Spatrick            # Handle captures.
452061da546Spatrick            for group_index, var_name in list(self.capture.items()):
453061da546Spatrick                capture_text = match.group(group_index)
454061da546Spatrick                # It is okay for capture text to be None - which it will be if it is a group that can match nothing.
455061da546Spatrick                # The user must be okay with it since the regex itself matched
456061da546Spatrick                # above.
457061da546Spatrick                context[var_name] = capture_text
458061da546Spatrick
459061da546Spatrick        return context
460061da546Spatrick
461061da546Spatrick    def assert_match(self, asserter, actual_packet, context=None):
462061da546Spatrick        # This only makes sense for matching lines coming from the
463061da546Spatrick        # remote debug monitor.
464061da546Spatrick        if self.is_send_to_remote():
465061da546Spatrick            raise Exception(
466061da546Spatrick                "Attempted to match a packet being sent to the remote debug monitor, doesn't make sense.")
467061da546Spatrick
468061da546Spatrick        # Create a new context if needed.
469061da546Spatrick        if not context:
470061da546Spatrick            context = {}
471061da546Spatrick
472061da546Spatrick        # If this is an exact payload, ensure they match exactly,
473061da546Spatrick        # ignoring the packet checksum which is optional for no-ack
474061da546Spatrick        # mode.
475061da546Spatrick        if self.exact_payload:
476061da546Spatrick            self._assert_exact_payload_match(asserter, actual_packet)
477061da546Spatrick            return context
478061da546Spatrick        elif self.regex:
479061da546Spatrick            return self._assert_regex_match(asserter, actual_packet, context)
480061da546Spatrick        else:
481061da546Spatrick            raise Exception(
482061da546Spatrick                "Don't know how to match a remote-sent packet when exact_payload isn't specified.")
483061da546Spatrick
484061da546Spatrick
485061da546Spatrickclass MultiResponseGdbRemoteEntry(GdbRemoteEntryBase):
486061da546Spatrick    """Represents a query/response style packet.
487061da546Spatrick
488061da546Spatrick    Assumes the first item is sent to the gdb remote.
489061da546Spatrick    An end sequence regex indicates the end of the query/response
490061da546Spatrick    packet sequence.  All responses up through (but not including) the
491061da546Spatrick    end response are stored in a context variable.
492061da546Spatrick
493061da546Spatrick    Settings accepted from params:
494061da546Spatrick
495061da546Spatrick        next_query or query: required.  The typical query packet without the $ prefix or #xx suffix.
496061da546Spatrick            If there is a special first packet to start the iteration query, see the
497061da546Spatrick            first_query key.
498061da546Spatrick
499061da546Spatrick        first_query: optional. If the first query requires a special query command, specify
500061da546Spatrick            it with this key.  Do not specify the $ prefix or #xx suffix.
501061da546Spatrick
502061da546Spatrick        append_iteration_suffix: defaults to False.  Specify True if the 0-based iteration
503061da546Spatrick            index should be appended as a suffix to the command.  e.g. qRegisterInfo with
504061da546Spatrick            this key set true will generate query packets of qRegisterInfo0, qRegisterInfo1,
505061da546Spatrick            etc.
506061da546Spatrick
507061da546Spatrick        end_regex: required. Specifies a compiled regex object that will match the full text
508061da546Spatrick            of any response that signals an end to the iteration.  It must include the
509061da546Spatrick            initial $ and ending #xx and must match the whole packet.
510061da546Spatrick
511061da546Spatrick        save_key: required.  Specifies the key within the context where an array will be stored.
512061da546Spatrick            Each packet received from the gdb remote that does not match the end_regex will get
513061da546Spatrick            appended to the array stored within the context at that key.
514061da546Spatrick
515061da546Spatrick        runaway_response_count: optional. Defaults to 10000. If this many responses are retrieved,
516061da546Spatrick            assume there is something wrong with either the response collection or the ending
517061da546Spatrick            detection regex and throw an exception.
518061da546Spatrick    """
519061da546Spatrick
520061da546Spatrick    def __init__(self, params):
521061da546Spatrick        self._next_query = params.get("next_query", params.get("query"))
522061da546Spatrick        if not self._next_query:
523061da546Spatrick            raise "either next_query or query key must be specified for MultiResponseGdbRemoteEntry"
524061da546Spatrick
525061da546Spatrick        self._first_query = params.get("first_query", self._next_query)
526061da546Spatrick        self._append_iteration_suffix = params.get(
527061da546Spatrick            "append_iteration_suffix", False)
528061da546Spatrick        self._iteration = 0
529061da546Spatrick        self._end_regex = params["end_regex"]
530061da546Spatrick        self._save_key = params["save_key"]
531061da546Spatrick        self._runaway_response_count = params.get(
532061da546Spatrick            "runaway_response_count", 10000)
533061da546Spatrick        self._is_send_to_remote = True
534061da546Spatrick        self._end_matched = False
535061da546Spatrick
536061da546Spatrick    def is_send_to_remote(self):
537061da546Spatrick        return self._is_send_to_remote
538061da546Spatrick
539061da546Spatrick    def get_send_packet(self):
540061da546Spatrick        if not self.is_send_to_remote():
541061da546Spatrick            raise Exception(
542061da546Spatrick                "get_send_packet() called on MultiResponseGdbRemoteEntry that is not in the send state")
543061da546Spatrick        if self._end_matched:
544061da546Spatrick            raise Exception(
545061da546Spatrick                "get_send_packet() called on MultiResponseGdbRemoteEntry but end of query/response sequence has already been seen.")
546061da546Spatrick
547061da546Spatrick        # Choose the first or next query for the base payload.
548061da546Spatrick        if self._iteration == 0 and self._first_query:
549061da546Spatrick            payload = self._first_query
550061da546Spatrick        else:
551061da546Spatrick            payload = self._next_query
552061da546Spatrick
553061da546Spatrick        # Append the suffix as needed.
554061da546Spatrick        if self._append_iteration_suffix:
555061da546Spatrick            payload += "%x" % self._iteration
556061da546Spatrick
557061da546Spatrick        # Keep track of the iteration.
558061da546Spatrick        self._iteration += 1
559061da546Spatrick
560061da546Spatrick        # Now that we've given the query packet, flip the mode to
561061da546Spatrick        # receive/match.
562061da546Spatrick        self._is_send_to_remote = False
563061da546Spatrick
564061da546Spatrick        # Return the result, converted to packet form.
565061da546Spatrick        return gdbremote_packet_encode_string(payload)
566061da546Spatrick
567061da546Spatrick    def is_consumed(self):
568061da546Spatrick        return self._end_matched
569061da546Spatrick
570061da546Spatrick    def assert_match(self, asserter, actual_packet, context=None):
571061da546Spatrick        # This only makes sense for matching lines coming from the remote debug
572061da546Spatrick        # monitor.
573061da546Spatrick        if self.is_send_to_remote():
574061da546Spatrick            raise Exception(
575061da546Spatrick                "assert_match() called on MultiResponseGdbRemoteEntry but state is set to send a query packet.")
576061da546Spatrick
577061da546Spatrick        if self._end_matched:
578061da546Spatrick            raise Exception(
579061da546Spatrick                "assert_match() called on MultiResponseGdbRemoteEntry but end of query/response sequence has already been seen.")
580061da546Spatrick
581061da546Spatrick        # Set up a context as needed.
582061da546Spatrick        if not context:
583061da546Spatrick            context = {}
584061da546Spatrick
585061da546Spatrick        # Check if the packet matches the end condition.
586061da546Spatrick        match = self._end_regex.match(actual_packet)
587061da546Spatrick        if match:
588061da546Spatrick            # We're done iterating.
589061da546Spatrick            self._end_matched = True
590061da546Spatrick            return context
591061da546Spatrick
592061da546Spatrick        # Not done iterating - save the packet.
593061da546Spatrick        context[self._save_key] = context.get(self._save_key, [])
594061da546Spatrick        context[self._save_key].append(actual_packet)
595061da546Spatrick
596061da546Spatrick        # Check for a runaway response cycle.
597061da546Spatrick        if len(context[self._save_key]) >= self._runaway_response_count:
598061da546Spatrick            raise Exception(
599061da546Spatrick                "runaway query/response cycle detected: %d responses captured so far. Last response: %s" %
600061da546Spatrick                (len(
601061da546Spatrick                    context[
602061da546Spatrick                        self._save_key]), context[
603061da546Spatrick                    self._save_key][
604061da546Spatrick                    -1]))
605061da546Spatrick
606061da546Spatrick        # Flip the mode to send for generating the query.
607061da546Spatrick        self._is_send_to_remote = True
608061da546Spatrick        return context
609061da546Spatrick
610061da546Spatrick
611061da546Spatrickclass MatchRemoteOutputEntry(GdbRemoteEntryBase):
612061da546Spatrick    """Waits for output from the debug monitor to match a regex or time out.
613061da546Spatrick
614061da546Spatrick    This entry type tries to match each time new gdb remote output is accumulated
615061da546Spatrick    using a provided regex.  If the output does not match the regex within the
616061da546Spatrick    given timeframe, the command fails the playback session.  If the regex does
617061da546Spatrick    match, any capture fields are recorded in the context.
618061da546Spatrick
619061da546Spatrick    Settings accepted from params:
620061da546Spatrick
621061da546Spatrick        regex: required. Specifies a compiled regex object that must either succeed
622061da546Spatrick            with re.match or re.search (see regex_mode below) within the given timeout
623061da546Spatrick            (see timeout_seconds below) or cause the playback to fail.
624061da546Spatrick
625061da546Spatrick        regex_mode: optional. Available values: "match" or "search". If "match", the entire
626061da546Spatrick            stub output as collected so far must match the regex.  If search, then the regex
627061da546Spatrick            must match starting somewhere within the output text accumulated thus far.
628061da546Spatrick            Default: "match" (i.e. the regex must match the entirety of the accumulated output
629061da546Spatrick            buffer, so unexpected text will generally fail the match).
630061da546Spatrick
631061da546Spatrick        capture: optional.  If specified, is a dictionary of regex match group indices (should start
632061da546Spatrick            with 1) to variable names that will store the capture group indicated by the
633061da546Spatrick            index. For example, {1:"thread_id"} will store capture group 1's content in the
634061da546Spatrick            context dictionary where "thread_id" is the key and the match group value is
635*be691f3bSpatrick            the value. This arg only makes sense when regex is specified.
636061da546Spatrick    """
637061da546Spatrick
638061da546Spatrick    def __init__(self, regex=None, regex_mode="match", capture=None):
639061da546Spatrick        self._regex = regex
640061da546Spatrick        self._regex_mode = regex_mode
641061da546Spatrick        self._capture = capture
642061da546Spatrick        self._matched = False
643061da546Spatrick
644061da546Spatrick        if not self._regex:
645061da546Spatrick            raise Exception("regex cannot be None")
646061da546Spatrick
647061da546Spatrick        if not self._regex_mode in ["match", "search"]:
648061da546Spatrick            raise Exception(
649061da546Spatrick                "unsupported regex mode \"{}\": must be \"match\" or \"search\"".format(
650061da546Spatrick                    self._regex_mode))
651061da546Spatrick
652061da546Spatrick    def is_output_matcher(self):
653061da546Spatrick        return True
654061da546Spatrick
655061da546Spatrick    def is_send_to_remote(self):
656061da546Spatrick        # This is always a "wait for remote" command.
657061da546Spatrick        return False
658061da546Spatrick
659061da546Spatrick    def is_consumed(self):
660061da546Spatrick        return self._matched
661061da546Spatrick
662061da546Spatrick    def assert_match(self, asserter, accumulated_output, context):
663061da546Spatrick        # Validate args.
664061da546Spatrick        if not accumulated_output:
665061da546Spatrick            raise Exception("accumulated_output cannot be none")
666061da546Spatrick        if not context:
667061da546Spatrick            raise Exception("context cannot be none")
668061da546Spatrick
669061da546Spatrick        # Validate that we haven't already matched.
670061da546Spatrick        if self._matched:
671061da546Spatrick            raise Exception(
672061da546Spatrick                "invalid state - already matched, attempting to match again")
673061da546Spatrick
674061da546Spatrick        # If we don't have any content yet, we don't match.
675061da546Spatrick        if len(accumulated_output) < 1:
676061da546Spatrick            return context
677061da546Spatrick
678061da546Spatrick        # Check if we match
679061da546Spatrick        if self._regex_mode == "match":
680061da546Spatrick            match = self._regex.match(accumulated_output)
681061da546Spatrick        elif self._regex_mode == "search":
682061da546Spatrick            match = self._regex.search(accumulated_output)
683061da546Spatrick        else:
684061da546Spatrick            raise Exception(
685061da546Spatrick                "Unexpected regex mode: {}".format(
686061da546Spatrick                    self._regex_mode))
687061da546Spatrick
688061da546Spatrick        # If we don't match, wait to try again after next $O content, or time
689061da546Spatrick        # out.
690061da546Spatrick        if not match:
691061da546Spatrick            # print("re pattern \"{}\" did not match against \"{}\"".format(self._regex.pattern, accumulated_output))
692061da546Spatrick            return context
693061da546Spatrick
694061da546Spatrick        # We do match.
695061da546Spatrick        self._matched = True
696061da546Spatrick        # print("re pattern \"{}\" matched against \"{}\"".format(self._regex.pattern, accumulated_output))
697061da546Spatrick
698061da546Spatrick        # Collect up any captures into the context.
699061da546Spatrick        if self._capture:
700061da546Spatrick            # Handle captures.
701061da546Spatrick            for group_index, var_name in list(self._capture.items()):
702061da546Spatrick                capture_text = match.group(group_index)
703061da546Spatrick                if not capture_text:
704061da546Spatrick                    raise Exception(
705061da546Spatrick                        "No content for group index {}".format(group_index))
706061da546Spatrick                context[var_name] = capture_text
707061da546Spatrick
708061da546Spatrick        return context
709061da546Spatrick
710061da546Spatrick
711061da546Spatrickclass GdbRemoteTestSequence(object):
712061da546Spatrick
713061da546Spatrick    _LOG_LINE_REGEX = re.compile(r'^.*(read|send)\s+packet:\s+(.+)$')
714061da546Spatrick
715061da546Spatrick    def __init__(self, logger):
716061da546Spatrick        self.entries = []
717061da546Spatrick        self.logger = logger
718061da546Spatrick
719*be691f3bSpatrick    def __len__(self):
720*be691f3bSpatrick        return len(self.entries)
721*be691f3bSpatrick
722061da546Spatrick    def add_log_lines(self, log_lines, remote_input_is_read):
723061da546Spatrick        for line in log_lines:
724061da546Spatrick            if isinstance(line, str):
725061da546Spatrick                # Handle log line import
726061da546Spatrick                # if self.logger:
727061da546Spatrick                #     self.logger.debug("processing log line: {}".format(line))
728061da546Spatrick                match = self._LOG_LINE_REGEX.match(line)
729061da546Spatrick                if match:
730061da546Spatrick                    playback_packet = match.group(2)
731061da546Spatrick                    direction = match.group(1)
732061da546Spatrick                    if _is_packet_lldb_gdbserver_input(
733061da546Spatrick                            direction, remote_input_is_read):
734061da546Spatrick                        # Handle as something to send to the remote debug monitor.
735061da546Spatrick                        # if self.logger:
736061da546Spatrick                        #     self.logger.info("processed packet to send to remote: {}".format(playback_packet))
737061da546Spatrick                        self.entries.append(
738061da546Spatrick                            GdbRemoteEntry(
739061da546Spatrick                                is_send_to_remote=True,
740061da546Spatrick                                exact_payload=playback_packet))
741061da546Spatrick                    else:
742061da546Spatrick                        # Log line represents content to be expected from the remote debug monitor.
743061da546Spatrick                        # if self.logger:
744061da546Spatrick                        #     self.logger.info("receiving packet from llgs, should match: {}".format(playback_packet))
745061da546Spatrick                        self.entries.append(
746061da546Spatrick                            GdbRemoteEntry(
747061da546Spatrick                                is_send_to_remote=False,
748061da546Spatrick                                exact_payload=playback_packet))
749061da546Spatrick                else:
750061da546Spatrick                    raise Exception(
751061da546Spatrick                        "failed to interpret log line: {}".format(line))
752061da546Spatrick            elif isinstance(line, dict):
753061da546Spatrick                entry_type = line.get("type", "regex_capture")
754061da546Spatrick                if entry_type == "regex_capture":
755061da546Spatrick                    # Handle more explicit control over details via dictionary.
756061da546Spatrick                    direction = line.get("direction", None)
757061da546Spatrick                    regex = line.get("regex", None)
758061da546Spatrick                    capture = line.get("capture", None)
759061da546Spatrick
760061da546Spatrick                    # Compile the regex.
761061da546Spatrick                    if regex and (isinstance(regex, str)):
762061da546Spatrick                        regex = re.compile(regex)
763061da546Spatrick
764061da546Spatrick                    if _is_packet_lldb_gdbserver_input(
765061da546Spatrick                            direction, remote_input_is_read):
766061da546Spatrick                        # Handle as something to send to the remote debug monitor.
767061da546Spatrick                        # if self.logger:
768061da546Spatrick                        #     self.logger.info("processed dict sequence to send to remote")
769061da546Spatrick                        self.entries.append(
770061da546Spatrick                            GdbRemoteEntry(
771061da546Spatrick                                is_send_to_remote=True,
772061da546Spatrick                                regex=regex,
773*be691f3bSpatrick                                capture=capture))
774061da546Spatrick                    else:
775061da546Spatrick                        # Log line represents content to be expected from the remote debug monitor.
776061da546Spatrick                        # if self.logger:
777061da546Spatrick                        #     self.logger.info("processed dict sequence to match receiving from remote")
778061da546Spatrick                        self.entries.append(
779061da546Spatrick                            GdbRemoteEntry(
780061da546Spatrick                                is_send_to_remote=False,
781061da546Spatrick                                regex=regex,
782*be691f3bSpatrick                                capture=capture))
783061da546Spatrick                elif entry_type == "multi_response":
784061da546Spatrick                    self.entries.append(MultiResponseGdbRemoteEntry(line))
785061da546Spatrick                elif entry_type == "output_match":
786061da546Spatrick
787061da546Spatrick                    regex = line.get("regex", None)
788061da546Spatrick                    # Compile the regex.
789061da546Spatrick                    if regex and (isinstance(regex, str)):
790061da546Spatrick                        regex = re.compile(regex, re.DOTALL)
791061da546Spatrick
792061da546Spatrick                    regex_mode = line.get("regex_mode", "match")
793061da546Spatrick                    capture = line.get("capture", None)
794061da546Spatrick                    self.entries.append(
795061da546Spatrick                        MatchRemoteOutputEntry(
796061da546Spatrick                            regex=regex,
797061da546Spatrick                            regex_mode=regex_mode,
798061da546Spatrick                            capture=capture))
799061da546Spatrick                else:
800061da546Spatrick                    raise Exception("unknown entry type \"%s\"" % entry_type)
801061da546Spatrick
802061da546Spatrick
803061da546Spatrickdef process_is_running(pid, unknown_value=True):
804061da546Spatrick    """If possible, validate that the given pid represents a running process on the local system.
805061da546Spatrick
806061da546Spatrick    Args:
807061da546Spatrick
808061da546Spatrick        pid: an OS-specific representation of a process id.  Should be an integral value.
809061da546Spatrick
810061da546Spatrick        unknown_value: value used when we cannot determine how to check running local
811061da546Spatrick        processes on the OS.
812061da546Spatrick
813061da546Spatrick    Returns:
814061da546Spatrick
815061da546Spatrick        If we can figure out how to check running process ids on the given OS:
816061da546Spatrick        return True if the process is running, or False otherwise.
817061da546Spatrick
818061da546Spatrick        If we don't know how to check running process ids on the given OS:
819061da546Spatrick        return the value provided by the unknown_value arg.
820061da546Spatrick    """
821061da546Spatrick    if not isinstance(pid, six.integer_types):
822061da546Spatrick        raise Exception(
823061da546Spatrick            "pid must be an integral type (actual type: %s)" % str(
824061da546Spatrick                type(pid)))
825061da546Spatrick
826061da546Spatrick    process_ids = []
827061da546Spatrick
828061da546Spatrick    if lldb.remote_platform:
829061da546Spatrick        # Don't know how to get list of running process IDs on a remote
830061da546Spatrick        # platform
831061da546Spatrick        return unknown_value
832061da546Spatrick    elif platform.system() in ['Darwin', 'Linux', 'FreeBSD', 'NetBSD']:
833061da546Spatrick        # Build the list of running process ids
834061da546Spatrick        output = subprocess.check_output(
835061da546Spatrick            "ps ax | awk '{ print $1; }'", shell=True).decode("utf-8")
836061da546Spatrick        text_process_ids = output.split('\n')[1:]
837061da546Spatrick        # Convert text pids to ints
838061da546Spatrick        process_ids = [int(text_pid)
839061da546Spatrick                       for text_pid in text_process_ids if text_pid != '']
840061da546Spatrick    elif platform.system() == 'Windows':
841061da546Spatrick        output = subprocess.check_output(
842061da546Spatrick            "for /f \"tokens=2 delims=,\" %F in ('tasklist /nh /fi \"PID ne 0\" /fo csv') do @echo %~F", shell=True).decode("utf-8")
843061da546Spatrick        text_process_ids = output.split('\n')[1:]
844061da546Spatrick        process_ids = [int(text_pid)
845061da546Spatrick                       for text_pid in text_process_ids if text_pid != '']
846061da546Spatrick    # elif {your_platform_here}:
847061da546Spatrick    #   fill in process_ids as a list of int type process IDs running on
848061da546Spatrick    #   the local system.
849061da546Spatrick    else:
850061da546Spatrick        # Don't know how to get list of running process IDs on this
851061da546Spatrick        # OS, so return the "don't know" value.
852061da546Spatrick        return unknown_value
853061da546Spatrick
854061da546Spatrick    # Check if the pid is in the process_ids
855061da546Spatrick    return pid in process_ids
856061da546Spatrick
857*be691f3bSpatrickdef _handle_output_packet_string(packet_contents):
858*be691f3bSpatrick    if (not packet_contents) or (len(packet_contents) < 1):
859*be691f3bSpatrick        return None
860*be691f3bSpatrick    elif packet_contents[0:1] != b"O":
861*be691f3bSpatrick        return None
862*be691f3bSpatrick    elif packet_contents == b"OK":
863*be691f3bSpatrick        return None
864061da546Spatrick    else:
865*be691f3bSpatrick        return binascii.unhexlify(packet_contents[1:])
866*be691f3bSpatrick
867*be691f3bSpatrickclass Server(object):
868*be691f3bSpatrick
869*be691f3bSpatrick    _GDB_REMOTE_PACKET_REGEX = re.compile(br'^\$([^\#]*)#[0-9a-fA-F]{2}')
870*be691f3bSpatrick
871*be691f3bSpatrick    class ChecksumMismatch(Exception):
872*be691f3bSpatrick        pass
873*be691f3bSpatrick
874*be691f3bSpatrick    def __init__(self, sock, proc = None):
875*be691f3bSpatrick        self._accumulated_output = b""
876*be691f3bSpatrick        self._receive_buffer = b""
877*be691f3bSpatrick        self._normal_queue = []
878*be691f3bSpatrick        self._output_queue = []
879*be691f3bSpatrick        self._sock = sock
880*be691f3bSpatrick        self._proc = proc
881*be691f3bSpatrick
882*be691f3bSpatrick    def send_raw(self, frame):
883*be691f3bSpatrick        self._sock.sendall(frame)
884*be691f3bSpatrick
885*be691f3bSpatrick    def send_ack(self):
886*be691f3bSpatrick        self.send_raw(b"+")
887*be691f3bSpatrick
888*be691f3bSpatrick    def send_packet(self, packet):
889*be691f3bSpatrick        self.send_raw(b'$%s#%02x'%(packet, self._checksum(packet)))
890*be691f3bSpatrick
891*be691f3bSpatrick    @staticmethod
892*be691f3bSpatrick    def _checksum(packet):
893*be691f3bSpatrick        checksum = 0
894*be691f3bSpatrick        for c in six.iterbytes(packet):
895*be691f3bSpatrick            checksum += c
896*be691f3bSpatrick        return checksum % 256
897*be691f3bSpatrick
898*be691f3bSpatrick    def _read(self, q):
899*be691f3bSpatrick        while not q:
900*be691f3bSpatrick            new_bytes = self._sock.recv(4096)
901*be691f3bSpatrick            self._process_new_bytes(new_bytes)
902*be691f3bSpatrick        return q.pop(0)
903*be691f3bSpatrick
904*be691f3bSpatrick    def _process_new_bytes(self, new_bytes):
905*be691f3bSpatrick        # Add new bytes to our accumulated unprocessed packet bytes.
906*be691f3bSpatrick        self._receive_buffer += new_bytes
907*be691f3bSpatrick
908*be691f3bSpatrick        # Parse fully-formed packets into individual packets.
909*be691f3bSpatrick        has_more = len(self._receive_buffer) > 0
910*be691f3bSpatrick        while has_more:
911*be691f3bSpatrick            if len(self._receive_buffer) <= 0:
912*be691f3bSpatrick                has_more = False
913*be691f3bSpatrick            # handle '+' ack
914*be691f3bSpatrick            elif self._receive_buffer[0:1] == b"+":
915*be691f3bSpatrick                self._normal_queue += [b"+"]
916*be691f3bSpatrick                self._receive_buffer = self._receive_buffer[1:]
917*be691f3bSpatrick            else:
918*be691f3bSpatrick                packet_match = self._GDB_REMOTE_PACKET_REGEX.match(
919*be691f3bSpatrick                    self._receive_buffer)
920*be691f3bSpatrick                if packet_match:
921*be691f3bSpatrick                    # Our receive buffer matches a packet at the
922*be691f3bSpatrick                    # start of the receive buffer.
923*be691f3bSpatrick                    new_output_content = _handle_output_packet_string(
924*be691f3bSpatrick                        packet_match.group(1))
925*be691f3bSpatrick                    if new_output_content:
926*be691f3bSpatrick                        # This was an $O packet with new content.
927*be691f3bSpatrick                        self._accumulated_output += new_output_content
928*be691f3bSpatrick                        self._output_queue += [self._accumulated_output]
929*be691f3bSpatrick                    else:
930*be691f3bSpatrick                        # Any packet other than $O.
931*be691f3bSpatrick                        self._normal_queue += [packet_match.group(0)]
932*be691f3bSpatrick
933*be691f3bSpatrick                    # Remove the parsed packet from the receive
934*be691f3bSpatrick                    # buffer.
935*be691f3bSpatrick                    self._receive_buffer = self._receive_buffer[
936*be691f3bSpatrick                        len(packet_match.group(0)):]
937*be691f3bSpatrick                else:
938*be691f3bSpatrick                    # We don't have enough in the receive bufferto make a full
939*be691f3bSpatrick                    # packet. Stop trying until we read more.
940*be691f3bSpatrick                    has_more = False
941*be691f3bSpatrick
942*be691f3bSpatrick    def get_raw_output_packet(self):
943*be691f3bSpatrick        return self._read(self._output_queue)
944*be691f3bSpatrick
945*be691f3bSpatrick    def get_raw_normal_packet(self):
946*be691f3bSpatrick        return self._read(self._normal_queue)
947*be691f3bSpatrick
948*be691f3bSpatrick    @staticmethod
949*be691f3bSpatrick    def _get_payload(frame):
950*be691f3bSpatrick        payload = frame[1:-3]
951*be691f3bSpatrick        checksum = int(frame[-2:], 16)
952*be691f3bSpatrick        if checksum != Server._checksum(payload):
953*be691f3bSpatrick            raise ChecksumMismatch
954*be691f3bSpatrick        return payload
955*be691f3bSpatrick
956*be691f3bSpatrick    def get_normal_packet(self):
957*be691f3bSpatrick        frame = self.get_raw_normal_packet()
958*be691f3bSpatrick        if frame == b"+": return frame
959*be691f3bSpatrick        return self._get_payload(frame)
960*be691f3bSpatrick
961*be691f3bSpatrick    def get_accumulated_output(self):
962*be691f3bSpatrick        return self._accumulated_output
963*be691f3bSpatrick
964*be691f3bSpatrick    def consume_accumulated_output(self):
965*be691f3bSpatrick        output = self._accumulated_output
966*be691f3bSpatrick        self._accumulated_output = b""
967*be691f3bSpatrick        return output
968*be691f3bSpatrick
969*be691f3bSpatrick    def __str__(self):
970*be691f3bSpatrick        return dedent("""\
971*be691f3bSpatrick            server '{}' on '{}'
972*be691f3bSpatrick            _receive_buffer: {}
973*be691f3bSpatrick            _normal_queue: {}
974*be691f3bSpatrick            _output_queue: {}
975*be691f3bSpatrick            _accumulated_output: {}
976*be691f3bSpatrick            """).format(self._proc, self._sock, self._receive_buffer,
977*be691f3bSpatrick                    self._normal_queue, self._output_queue,
978*be691f3bSpatrick                    self._accumulated_output)
979