2020-01-26 16:14:16 +01:00
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
"""
socket_protocol ( Socket Protocol )
== == == == == == == == == == == == == == == == =
* * Author : * *
* Dirk Alders < sudo - dirk @mount - mockery . de >
* * Description : * *
This Module supports point to point communication for client - server issues .
* * Submodules : * *
* : class : ` socket_protocol . pure_json_protocol `
2021-01-01 20:16:12 +01:00
* : class : ` socket_protocol . struct_json_protocol `
2020-01-26 16:14:16 +01:00
* * Unittest : * *
2021-01-01 20:16:12 +01:00
See also the : download : ` unittest < . . / pylibs / socket_protocol / _testresults_ / unittest . pdf > ` documentation .
* * Module Documentation : * *
2020-01-26 16:14:16 +01:00
"""
__DEPENDENCIES__ = [ ' stringtools ' ]
import stringtools
import binascii
import hashlib
import json
import logging
import os
import struct
import sys
import time
2020-12-21 22:34:26 +01:00
try :
from config import APP_NAME as ROOT_LOGGER_NAME
except ImportError :
ROOT_LOGGER_NAME = ' root '
logger = logging . getLogger ( ROOT_LOGGER_NAME ) . getChild ( __name__ )
2020-01-26 16:14:16 +01:00
2021-01-01 20:16:12 +01:00
__DESCRIPTION__ = """ The Module { \\ tt %s } is designed for point to point communication for client-server issues.
2020-01-26 16:14:16 +01:00
For more Information read the sphinx documentation . """ % __name__.replace( ' _ ' , ' \ _ ' )
""" The Module Description """
__INTERPRETER__ = ( 2 , 3 )
""" The Tested Interpreter-Versions """
2021-01-01 20:16:12 +01:00
class _callback_storage ( dict ) :
2020-12-25 17:01:16 +01:00
DEFAULT_CHANNEL_NAME = ' all_others '
def __init__ ( self , channel_name ) :
self . init_channel_name ( channel_name )
2020-01-26 16:14:16 +01:00
dict . __init__ ( self )
2020-12-25 17:01:16 +01:00
def init_channel_name ( self , channel_name ) :
if channel_name is None :
self . logger = logging . getLogger ( ROOT_LOGGER_NAME ) . getChild ( __name__ + ' . ' + self . DEFAULT_CHANNEL_NAME )
else :
self . logger = logging . getLogger ( ROOT_LOGGER_NAME ) . getChild ( __name__ + ' . ' + channel_name )
2020-01-26 16:14:16 +01:00
def get ( self , service_id , data_id ) :
if service_id is not None and data_id is not None :
try :
return self [ service_id ] [ data_id ]
except KeyError :
pass # nothing to append
if data_id is not None :
try :
return self [ None ] [ data_id ]
except KeyError :
pass # nothing to append
if service_id is not None :
try :
return self [ service_id ] [ None ]
except KeyError :
pass # nothing to append
try :
return self [ None ] [ None ]
except KeyError :
pass # nothing to append
2020-12-21 22:34:26 +01:00
return ( None , None , None )
2020-01-26 16:14:16 +01:00
2020-09-20 19:24:45 +02:00
def add ( self , service_id , data_id , callback , * args , * * kwargs ) :
2020-12-25 17:01:16 +01:00
cb_data = self . get ( service_id , data_id )
if cb_data != ( None , None , None ) :
self . logger . warning ( " Overwriting existing callback %s for service_id ( %s ) and data_id ( %s ) to %s ! " , repr ( cb_data [ 0 ] . __name__ ) , repr ( service_id ) , repr ( data_id ) , repr ( callback . __name__ ) )
2020-01-26 16:14:16 +01:00
if service_id not in self :
self [ service_id ] = { }
2020-09-20 19:24:45 +02:00
self [ service_id ] [ data_id ] = ( callback , args , kwargs )
2020-01-26 16:14:16 +01:00
class data_storage ( dict ) :
2021-01-01 20:16:12 +01:00
"""
: param status : The message status .
: type status : int
: param service_id : The Service - ID .
: type service_id : int
: param data_id : The Data - ID .
: type data_id : int
: param data : The transfered data .
: type data : any
This is a storage object for socket_protocol messages .
"""
2020-01-26 16:14:16 +01:00
KEY_STATUS = ' status '
KEY_SERVICE_ID = ' service_id '
KEY_DATA_ID = ' data_id '
KEY_DATA = ' data '
def __init__ ( self , * args , * * kwargs ) :
dict . __init__ ( self , * args , * * kwargs )
def get_status ( self , default = None ) :
2021-01-01 20:16:12 +01:00
"""
: param default : The default value , if no data is available .
This Method returns the message status .
"""
2020-01-26 16:14:16 +01:00
return self . get ( self . KEY_STATUS , default )
def get_service_id ( self , default = None ) :
2021-01-01 20:16:12 +01:00
"""
: param default : The default value , if no data is available .
This Method returns the message Service - ID .
"""
2020-01-26 16:14:16 +01:00
return self . get ( self . KEY_SERVICE_ID , default )
def get_data_id ( self , default = None ) :
2021-01-01 20:16:12 +01:00
"""
: param default : The default value , if no data is available .
This Method returns the message Data - ID .
"""
2020-01-26 16:14:16 +01:00
return self . get ( self . KEY_DATA_ID , default )
def get_data ( self , default = None ) :
2021-01-01 20:16:12 +01:00
"""
: param default : The default value , if no data is available .
This Method returns the message data .
"""
2020-01-26 16:14:16 +01:00
return self . get ( self . KEY_DATA , default )
2021-01-01 20:16:12 +01:00
class pure_json_protocol ( object ) :
2020-01-26 16:14:16 +01:00
"""
2021-01-01 20:16:12 +01:00
: param comm_instance : A communication instance .
2020-01-26 16:14:16 +01:00
: type comm_instance : instance
2021-01-01 20:16:12 +01:00
: param secret : An optinal secret ( e . g . created by ` ` binascii . hexlify ( os . urandom ( 24 ) ) ` ` ) .
2020-01-26 16:14:16 +01:00
: type secret : str
2021-01-01 20:16:12 +01:00
: param auto_auth : An optional parameter ( True ) to enable automatic authentification , otherwise you need to do it manually , if needed .
: type auto_auth : bool
: param channel_name : An optional parameter to set a channel name for logging of the communication .
: type channel_name : str
2020-01-26 16:14:16 +01:00
2021-01-01 20:16:12 +01:00
. . hint : : This ` class ` supports to transfer a Service - ID , Data - ID and Data .
2020-01-26 16:14:16 +01:00
2021-01-01 20:16:12 +01:00
* The Service - ID is designed to identify the type of the communication ( e . g . READ_REQUEST , WRITE_REQUEST , READ_RESPONSE , WRITE_RESPONSE , . . . )
* The Data - ID is designed to identify the requests / responses using the same Service_ID .
2020-01-26 16:14:16 +01:00
2021-01-01 20:16:12 +01:00
. . note : : The : class : ` comm_instance ` needs to have at least the following interface :
2020-01-26 16:14:16 +01:00
2021-01-01 20:16:12 +01:00
* A Method : func : ` comm_instance . init_channel_name ` to set the channel name if needed .
* A Constant : const : ` comm_instance . IS_CLIENT ` to identify that the : class : ` comm_instance ` is a client ( True ) or a server ( False ) .
* A Method : func : ` comm_instance . is_connected ` to identify if the instance is connected ( True ) or not ( False ) .
* A Method : func : ` comm_instance . reconnect ` to initiate a reconnect .
* A Method : func : ` comm_instance . register_callback ` to register a data available callback .
* A Method : func : ` comm_instance . register_connect_callback ` to register a connect callback .
* A Method : func : ` comm_instance . register_disconnect_callback ` to register a disconnect callback .
* A Method : func : ` comm_instance . send ` to send data via the : class : ` comm_instance ` .
2020-01-26 16:14:16 +01:00
2021-01-01 20:16:12 +01:00
. . note : : The parameter : const : ` auto_auth ` is only relevant , if a secret is given and the : class : ` comm_instance ` is a client . The authentification is initiated directly after the connection is established .
2020-01-26 16:14:16 +01:00
2021-01-01 20:16:12 +01:00
. . note : : The : const : ` channel_name ` - exchange will be initiated by the client directly after the the connection is established .
2020-01-26 16:14:16 +01:00
2021-01-01 20:16:12 +01:00
* If a channel_name is given at both communication sides and they are different , the client name is taken over and the server will log a warning message .
2020-01-26 16:14:16 +01:00
"""
2020-12-25 17:01:16 +01:00
DEFAULT_CHANNEL_NAME = ' all_others '
2020-01-26 16:14:16 +01:00
SID_AUTH_SEED_REQUEST = 1
2021-01-01 20:16:12 +01:00
""" SID for requesting a seed for authentification """
2020-01-26 16:14:16 +01:00
SID_AUTH_KEY_REQUEST = 2
2021-01-01 20:16:12 +01:00
""" SID for requesting a key for the given seed """
2020-01-26 16:14:16 +01:00
SID_AUTH_KEY_CHECK_REQUEST = 3
2021-01-01 20:16:12 +01:00
""" SID for request for checking a key """
2020-01-26 16:14:16 +01:00
SID_AUTH_KEY_CHECK_RESPONSE = 4
2021-01-01 20:16:12 +01:00
""" SID for the authentification response """
2020-12-25 17:01:16 +01:00
SID_CHANNEL_NAME_REQUEST = 5
2021-01-01 20:16:12 +01:00
""" SID for requesting a channel name exchange """
2020-12-25 17:01:16 +01:00
SID_CHANNEL_NAME_RESPONSE = 6
2021-01-01 20:16:12 +01:00
""" SID for the channel name response """
2020-01-26 16:14:16 +01:00
SID_READ_REQUEST = 10
2021-01-01 20:16:12 +01:00
""" SID for a read data request """
2020-01-26 16:14:16 +01:00
SID_READ_RESPONSE = 11
2021-01-01 20:16:12 +01:00
""" SID for read data response """
2020-01-26 16:14:16 +01:00
SID_WRITE_REQUEST = 20
2021-01-01 20:16:12 +01:00
""" SID for a write data request """
2020-01-26 16:14:16 +01:00
SID_WRITE_RESPONSE = 21
2021-01-01 20:16:12 +01:00
""" SID for a write data response """
2020-01-26 16:14:16 +01:00
SID_EXECUTE_REQUEST = 30
2021-01-01 20:16:12 +01:00
""" SID for a execute request """
2020-01-26 16:14:16 +01:00
SID_EXECUTE_RESPONSE = 31
2021-01-01 20:16:12 +01:00
""" SID for a execute response """
SID__RESPONSE_DICT = { SID_AUTH_SEED_REQUEST : SID_AUTH_KEY_REQUEST ,
SID_AUTH_KEY_REQUEST : SID_AUTH_KEY_CHECK_REQUEST ,
SID_AUTH_KEY_CHECK_REQUEST : SID_AUTH_KEY_CHECK_RESPONSE ,
SID_CHANNEL_NAME_REQUEST : SID_CHANNEL_NAME_RESPONSE ,
SID_READ_REQUEST : SID_READ_RESPONSE ,
SID_WRITE_REQUEST : SID_WRITE_RESPONSE ,
SID_EXECUTE_REQUEST : SID_EXECUTE_RESPONSE }
""" Dictionary to get the SID for the response by the key which is the SID for the request """
SID__NO_AUTH_LIST = [
2020-12-25 21:05:55 +01:00
SID_AUTH_SEED_REQUEST ,
SID_AUTH_KEY_REQUEST ,
2021-01-01 20:16:12 +01:00
SID_AUTH_KEY_CHECK_REQUEST ,
2020-12-25 21:05:55 +01:00
SID_AUTH_KEY_CHECK_RESPONSE ,
SID_CHANNEL_NAME_REQUEST ,
SID_CHANNEL_NAME_RESPONSE
]
2021-01-01 20:16:12 +01:00
""" List of SIDs without need of an authentification """
2020-01-26 16:14:16 +01:00
STATUS_OKAY = 0
2021-01-01 20:16:12 +01:00
""" Status for ' okay ' """
2020-01-26 16:14:16 +01:00
STATUS_BUFFERING_UNHANDLED_REQUEST = 1
2021-01-01 20:16:12 +01:00
""" Status for ' unhandled request ' """
2020-01-26 16:14:16 +01:00
STATUS_AUTH_REQUIRED = 2
2021-01-01 20:16:12 +01:00
""" Status for ' authentification is required ' """
2020-01-26 16:14:16 +01:00
STATUS_SERVICE_OR_DATA_UNKNOWN = 3
2021-01-01 20:16:12 +01:00
""" Status for ' service or data unknown ' """
2020-01-26 16:14:16 +01:00
STATUS_CHECKSUM_ERROR = 4
2021-01-01 20:16:12 +01:00
""" Status for ' checksum error ' """
2020-01-26 16:14:16 +01:00
STATUS_OPERATION_NOT_PERMITTED = 5
2021-01-01 20:16:12 +01:00
""" Status for ' operation not permitted ' """
STATUS__NAMES = { STATUS_OKAY : ' Okay ' ,
STATUS_BUFFERING_UNHANDLED_REQUEST : ' Request has no callback. Data buffered. ' ,
STATUS_AUTH_REQUIRED : ' Authentification required ' ,
STATUS_SERVICE_OR_DATA_UNKNOWN : ' Service or Data unknown ' ,
STATUS_CHECKSUM_ERROR : ' Checksum Error ' ,
STATUS_OPERATION_NOT_PERMITTED : ' Operation not permitted ' }
""" Status names for previous defined states """
2020-01-26 16:14:16 +01:00
AUTH_STATE_UNKNOWN_CLIENT = 0
2021-01-01 20:16:12 +01:00
""" Authentification Status for ' Unknown Client ' """
2020-01-26 16:14:16 +01:00
AUTH_STATE_SEED_REQUESTED = 1
2021-01-01 20:16:12 +01:00
""" Authentification Status for ' Seed was requested ' """
2020-01-26 16:14:16 +01:00
AUTH_STATE_SEED_TRANSFERRED = 2
2021-01-01 20:16:12 +01:00
""" Authentification Status for ' Seed has been sent ' """
2020-01-26 16:14:16 +01:00
AUTH_STATE_KEY_TRANSFERRED = 3
2021-01-01 20:16:12 +01:00
""" Authentification Status for ' Key has been sent ' """
2020-01-26 16:14:16 +01:00
AUTH_STATE_TRUSTED_CLIENT = 4
2021-01-01 20:16:12 +01:00
""" Authentification Status for ' Trusted Connection ' """
AUTH_STATE__NAMES = { AUTH_STATE_UNKNOWN_CLIENT : ' Unknown Client ' ,
2020-01-26 16:14:16 +01:00
AUTH_STATE_SEED_REQUESTED : ' Seed was requested ' ,
AUTH_STATE_SEED_TRANSFERRED : ' Seed has been sent ' ,
AUTH_STATE_KEY_TRANSFERRED : ' Key has been sent ' ,
2021-01-01 20:16:12 +01:00
AUTH_STATE_TRUSTED_CLIENT : ' Trusted Connection ' }
""" Authentification Status names for previous defined authentification states """
2020-01-26 16:14:16 +01:00
2020-12-25 17:01:16 +01:00
def __init__ ( self , comm_instance , secret = None , auto_auth = False , channel_name = None ) :
self . __comm_inst__ = comm_instance
2020-01-26 16:14:16 +01:00
self . __secret__ = secret
2020-09-10 21:55:01 +02:00
self . __auto_auth__ = auto_auth
2020-12-25 17:01:16 +01:00
#
2021-01-01 20:16:12 +01:00
self . __callbacks__ = _callback_storage ( channel_name )
2020-12-25 17:01:16 +01:00
self . __init_channel_name__ ( channel_name )
#
2020-01-26 16:14:16 +01:00
self . __clean_receive_buffer__ ( )
self . __callbacks__ . add ( self . SID_AUTH_SEED_REQUEST , 0 , self . __authentificate_create_seed__ )
self . __callbacks__ . add ( self . SID_AUTH_KEY_REQUEST , 0 , self . __authentificate_create_key__ )
self . __callbacks__ . add ( self . SID_AUTH_KEY_CHECK_REQUEST , 0 , self . __authentificate_check_key__ )
self . __callbacks__ . add ( self . SID_AUTH_KEY_CHECK_RESPONSE , 0 , self . __authentificate_process_feedback__ )
2020-12-25 17:01:16 +01:00
self . __callbacks__ . add ( self . SID_CHANNEL_NAME_REQUEST , 0 , self . __channel_name_request__ )
self . __callbacks__ . add ( self . SID_CHANNEL_NAME_RESPONSE , 0 , self . __channel_name_response__ )
2020-01-26 16:14:16 +01:00
self . __authentification_state_reset__ ( )
self . __seed__ = None
self . __comm_inst__ . register_callback ( self . __data_available_callback__ )
2020-09-10 21:55:01 +02:00
self . __comm_inst__ . register_connect_callback ( self . __connection_established__ )
2020-01-26 16:14:16 +01:00
self . __comm_inst__ . register_disconnect_callback ( self . __authentification_state_reset__ )
2021-01-01 20:16:12 +01:00
def __analyse_frame__ ( self , frame ) :
if sys . version_info > = ( 3 , 0 ) :
return data_storage ( json . loads ( frame [ : - 4 ] . decode ( ' utf-8 ' ) ) )
2020-12-25 17:01:16 +01:00
else :
2021-01-01 20:16:12 +01:00
return data_storage ( json . loads ( frame [ : - 4 ] ) )
def __authentificate_check_key__ ( self , msg ) :
key = msg . get_data ( )
if key == self . __authentificate_salt_and_hash__ ( self . __seed__ ) :
self . __authentification_state__ = self . AUTH_STATE_TRUSTED_CLIENT
self . logger . info ( " %s Got correct key, sending positive authentification feedback " , self . __log_prefix__ ( ) )
return self . STATUS_OKAY , True
else :
self . __authentification_state__ = self . AUTH_STATE_UNKNOWN_CLIENT
self . logger . info ( " %s Got incorrect key, sending negative authentification feedback " , self . __log_prefix__ ( ) )
return self . STATUS_OKAY , False
def __authentificate_create_key__ ( self , msg ) :
self . logger . info ( " %s Got seed, sending key for authentification " , self . __log_prefix__ ( ) )
self . __authentification_state__ = self . AUTH_STATE_KEY_TRANSFERRED
seed = msg . get_data ( )
key = self . __authentificate_salt_and_hash__ ( seed )
return self . STATUS_OKAY , key
def __authentificate_create_seed__ ( self , msg ) :
self . logger . info ( " %s Got seed request, sending seed for authentification " , self . __log_prefix__ ( ) )
self . __authentification_state__ = self . AUTH_STATE_SEED_TRANSFERRED
if sys . version_info > = ( 3 , 0 ) :
self . __seed__ = binascii . hexlify ( os . urandom ( 32 ) ) . decode ( ' utf-8 ' )
else :
self . __seed__ = binascii . hexlify ( os . urandom ( 32 ) )
return self . STATUS_OKAY , self . __seed__
def __authentificate_process_feedback__ ( self , msg ) :
feedback = msg . get_data ( )
if feedback :
self . __authentification_state__ = self . AUTH_STATE_TRUSTED_CLIENT
self . logger . info ( " %s Got positive authentification feedback " , self . __log_prefix__ ( ) )
else :
self . __authentification_state__ = self . AUTH_STATE_UNKNOWN_CLIENT
self . logger . warning ( " %s Got negative authentification feedback " , self . __log_prefix__ ( ) )
return self . STATUS_OKAY , None
def __authentificate_salt_and_hash__ ( self , seed ) :
if sys . version_info > = ( 3 , 0 ) :
return hashlib . sha512 ( bytes ( seed , ' utf-8 ' ) + self . __secret__ ) . hexdigest ( )
else :
return hashlib . sha512 ( seed . encode ( ' utf-8 ' ) + self . __secret__ . encode ( ' utf-8 ' ) ) . hexdigest ( )
def __authentification_state_reset__ ( self ) :
self . logger . info ( " %s Resetting authentification state to AUTH_STATE_UNKNOWN_CLIENT " , self . __log_prefix__ ( ) )
self . __authentification_state__ = self . AUTH_STATE_UNKNOWN_CLIENT
def __buffer_received_data__ ( self , msg ) :
if not msg . get_service_id ( ) in self . __msg_buffer__ :
self . __msg_buffer__ [ msg . get_service_id ( ) ] = { }
if not msg . get_data_id ( ) in self . __msg_buffer__ [ msg . get_service_id ( ) ] :
self . __msg_buffer__ [ msg . get_service_id ( ) ] [ msg . get_data_id ( ) ] = [ ]
self . __msg_buffer__ [ msg . get_service_id ( ) ] [ msg . get_data_id ( ) ] . append ( msg )
self . logger . debug ( " %s Message data is stored in buffer and is now ready to be retrieved by receive method " , self . __log_prefix__ ( ) )
def __build_frame__ ( self , service_id , data_id , data , status = STATUS_OKAY ) :
data_frame = json . dumps ( self . __mk_msg__ ( status , service_id , data_id , data ) )
if sys . version_info > = ( 3 , 0 ) :
data_frame = bytes ( data_frame , ' utf-8 ' )
checksum = self . __calc_chksum__ ( data_frame )
return data_frame + checksum
def __calc_chksum__ ( self , raw_data ) :
return struct . pack ( ' >I ' , binascii . crc32 ( raw_data ) & 0xffffffff )
2020-12-25 17:01:16 +01:00
@property
def __channel_name__ ( self ) :
cn = self . logger . name . split ( ' . ' ) [ - 1 ]
if cn != self . DEFAULT_CHANNEL_NAME :
return cn
2021-01-01 20:16:12 +01:00
def __channel_name_response__ ( self , msg ) :
data = msg . get_data ( )
if self . __channel_name__ is None and data is not None :
self . __init_channel_name__ ( data )
self . logger . info ( ' %s channel name is now %s ' , self . __log_prefix__ ( ) , repr ( self . __channel_name__ ) )
return self . STATUS_OKAY , None
2020-09-10 21:55:01 +02:00
2020-12-25 17:01:16 +01:00
def __channel_name_request__ ( self , msg ) :
data = msg . get_data ( )
if data is None :
return self . STATUS_OKAY , self . __channel_name__
else :
prev_channel_name = self . __channel_name__
self . __init_channel_name__ ( data )
if prev_channel_name is not None and prev_channel_name != data :
self . logger . warning ( ' %s overwriting user defined channel name from %s to %s ' , self . __log_prefix__ ( ) , repr ( prev_channel_name ) , repr ( data ) )
elif prev_channel_name is None :
self . logger . info ( ' %s channel name is now %s ' , self . __log_prefix__ ( ) , repr ( self . __channel_name__ ) )
return self . STATUS_OKAY , None
2020-01-26 16:14:16 +01:00
def __check_frame_checksum__ ( self , frame ) :
2021-01-01 20:16:12 +01:00
return self . __calc_chksum__ ( frame [ : - 4 ] ) == frame [ - 4 : ]
def __clean_receive_buffer__ ( self ) :
self . logger . debug ( " %s Cleaning up receive-buffer " , self . __log_prefix__ ( ) )
self . __msg_buffer__ = { }
2020-01-26 16:14:16 +01:00
def __data_available_callback__ ( self , comm_inst ) :
frame = comm_inst . receive ( )
if not self . __check_frame_checksum__ ( frame ) :
2020-12-25 17:01:16 +01:00
self . logger . warning ( " %s Received message has a wrong checksum and will be ignored: %s . " , self . __log_prefix__ ( ) , stringtools . hexlify ( frame ) )
2020-01-26 16:14:16 +01:00
else :
msg = self . __analyse_frame__ ( frame )
2020-12-25 17:01:16 +01:00
self . logger . info (
2020-01-26 16:14:16 +01:00
' %s RX <- status: %s , service_id: %s , data_id: %s , data: " %s " ' ,
2020-12-25 17:01:16 +01:00
self . __log_prefix__ ( ) ,
2020-01-26 16:14:16 +01:00
repr ( msg . get_status ( ) ) ,
repr ( msg . get_service_id ( ) ) ,
repr ( msg . get_data_id ( ) ) ,
repr ( msg . get_data ( ) )
)
2020-09-20 19:24:45 +02:00
callback , args , kwargs = self . __callbacks__ . get ( msg . get_service_id ( ) , msg . get_data_id ( ) )
2021-01-01 20:16:12 +01:00
if msg . get_service_id ( ) in self . SID__RESPONSE_DICT . keys ( ) :
2020-01-26 16:14:16 +01:00
#
# REQUEST RECEIVED
#
2021-01-01 20:16:12 +01:00
if self . __secret__ is not None and not self . check_authentification_state ( ) and msg . get_service_id ( ) not in self . SID__NO_AUTH_LIST :
2020-01-26 16:14:16 +01:00
status = self . STATUS_AUTH_REQUIRED
data = None
2021-01-01 20:16:12 +01:00
self . logger . warning ( " %s Received message needs authentification: %s . Sending negative response. " , self . __log_prefix__ ( ) , self . AUTH_STATE__NAMES . get ( self . __authentification_state__ , ' Unknown authentification status! ' ) )
2020-01-26 16:14:16 +01:00
elif callback is None :
2020-12-25 17:01:16 +01:00
self . logger . warning ( " %s Received message with no registered callback. Sending negative response. " , self . __log_prefix__ ( ) )
2020-01-26 16:14:16 +01:00
status = self . STATUS_BUFFERING_UNHANDLED_REQUEST
data = None
else :
try :
2020-12-25 17:01:16 +01:00
self . logger . debug ( " %s Executing callback %s to process received data " , self . __log_prefix__ ( ) , callback . __name__ )
2020-09-20 19:24:45 +02:00
status , data = callback ( msg , * args , * * kwargs )
2020-01-26 16:14:16 +01:00
except TypeError :
raise TypeError ( ' Check return value of callback function {callback_name} for service_id {service_id} and data_id {data_id} ' . format ( callback_name = callback . __name__ , service_id = repr ( msg . get_service_id ( ) ) , data_id = repr ( msg . get_data_id ( ) ) ) )
2021-01-01 20:16:12 +01:00
self . send ( self . SID__RESPONSE_DICT [ msg . get_service_id ( ) ] , msg . get_data_id ( ) , data , status = status )
2020-01-26 16:14:16 +01:00
else :
#
# RESPONSE RECEIVED
#
if msg . get_status ( ) not in [ self . STATUS_OKAY ] :
2021-01-01 20:16:12 +01:00
self . logger . warning ( " %s Received message has a peculiar status: %s " , self . __log_prefix__ ( ) , self . STATUS__NAMES . get ( msg . get_status ( ) , ' Unknown status response! ' ) )
2020-01-26 16:14:16 +01:00
if callback is None :
status = self . STATUS_OKAY
data = None
self . __buffer_received_data__ ( msg )
else :
try :
2020-12-25 17:01:16 +01:00
self . logger . debug ( " %s Executing callback %s to process received data " , self . __log_prefix__ ( ) , callback . __name__ )
2020-09-20 19:24:45 +02:00
status , data = callback ( msg , * args , * * kwargs )
2020-01-26 16:14:16 +01:00
except TypeError :
raise TypeError ( ' Check return value of callback function {callback_name} for service_id {service_id} and data_id {data_id} ' . format ( callback_name = callback . __name__ , service_id = repr ( msg . get_service_id ( ) ) , data_id = repr ( msg . get_data_id ( ) ) ) )
2021-01-01 20:16:12 +01:00
def __connection_established__ ( self ) :
self . __clean_receive_buffer__ ( )
if not self . __comm_inst__ . IS_CLIENT :
self . send ( self . SID_CHANNEL_NAME_REQUEST , 0 , self . __channel_name__ )
if self . __auto_auth__ and self . __comm_inst__ . IS_CLIENT and self . __secret__ is not None :
self . authentificate ( )
2020-01-26 16:14:16 +01:00
2021-01-01 20:16:12 +01:00
def __init_channel_name__ ( self , channel_name ) :
self . __comm_inst__ . init_channel_name ( channel_name )
self . __callbacks__ . init_channel_name ( channel_name )
if channel_name is None :
self . logger = logging . getLogger ( ROOT_LOGGER_NAME ) . getChild ( __name__ + ' . ' + self . DEFAULT_CHANNEL_NAME )
else :
self . logger = logging . getLogger ( ROOT_LOGGER_NAME ) . getChild ( __name__ + ' . ' + channel_name )
2020-01-26 16:14:16 +01:00
2021-01-01 20:16:12 +01:00
def __log_prefix__ ( self ) :
return ' SP client: ' if self . __comm_inst__ . IS_CLIENT else ' SP server: '
2020-01-26 16:14:16 +01:00
def __mk_msg__ ( self , status , service_id , data_id , data ) :
return data_storage ( { data_storage . KEY_DATA_ID : data_id , data_storage . KEY_SERVICE_ID : service_id , data_storage . KEY_STATUS : status , data_storage . KEY_DATA : data } )
def authentificate ( self , timeout = 2 ) :
"""
: param timeout : The timeout for the authentification ( requesting seed , sending key and getting authentification_feedback ) .
: type timeout : float
: returns : True , if authentification was successfull ; False , if not .
: rtype : bool
This method authetificates the client at the server .
. . note : : An authentification will only processed , if a secret had been given on initialisation .
. . note : : Client and Server needs to use the same secret .
"""
if self . __secret__ is not None :
self . __authentification_state__ = self . AUTH_STATE_SEED_REQUESTED
2020-12-25 17:01:16 +01:00
self . logger . info ( " %s Requesting seed for authentification " , self . __log_prefix__ ( ) )
2020-01-26 16:14:16 +01:00
self . send ( self . SID_AUTH_SEED_REQUEST , 0 , None )
cnt = 0
while cnt < timeout * 10 :
time . sleep ( 0.1 )
if self . __authentification_state__ == self . AUTH_STATE_TRUSTED_CLIENT :
return True
elif self . __authentification_state__ == self . AUTH_STATE_UNKNOWN_CLIENT :
break
cnt + = 1
return False
def check_authentification_state ( self ) :
"""
: return : True , if authentification state is okay , otherwise False
: rtype : bool
"""
return self . __authentification_state__ == self . AUTH_STATE_TRUSTED_CLIENT
2021-01-01 20:16:12 +01:00
def connection_established ( self ) :
"""
: return : True , if the connection is established ( incl . authentification , if a secret has been given )
: rtype : bool
"""
return self . is_connected ( ) and ( self . __secret__ is None or self . check_authentification_state ( ) )
2020-01-26 16:14:16 +01:00
2021-01-01 20:16:12 +01:00
def is_connected ( self ) :
"""
: return : True if the : class : ` comm_instance ` is connected , otherwise False . .
: rtype : bool
2020-01-26 16:14:16 +01:00
2021-01-01 20:16:12 +01:00
This methods returns the return value of : func : ` comm_instance . is_connected ` .
"""
return self . __comm_inst__ . is_connected ( )
2020-01-26 16:14:16 +01:00
2021-01-01 20:16:12 +01:00
def receive ( self , service_id , data_id , timeout = 1 ) :
"""
: param service_id : The Service - ID for the message . See class definitions starting with ` ` SID_ ` ` .
: type service_id : int
: param data_id : The Data - ID for the message .
: type data_id : int
: param timeout : The timeout for receiving .
: type timeout : float
: returns : The received data storage object or None , if no data was received .
: rtype : data_storage
"""
data = None
cnt = 0
while data is None and cnt < timeout * 10 :
try :
data = self . __msg_buffer__ . get ( service_id , { } ) . get ( data_id , [ ] ) . pop ( 0 )
except IndexError :
data = None
cnt + = 1
time . sleep ( 0.1 )
if data is None and cnt > = timeout * 10 :
self . logger . warning ( ' %s TIMEOUT ( %s s): Requested data (service_id: %s ; data_id: %s ) not in buffer. ' , self . __log_prefix__ ( ) , repr ( timeout ) , repr ( service_id ) , repr ( data_id ) )
return data
2020-01-26 16:14:16 +01:00
2021-01-01 20:16:12 +01:00
def reconnect ( self ) :
"""
This methods initiates a reconnect by calling : func : ` comm_instance . reconnect ` .
"""
return self . __comm_inst__ . reconnect ( )
def register_callback ( self , service_id , data_id , callback , * args , * * kwargs ) :
"""
: param service_id : The Service - ID for the message . See class definitions starting with ` ` SID_ ` ` .
: type service_id : int
: param data_id : The Data - ID for the message .
: type data_id : int
: returns : True , if registration was successfull ; False , if registration failed ( e . g . existance of a callback for this configuration )
: rtype : bool
2020-01-26 16:14:16 +01:00
2021-01-01 20:16:12 +01:00
This method registers a callback for the given parameters . Givin ` ` None ` ` means , that all Service - IDs or all Data - IDs are used .
If a message hitting these parameters has been received , the callback will be executed .
2020-01-26 16:14:16 +01:00
2021-01-01 20:16:12 +01:00
. . note : : The : func : ` callback ` is priorised in the following order :
2020-01-26 16:14:16 +01:00
2021-01-01 20:16:12 +01:00
* Callbacks with defined Service - ID and Data - ID .
* Callbacks with a defined Data - ID .
* Callbacks with a defined Service - ID .
* Unspecific Callbacks
. . note : : The : func : ` callback ` is executed with these arguments :
2020-01-26 16:14:16 +01:00
2021-01-01 20:16:12 +01:00
: param msg : A : class : ` data_storage ` object containing all message information .
: returns : ( : const : ` status ` , : const : ` response_data ` )
2020-01-26 16:14:16 +01:00
2021-01-01 20:16:12 +01:00
* : const : ` status ` : A status as defined as a constant of this class : const : ` STA_ * ` to be used as status for the response .
* : const : ` response_data ` : A JSON iterable object to be used as data for the response .
2020-01-26 16:14:16 +01:00
2021-01-01 20:16:12 +01:00
. . note : : Only callbacks defined in : const : ` pure_json_protocol . SID__RESPONSE_DICT ` will send a response , using a Service - ID given in the dict and the same Data - ID to the client .
"""
self . __callbacks__ . add ( service_id , data_id , callback , * args , * * kwargs )
2020-01-26 16:14:16 +01:00
2021-01-01 20:16:12 +01:00
def send ( self , service_id , data_id , data , status = STATUS_OKAY , timeout = 2 , log_lvl = logging . INFO ) :
"""
: param service_id : The Service - ID for the message . See class definitions starting with ` ` SERVICE_ ` ` .
: type service_id : int
: param data_id : The Data - ID for the message .
: type data_id : int
: param data : The data to be transfered . The data needs to be json compatible .
: type data : str
: param status : The Status for the message . All requests should have ` ` STATUS_OKAY ` ` .
: type status : int
: param timeout : The timeout for sending data ( e . g . time to establish new connection ) .
: type timeout : float
: param rx_log_lvl : The log level to log outgoing TX - data
: type rx_log_lvl : int
: return : True if data had been sent , otherwise False .
: rtype : bool
2020-01-26 16:14:16 +01:00
2021-01-01 20:16:12 +01:00
This methods sends out a message with the given content .
"""
self . logger . log ( log_lvl , ' %s TX -> status: %d , service_id: %d , data_id: %d , data: " %s " ' , self . __log_prefix__ ( ) , status , service_id , data_id , repr ( data ) )
return self . __comm_inst__ . send ( self . __build_frame__ ( service_id , data_id , data , status ) , timeout = timeout , log_lvl = logging . DEBUG )
2020-01-26 16:14:16 +01:00
2021-01-01 20:16:12 +01:00
class struct_json_protocol ( pure_json_protocol ) :
"""
This Class has the same functionality like : class : ` pure_json_protocol ` . The message length is less than for : class : ` pure_json_protocol ` , but the functionality and compatibility is reduced .
2020-01-26 16:14:16 +01:00
2021-01-01 20:16:12 +01:00
. . note : :
This class is depreceated and here for compatibility reasons ( to support old clients or servers ) . Usage of : class : ` pure_json_protocol ` is recommended .
2020-01-26 16:14:16 +01:00
"""
2020-09-10 21:55:01 +02:00
def __init__ ( self , * args , * * kwargs ) :
2021-01-01 20:16:12 +01:00
pure_json_protocol . __init__ ( self , * args , * * kwargs )
2020-01-26 16:14:16 +01:00
2021-01-01 20:16:12 +01:00
def __analyse_frame__ ( self , frame ) :
status , service_id , data_id = struct . unpack ( ' >III ' , frame [ 0 : 12 ] )
2020-01-26 16:14:16 +01:00
if sys . version_info > = ( 3 , 0 ) :
2021-01-01 20:16:12 +01:00
data = json . loads ( frame [ 12 : - 1 ] . decode ( ' utf-8 ' ) )
else :
data = json . loads ( frame [ 12 : - 1 ] )
return self . __mk_msg__ ( status , service_id , data_id , data )
2020-01-26 16:14:16 +01:00
2021-01-01 20:16:12 +01:00
def __build_frame__ ( self , service_id , data_id , data , status = pure_json_protocol . STATUS_OKAY ) :
frame = struct . pack ( ' >III ' , status , service_id , data_id )
2020-01-26 16:14:16 +01:00
if sys . version_info > = ( 3 , 0 ) :
2021-01-01 20:16:12 +01:00
frame + = bytes ( json . dumps ( data ) , ' utf-8 ' )
frame + = self . __calc_chksum__ ( frame )
2020-01-26 16:14:16 +01:00
else :
2021-01-01 20:16:12 +01:00
frame + = json . dumps ( data )
frame + = self . __calc_chksum__ ( frame )
return frame
2020-01-26 16:14:16 +01:00
def __calc_chksum__ ( self , raw_data ) :
2021-01-01 20:16:12 +01:00
chksum = 0
for b in raw_data :
if sys . version_info > = ( 3 , 0 ) :
chksum ^ = b
else :
chksum ^ = ord ( b )
if sys . version_info > = ( 3 , 0 ) :
return bytes ( [ chksum ] )
else :
return chr ( chksum )
2020-01-26 16:14:16 +01:00
def __check_frame_checksum__ ( self , frame ) :
2021-01-01 20:16:12 +01:00
return self . __calc_chksum__ ( frame [ : - 1 ] ) == frame [ - 1 : ]