# -*- coding: utf-8 -*-
# wasp_general/network/transport.py
#
# Copyright (C) 2016 the wasp-general authors and contributors
# <see AUTHORS file>
#
# This file is part of wasp-general.
#
# Wasp-general is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Wasp-general is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with wasp-general. If not, see <http://www.gnu.org/licenses/>.
# noinspection PyUnresolvedReferences
from wasp_general.version import __author__, __version__, __credits__, __license__, __copyright__, __email__
# noinspection PyUnresolvedReferences
from wasp_general.version import __status__
from abc import ABCMeta, abstractmethod
import socket
import struct
from wasp_general.verify import verify_type, verify_value
from wasp_general.config import WConfig
from wasp_general.network.primitives import WIPV4SocketInfo, WIPV4Address, WNetworkIPV4
[docs]class WNetworkNativeTransportProto(metaclass=ABCMeta):
""" This is interface for classes, that implement transport logic for network communication. "Native" means
that these classes use socket objects directly.
"""
[docs] @abstractmethod
@verify_type(config=WConfig)
def server_socket(self, config):
""" Return server socket. This socket is used for receiving requests and sending results. It is
important, that the result can be polled by a IOLoop instance.
:param config: server configuration
:return: socket.socket
"""
raise NotImplementedError('This method is abstract')
[docs] @abstractmethod
@verify_type(config=WConfig, close_fd=bool)
def close_server_socket(self, config, close_fd=True):
""" Close previously opened server socket. If no socket is opened - do nothing.
:param config: server configuration
:param close_fd: should this function close socket fd, or will it close by an external function?. It \
is safer to pass True here.
:return: None
"""
raise NotImplementedError('This method is abstract')
[docs] @abstractmethod
@verify_type(config=WConfig)
def client_socket(self, config):
""" Return client socket. This socket is used for sending request and receiving results. It is
important, that the result can be polled by a IOLoop instance.
:param config: client configuration
:return: socket.socket
"""
raise NotImplementedError('This method is abstract')
[docs] @abstractmethod
@verify_type(config=WConfig, close_fd=bool)
def close_client_socket(self, config, close_fd=True):
""" Close previously opened client socket. If no socket is opened - do nothing.
:param config: client configuration
:param close_fd: should this function close socket fd, or will it close by an external function?. It \
is safer to pass True here.
:return: None
"""
raise NotImplementedError('This method is abstract')
[docs] @verify_type(config=WConfig)
def target_socket(self, config):
""" Return socket information with server address. Mostly used for address validation.
:param config: client configuration
:return: WIPV4SocketInfo
"""
raise NotImplementedError('This method is abstract')
[docs] @verify_type(config=WConfig)
def bind_socket(self, config):
""" Return socket information with address that server binds to.
:param config: server configuration
:return: WIPV4SocketInfo
"""
raise NotImplementedError('This method is abstract')
[docs]class WNetworkNativeTransportSocketConfig:
""" Represent socket configuration settings.
"""
@verify_type(section=str, address_option=str, port_option=str)
@verify_value(section=lambda x: len(x) > 0, address_option=lambda x: len(x) > 0, port_option=lambda x: len(x) > 0)
def __init__(self, section, address_option, port_option):
""" Construct new configuration settings
:param section: section name
:param address_option: address option name
:param port_option: port option name
"""
self.section = section
self.address_option = address_option
self.port_option = port_option
[docs]class WNetworkNativeTransport(WNetworkNativeTransportProto, metaclass=ABCMeta):
""" Basic WNetworkNativeTransportProto implementation. This class isn't ready to use, but it has general
implementation for the most WNetworkNativeTransportProto methods.
"""
@verify_type(target_socket_config=WNetworkNativeTransportSocketConfig)
@verify_type(bind_socket_config=WNetworkNativeTransportSocketConfig)
def __init__(self, target_socket_config, bind_socket_config):
""" Create new transport
:param target_socket_config: configuration for client socket
:param bind_socket_config: configuration for server socket
"""
self.__target_socket_config = target_socket_config
self.__bind_socket_config = bind_socket_config
self.__server_socket = None
self.__client_socket = None
[docs] @verify_type(config=WConfig)
def target_socket(self, config):
""" :meth:`.WNetworkNativeTransportProto.server_socket` method implementation
"""
address = config[self.__target_socket_config.section][self.__target_socket_config.address_option]
port = config.getint(self.__target_socket_config.section, self.__target_socket_config.port_option)
target = WIPV4SocketInfo(address, port)
if target.address() is None or target.port() is None:
raise ValueError('Invalid target address or port')
return target
[docs] @verify_type(config=WConfig)
def bind_socket(self, config):
""" :meth:`.WNetworkNativeTransportProto.bind_socket` method implementation
"""
address = config[self.__bind_socket_config.section][self.__bind_socket_config.address_option]
port = config.getint(self.__bind_socket_config.section, self.__bind_socket_config.port_option)
return WIPV4SocketInfo(address, port)
@abstractmethod
def _create_socket(self):
""" Create general socket object, that can be used for client and/or server usage
:return: socket.socket
"""
raise NotImplementedError('This method is abstract')
[docs] @verify_type(config=WConfig)
def create_server_socket(self, config):
""" Create socket for server. (By default, same as WNetworkNativeTransport._create_socket)
:param config: server configuration
:return: socket.socket
"""
return self._create_socket()
[docs] @verify_type(config=WConfig)
def create_client_socket(self, config):
""" Create socket for client. (By default, same as WNetworkNativeTransport._create_socket)
:param config: client configuration
:return: socket.socket
"""
return self._create_socket()
[docs] @verify_type(config=WConfig)
def server_socket(self, config):
""" :meth:`.WNetworkNativeTransportProto.server_socket` method implementation
"""
if self.__server_socket is None:
self.__server_socket = self.create_server_socket(config)
self.__server_socket.bind(self.bind_socket(config).pair())
return self.__server_socket
[docs] @verify_type(config=WConfig, close_fd=bool)
def close_server_socket(self, config, close_fd=True):
""" :meth:`.WNetworkNativeTransportProto.close_server_socket` method implementation
"""
if close_fd is True:
self.__server_socket.close()
self.__server_socket = None
[docs] @verify_type(config=WConfig)
def client_socket(self, config):
""" :meth:`.WNetworkNativeTransportProto.client_socket` method implementation
"""
if self.__client_socket is None:
self.__client_socket = self.create_client_socket(config)
return self.__client_socket
[docs] @verify_type(config=WConfig, close_fd=bool)
def close_client_socket(self, config, close_fd=True):
""" :meth:`.WNetworkNativeTransportProto.close_client_socket` method implementation
"""
if close_fd is True:
self.__client_socket.close()
self.__client_socket = None
[docs]class WUDPNetworkNativeTransport(WNetworkNativeTransport, metaclass=ABCMeta):
""" Basic UDP transport implementation
"""
def _create_socket(self):
""" Create general UDP-socket
:return: socket.socket
"""
return socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
[docs]class WTCPNetworkNativeTransport(WNetworkNativeTransport):
""" Basic TCP transport implementation
"""
def _create_socket(self):
""" Create general TCP-socket
:return: socket.socket
"""
return socket.socket(socket.AF_INET, socket.SOCK_STREAM)
[docs]class WBroadcastNetworkTransport(WUDPNetworkNativeTransport):
""" Network transport, that uses IPv4 broadcast (UDP) communication
"""
@verify_type('paranoid', target_socket_config=WNetworkNativeTransportSocketConfig)
@verify_type('paranoid', bind_socket_config=WNetworkNativeTransportSocketConfig)
def __init__(self, target_socket_config, bind_socket_config):
""" Create new broadcast transport
"""
WNetworkNativeTransport.__init__(self, target_socket_config, bind_socket_config)
[docs] @verify_type('paranoid', config=WConfig)
def target_socket(self, config):
""" This method overrides :meth:`.WNetworkNativeTransport.target_socket` method. Do the same thing as
basic method do, but also checks that the result address is IPv4 address.
:param config: beacon configuration
:return: WIPV4SocketInfo
"""
target = WNetworkNativeTransport.target_socket(self, config)
if isinstance(target.address(), WIPV4Address) is False:
raise ValueError('Invalid address for broadcast transport')
return target
[docs] @verify_type('paranoid', config=WConfig)
def create_client_socket(self, config):
""" Create client broadcast socket
:param config: client configuration
:return: socket.socket
"""
client_socket = WUDPNetworkNativeTransport.create_client_socket(self, config)
client_socket.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
return client_socket
[docs]class WMulticastNetworkTransport(WUDPNetworkNativeTransport):
""" Network transport, that uses IPv4 multicast communication
"""
@verify_type('paranoid', target_socket_config=WNetworkNativeTransportSocketConfig)
@verify_type('paranoid', bind_socket_config=WNetworkNativeTransportSocketConfig)
def __init__(self, target_socket_config, bind_socket_config):
""" Create new multicast transport
"""
WUDPNetworkNativeTransport.__init__(self, target_socket_config, bind_socket_config)
[docs] @verify_type('paranoid', config=WConfig)
def target_socket(self, config):
""" This method overrides :meth:`.WNetworkNativeTransport.target_socket` method. Do the same thing as
basic method do, but also checks that the result address is IPv4 multicast address.
:param config: beacon configuration
:return: WIPV4SocketInfo
"""
target = WUDPNetworkNativeTransport.target_socket(self, config)
if WNetworkIPV4.is_multicast(target.address()) is False:
raise ValueError('IP multicast address not RFC compliant')
return target
[docs] @verify_type('paranoid', config=WConfig)
def create_server_socket(self, config):
""" Create server multicast socket. Socket will be joined to the multicast-group (same as it is
specified in client configuration, same as client does)
:param config: server configuration
:return: socket.socket
"""
server_socket = WUDPNetworkNativeTransport.create_server_socket(self, config)
group = socket.inet_aton(str(self.target_socket(config).address()))
group_membership = struct.pack('4sL', group, socket.INADDR_ANY)
server_socket.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, group_membership)
return server_socket