| #!/usr/bin/env python |
| # |
| # Copyright (c) 2016, The OpenThread Authors. |
| # All rights reserved. |
| # |
| # Redistribution and use in source and binary forms, with or without |
| # modification, are permitted provided that the following conditions are met: |
| # 1. Redistributions of source code must retain the above copyright |
| # notice, this list of conditions and the following disclaimer. |
| # 2. Redistributions in binary form must reproduce the above copyright |
| # notice, this list of conditions and the following disclaimer in the |
| # documentation and/or other materials provided with the distribution. |
| # 3. Neither the name of the copyright holder nor the |
| # names of its contributors may be used to endorse or promote products |
| # derived from this software without specific prior written permission. |
| # |
| # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" |
| # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
| # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE |
| # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
| # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
| # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
| # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
| # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
| # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
| # POSSIBILITY OF SUCH DAMAGE. |
| # |
| |
| import ctypes |
| import os |
| import socket |
| import sys |
| |
| class SnifferTransport(object): |
| """ Interface for transport that allows eavesdrop other nodes. """ |
| |
| def open(self): |
| """ Open transport. |
| |
| Raises: |
| RuntimeError: when transport is already opened or when transport opening failed. |
| """ |
| raise NotImplementedError |
| |
| def close(self): |
| """ Close transport. |
| |
| Raises: |
| RuntimeError: when transport is already closed. |
| """ |
| raise NotImplementedError |
| |
| @property |
| def is_opened(self): |
| """ Check if transport is opened. |
| |
| Returns: |
| bool: True if the transport is opened, False in otherwise |
| """ |
| raise NotImplementedError |
| |
| def send(self, data, nodeid): |
| """ Send data to the node with nodeid. |
| |
| Args: |
| data (bytearray): outcoming data. |
| nodeid (int): node id |
| |
| Returns: |
| int: number of sent bytes |
| """ |
| raise NotImplementedError |
| |
| def recv(self, bufsize): |
| """ Receive data sent by other node. |
| |
| Args: |
| bufsize (int): size of buffer for incoming data. |
| |
| Returns: |
| A tuple contains data and node id. |
| |
| For example: |
| (bytearray([0x00, 0x01...], 1) |
| """ |
| raise NotImplementedError |
| |
| |
| class SnifferSocketTransport(SnifferTransport): |
| """ Socket based implementation of sniffer transport. """ |
| |
| BASE_PORT = 9000 |
| |
| WELLKNOWN_NODE_ID = 34 |
| |
| PORT_OFFSET = int(os.getenv('PORT_OFFSET', "0")) |
| |
| def __init__(self, nodeid): |
| self._nodeid = nodeid |
| self._socket = None |
| |
| def __del__(self): |
| if not self.is_opened: |
| return |
| |
| self.close() |
| |
| def _nodeid_to_address(self, nodeid, ip_address=""): |
| return ip_address, self.BASE_PORT + (self.PORT_OFFSET * self.WELLKNOWN_NODE_ID) + nodeid |
| |
| def _address_to_nodeid(self, address): |
| _, port = address |
| return (port - self.BASE_PORT - (self.PORT_OFFSET * self.WELLKNOWN_NODE_ID)) |
| |
| def open(self): |
| if self.is_opened: |
| raise RuntimeError("Transport is already opened.") |
| |
| self._socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) |
| |
| if not self.is_opened: |
| raise RuntimeError("Transport opening failed.") |
| |
| self._socket.bind(self._nodeid_to_address(self._nodeid)) |
| |
| def close(self): |
| if not self.is_opened: |
| raise RuntimeError("Transport is closed.") |
| |
| self._socket.close() |
| self._socket = None |
| |
| @property |
| def is_opened(self): |
| return bool(self._socket is not None) |
| |
| def send(self, data, nodeid): |
| address = self._nodeid_to_address(nodeid) |
| |
| return self._socket.sendto(data, address) |
| |
| def recv(self, bufsize): |
| data, address = self._socket.recvfrom(bufsize) |
| |
| nodeid = self._address_to_nodeid(address) |
| |
| return bytearray(data), nodeid |
| |
| |
| class MacFrame(ctypes.Structure): |
| _fields_ = [("buffer", ctypes.c_ubyte * 128), |
| ("length", ctypes.c_ubyte), |
| ("nodeid", ctypes.c_uint)] |
| |
| class SnifferVirtualTransport(SnifferTransport): |
| """ Virtual interface based implementation of sniffer transport. """ |
| |
| def __init__(self, nodeid): |
| self.Handle = None |
| |
| # Load the DLL |
| self.Api = ctypes.WinDLL("otnodeapi.dll") |
| if self.Api == None: |
| raise OSError("Failed to load otnodeapi.dll!") |
| |
| # Define the functions |
| self.Api.otListenerInit.argtypes = [ctypes.c_uint] |
| self.Api.otListenerInit.restype = ctypes.c_void_p |
| |
| self.Api.otListenerFinalize.argtypes = [ctypes.c_void_p] |
| |
| self.Api.otListenerRead.argtypes = [ctypes.c_void_p, ctypes.POINTER(MacFrame)] |
| |
| def __del__(self): |
| if not self.is_opened: |
| return |
| |
| self.close() |
| |
| def open(self): |
| if self.is_opened: |
| raise RuntimeError("Transport is already opened.") |
| |
| # Initialize a listener |
| self.Handle = self.Api.otListenerInit(0) |
| |
| if not self.is_opened: |
| raise RuntimeError("Transport opening failed.") |
| |
| def close(self): |
| if not self.is_opened: |
| raise RuntimeError("Transport is closed.") |
| |
| self.Api.otListenerFinalize(self.Handle); |
| self.Handle = None |
| |
| @property |
| def is_opened(self): |
| return bool(self.Handle is not None) |
| |
| def recv(self, bufsize): |
| frame = MacFrame() |
| pFrame = ctypes.pointer(frame); |
| |
| self.Api.otListenerRead(self.Handle, pFrame) |
| |
| return bytearray(frame.buffer)[:frame.length], frame.nodeid |
| |
| |
| class SnifferTransportFactory(object): |
| |
| def create_transport(self, nodeid): |
| if sys.platform != "win32": |
| return SnifferSocketTransport(nodeid) |
| |
| else: |
| return SnifferVirtualTransport(nodeid) |