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 ctypes
31import os
32import socket
33
34
35class SnifferTransport(object):
36    """ Interface for transport that allows eavesdrop other nodes. """
37
38    def open(self):
39        """ Open transport.
40
41        Raises:
42            RuntimeError: when transport is already opened or when transport opening failed.
43        """
44        raise NotImplementedError
45
46    def close(self):
47        """ Close transport.
48
49        Raises:
50            RuntimeError: when transport is already closed.
51        """
52        raise NotImplementedError
53
54    @property
55    def is_opened(self):
56        """ Check if transport is opened.
57
58        Returns:
59            bool: True if the transport is opened, False in otherwise
60        """
61        raise NotImplementedError
62
63    def send(self, data, nodeid):
64        """ Send data to the node with nodeid.
65
66        Args:
67            data (bytearray): outcoming data.
68            nodeid (int): node id
69
70        Returns:
71            int: number of sent bytes
72        """
73        raise NotImplementedError
74
75    def recv(self, bufsize):
76        """ Receive data sent by other node.
77
78        Args:
79            bufsize (int): size of buffer for incoming data.
80
81        Returns:
82            A tuple contains data and node id.
83
84            For example:
85            (bytearray([0x00, 0x01...], 1)
86        """
87        raise NotImplementedError
88
89
90class SnifferSocketTransport(SnifferTransport):
91    """ Socket based implementation of sniffer transport. """
92
93    BASE_PORT = 9000
94
95    MAX_NETWORK_SIZE = 33
96
97    PORT_OFFSET = int(os.getenv('PORT_OFFSET', "0"))
98
99    RADIO_GROUP = '224.0.0.116'
100
101    def __init__(self):
102        self._socket = None
103
104    def __del__(self):
105        if not self.is_opened:
106            return
107
108        self.close()
109
110    def _nodeid_to_address(self, nodeid, ip_address=''):
111        return (
112            ip_address,
113            self.BASE_PORT + (self.PORT_OFFSET * (self.MAX_NETWORK_SIZE + 1)) + nodeid,
114        )
115
116    def _address_to_nodeid(self, address):
117        _, port = address
118        return (port - self.BASE_PORT - (self.PORT_OFFSET * (self.MAX_NETWORK_SIZE + 1)))
119
120    def open(self):
121        if self.is_opened:
122            raise RuntimeError("Transport is already opened.")
123
124        self._socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
125
126        if not self.is_opened:
127            raise RuntimeError("Transport opening failed.")
128
129        self._socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
130        self._socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
131        self._socket.setsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF, 2 * 1024 * 1024)
132        self._socket.setsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF, 2 * 1024 * 1024)
133        self._socket.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP,
134                                socket.inet_aton(self.RADIO_GROUP) + socket.inet_aton('127.0.0.1'))
135        self._socket.bind(self._nodeid_to_address(0))
136
137    def close(self):
138        if not self.is_opened:
139            raise RuntimeError("Transport is closed.")
140
141        self._socket.close()
142        self._socket = None
143
144    @property
145    def is_opened(self):
146        return bool(self._socket is not None)
147
148    def send(self, data, nodeid):
149        address = self._nodeid_to_address(nodeid)
150
151        return self._socket.sendto(data, address)
152
153    def recv(self, bufsize):
154        data, address = self._socket.recvfrom(bufsize)
155
156        nodeid = self._address_to_nodeid(address)
157
158        return bytearray(data), nodeid
159
160
161class MacFrame(ctypes.Structure):
162    _fields_ = [
163        ("buffer", ctypes.c_ubyte * 128),
164        ("length", ctypes.c_ubyte),
165        ("nodeid", ctypes.c_uint),
166    ]
167
168
169class SnifferTransportFactory(object):
170
171    def create_transport(self):
172        return SnifferSocketTransport()
173