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