1#!/usr/bin/env python3
2#
3#  Copyright (c) 2016, The OpenThread Authors.
4#  All rights reserved.
5#
6#  Redistribution and use in source and binary forms, with or without
7#  modification, are permitted provided that the following conditions are met:
8#  1. Redistributions of source code must retain the above copyright
9#     notice, this list of conditions and the following disclaimer.
10#  2. Redistributions in binary form must reproduce the above copyright
11#     notice, this list of conditions and the following disclaimer in the
12#     documentation and/or other materials provided with the distribution.
13#  3. Neither the name of the copyright holder nor the
14#     names of its contributors may be used to endorse or promote products
15#     derived from this software without specific prior written permission.
16#
17#  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
18#  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19#  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20#  ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
21#  LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
22#  CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
23#  SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
24#  INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
25#  CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
26#  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27#  POSSIBILITY OF SUCH DAMAGE.
28#
29
30import collections
31import io
32import logging
33import os
34import sys
35import time
36
37import pcap
38import threading
39import traceback
40
41try:
42    import Queue
43except ImportError:
44    import queue as Queue
45
46import message
47import sniffer_transport
48
49
50class Sniffer:
51    """ Class representing the Sniffing node, whose main task is listening
52        and logging message exchange performed by other nodes.
53    """
54
55    logger = logging.getLogger("sniffer.Sniffer")
56
57    RECV_BUFFER_SIZE = 4096
58
59    def __init__(self, message_factory):
60        """
61        Args:
62            message_factory (MessageFactory): Class producing messages from data bytes.
63        """
64
65        self._message_factory = message_factory
66
67        self._pcap = pcap.PcapCodec(os.getenv('TEST_NAME', 'current'))
68        if __name__ == '__main__':
69            sys.stdout.buffer.write(self._pcap.encode_header())
70            sys.stdout.buffer.flush()
71
72        # Create transport
73        transport_factory = sniffer_transport.SnifferTransportFactory()
74        self._transport = transport_factory.create_transport()
75
76        self._thread = None
77        self._thread_alive = threading.Event()
78        self._thread_alive.clear()
79
80        self._buckets = collections.defaultdict(Queue.Queue)
81
82    def _sniffer_main_loop(self):
83        """ Sniffer main loop. """
84
85        self.logger.debug("Sniffer started.")
86
87        while self._thread_alive.is_set():
88            data, nodeid = self._transport.recv(self.RECV_BUFFER_SIZE)
89
90            pkt = self._pcap.append(data)
91            if __name__ == '__main__':
92                try:
93                    sys.stdout.buffer.write(pkt)
94                    sys.stdout.flush()
95                except BrokenPipeError:
96                    self._thread_alive.clear()
97                    break
98
99            # Ignore any exceptions
100            if self._message_factory is not None:
101                try:
102                    messages = self._message_factory.create(io.BytesIO(data))
103                    self.logger.debug("Received messages: {}".format(messages))
104                    for msg in messages:
105                        self._buckets[nodeid].put(msg)
106
107                except Exception as e:
108                    # Just print the exception to the console
109                    self.logger.error("EXCEPTION: %s" % e)
110                    traceback.print_exc()
111
112        self.logger.debug("Sniffer stopped.")
113
114    def start(self):
115        """ Start sniffing. """
116
117        self._thread = threading.Thread(target=self._sniffer_main_loop)
118        self._thread.daemon = True
119
120        self._transport.open()
121
122        self._thread_alive.set()
123        self._thread.start()
124
125    def stop(self):
126        """ Stop sniffing. """
127
128        self._thread_alive.clear()
129
130        self._transport.close()
131
132        self._thread.join(timeout=1)
133        self._thread = None
134
135    def set_lowpan_context(self, cid, prefix):
136        self._message_factory.set_lowpan_context(cid, prefix)
137
138    def get_messages_sent_by(self, nodeid):
139        """ Get sniffed messages.
140
141        Note! This method flushes the message queue so calling this
142        method again will return only the newly logged messages.
143
144        Args:
145            nodeid (int): node id
146
147        Returns:
148            MessagesSet: a set with received messages.
149        """
150        bucket = self._buckets[nodeid]
151        messages = []
152
153        while not bucket.empty():
154            messages.append(bucket.get_nowait())
155
156        return message.MessagesSet(messages)
157
158
159def run_sniffer():
160    sniffer = Sniffer(None)
161    sniffer.start()
162    while sniffer._thread_alive.is_set():
163        try:
164            time.sleep(1)
165        except KeyboardInterrupt:
166            break
167
168    sniffer.stop()
169
170
171if __name__ == '__main__':
172    run_sniffer()
173