Python Library Socket Protocol
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643
  1. #!/usr/bin/env python
  2. # -*- coding: utf-8 -*-
  3. #
  4. """
  5. socket_protocol (Socket Protocol)
  6. =================================
  7. **Author:**
  8. * Dirk Alders <sudo-dirk@mount-mockery.de>
  9. **Description:**
  10. This Module supports point to point communication for client-server issues.
  11. **Submodules:**
  12. * :class:`socket_protocol.pure_json_protocol`
  13. * :class:`socket_protocol.struct_json_protocol`
  14. **Unittest:**
  15. See also the :download:`unittest <../pylibs/socket_protocol/_testresults_/unittest.pdf>` documentation.
  16. **Module Documentation:**
  17. """
  18. __DEPENDENCIES__ = ['stringtools']
  19. import stringtools
  20. import binascii
  21. import hashlib
  22. import json
  23. import logging
  24. import os
  25. import struct
  26. import sys
  27. import time
  28. try:
  29. from config import APP_NAME as ROOT_LOGGER_NAME
  30. except ImportError:
  31. ROOT_LOGGER_NAME = 'root'
  32. logger = logging.getLogger(ROOT_LOGGER_NAME).getChild(__name__)
  33. __DESCRIPTION__ = """The Module {\\tt %s} is designed for point to point communication for client-server issues.
  34. For more Information read the sphinx documentation.""" % __name__.replace('_', '\_')
  35. """The Module Description"""
  36. __INTERPRETER__ = (2, 3)
  37. """The Tested Interpreter-Versions"""
  38. class _callback_storage(dict):
  39. DEFAULT_CHANNEL_NAME = 'all_others'
  40. def __init__(self, channel_name):
  41. self.init_channel_name(channel_name)
  42. dict.__init__(self)
  43. def init_channel_name(self, channel_name):
  44. if channel_name is None:
  45. self.logger = logging.getLogger(ROOT_LOGGER_NAME).getChild(__name__ + '.' + self.DEFAULT_CHANNEL_NAME)
  46. else:
  47. self.logger = logging.getLogger(ROOT_LOGGER_NAME).getChild(__name__ + '.' + channel_name)
  48. def get(self, service_id, data_id):
  49. if service_id is not None and data_id is not None:
  50. try:
  51. return self[service_id][data_id]
  52. except KeyError:
  53. pass # nothing to append
  54. if data_id is not None:
  55. try:
  56. return self[None][data_id]
  57. except KeyError:
  58. pass # nothing to append
  59. if service_id is not None:
  60. try:
  61. return self[service_id][None]
  62. except KeyError:
  63. pass # nothing to append
  64. try:
  65. return self[None][None]
  66. except KeyError:
  67. pass # nothing to append
  68. return (None, None, None)
  69. def add(self, service_id, data_id, callback, *args, **kwargs):
  70. cb_data = self.get(service_id, data_id)
  71. if cb_data != (None, None, None):
  72. 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__))
  73. if service_id not in self:
  74. self[service_id] = {}
  75. self[service_id][data_id] = (callback, args, kwargs)
  76. class data_storage(dict):
  77. """
  78. :param status: The message status.
  79. :type status: int
  80. :param service_id: The Service-ID.
  81. :type service_id: int
  82. :param data_id: The Data-ID.
  83. :type data_id: int
  84. :param data: The transfered data.
  85. :type data: any
  86. This is a storage object for socket_protocol messages.
  87. """
  88. KEY_STATUS = 'status'
  89. KEY_SERVICE_ID = 'service_id'
  90. KEY_DATA_ID = 'data_id'
  91. KEY_DATA = 'data'
  92. def __init__(self, *args, **kwargs):
  93. dict.__init__(self, *args, **kwargs)
  94. def get_status(self, default=None):
  95. """
  96. :param default: The default value, if no data is available.
  97. This Method returns the message status.
  98. """
  99. return self.get(self.KEY_STATUS, default)
  100. def get_service_id(self, default=None):
  101. """
  102. :param default: The default value, if no data is available.
  103. This Method returns the message Service-ID.
  104. """
  105. return self.get(self.KEY_SERVICE_ID, default)
  106. def get_data_id(self, default=None):
  107. """
  108. :param default: The default value, if no data is available.
  109. This Method returns the message Data-ID.
  110. """
  111. return self.get(self.KEY_DATA_ID, default)
  112. def get_data(self, default=None):
  113. """
  114. :param default: The default value, if no data is available.
  115. This Method returns the message data.
  116. """
  117. return self.get(self.KEY_DATA, default)
  118. class pure_json_protocol(object):
  119. """
  120. :param comm_instance: A communication instance.
  121. :type comm_instance: instance
  122. :param secret: An optinal secret (e.g. created by ``binascii.hexlify(os.urandom(24))``).
  123. :type secret: str
  124. :param auto_auth: An optional parameter (True) to enable automatic authentification, otherwise you need to do it manually, if needed.
  125. :type auto_auth: bool
  126. :param channel_name: An optional parameter to set a channel name for logging of the communication.
  127. :type channel_name: str
  128. .. hint:: This `class` supports to transfer a Service-ID, Data-ID and Data.
  129. * The Service-ID is designed to identify the type of the communication (e.g. READ_REQUEST, WRITE_REQUEST, READ_RESPONSE, WRITE_RESPONSE, ...)
  130. * The Data-ID is designed to identify the requests / responses using the same Service_ID.
  131. .. note:: The :class:`comm_instance` needs to have at least the following interface:
  132. * A Method :func:`comm_instance.init_channel_name` to set the channel name if needed.
  133. * A Constant :const:`comm_instance.IS_CLIENT` to identify that the :class:`comm_instance` is a client (True) or a server (False).
  134. * A Method :func:`comm_instance.is_connected` to identify if the instance is connected (True) or not (False).
  135. * A Method :func:`comm_instance.reconnect` to initiate a reconnect.
  136. * A Method :func:`comm_instance.register_callback` to register a data available callback.
  137. * A Method :func:`comm_instance.register_connect_callback` to register a connect callback.
  138. * A Method :func:`comm_instance.register_disconnect_callback` to register a disconnect callback.
  139. * A Method :func:`comm_instance.send` to send data via the :class:`comm_instance`.
  140. .. 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.
  141. .. note:: The :const:`channel_name`-exchange will be initiated by the client directly after the the connection is established.
  142. * 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.
  143. """
  144. DEFAULT_CHANNEL_NAME = 'all_others'
  145. SID_AUTH_SEED_REQUEST = 1
  146. """SID for requesting a seed for authentification"""
  147. SID_AUTH_KEY_REQUEST = 2
  148. """SID for requesting a key for the given seed"""
  149. SID_AUTH_KEY_CHECK_REQUEST = 3
  150. """SID for request for checking a key"""
  151. SID_AUTH_KEY_CHECK_RESPONSE = 4
  152. """SID for the authentification response"""
  153. SID_CHANNEL_NAME_REQUEST = 5
  154. """SID for requesting a channel name exchange"""
  155. SID_CHANNEL_NAME_RESPONSE = 6
  156. """SID for the channel name response"""
  157. SID_READ_REQUEST = 10
  158. """SID for a read data request"""
  159. SID_READ_RESPONSE = 11
  160. """SID for read data response"""
  161. SID_WRITE_REQUEST = 20
  162. """SID for a write data request"""
  163. SID_WRITE_RESPONSE = 21
  164. """SID for a write data response"""
  165. SID_EXECUTE_REQUEST = 30
  166. """SID for a execute request"""
  167. SID_EXECUTE_RESPONSE = 31
  168. """SID for a execute response"""
  169. SID__RESPONSE_DICT = {SID_AUTH_SEED_REQUEST: SID_AUTH_KEY_REQUEST,
  170. SID_AUTH_KEY_REQUEST: SID_AUTH_KEY_CHECK_REQUEST,
  171. SID_AUTH_KEY_CHECK_REQUEST: SID_AUTH_KEY_CHECK_RESPONSE,
  172. SID_CHANNEL_NAME_REQUEST: SID_CHANNEL_NAME_RESPONSE,
  173. SID_READ_REQUEST: SID_READ_RESPONSE,
  174. SID_WRITE_REQUEST: SID_WRITE_RESPONSE,
  175. SID_EXECUTE_REQUEST: SID_EXECUTE_RESPONSE}
  176. """Dictionary to get the SID for the response by the key which is the SID for the request"""
  177. SID__NO_AUTH_LIST = [
  178. SID_AUTH_SEED_REQUEST,
  179. SID_AUTH_KEY_REQUEST,
  180. SID_AUTH_KEY_CHECK_REQUEST,
  181. SID_AUTH_KEY_CHECK_RESPONSE,
  182. SID_CHANNEL_NAME_REQUEST,
  183. SID_CHANNEL_NAME_RESPONSE
  184. ]
  185. """List of SIDs without need of an authentification"""
  186. STATUS_OKAY = 0
  187. """Status for 'okay'"""
  188. STATUS_BUFFERING_UNHANDLED_REQUEST = 1
  189. """Status for 'unhandled request'"""
  190. STATUS_AUTH_REQUIRED = 2
  191. """Status for 'authentification is required'"""
  192. STATUS_SERVICE_OR_DATA_UNKNOWN = 3
  193. """Status for 'service or data unknown'"""
  194. STATUS_CHECKSUM_ERROR = 4
  195. """Status for 'checksum error'"""
  196. STATUS_OPERATION_NOT_PERMITTED = 5
  197. """Status for 'operation not permitted'"""
  198. STATUS__NAMES = {STATUS_OKAY: 'Okay',
  199. STATUS_BUFFERING_UNHANDLED_REQUEST: 'Request has no callback. Data buffered.',
  200. STATUS_AUTH_REQUIRED: 'Authentification required',
  201. STATUS_SERVICE_OR_DATA_UNKNOWN: 'Service or Data unknown',
  202. STATUS_CHECKSUM_ERROR: 'Checksum Error',
  203. STATUS_OPERATION_NOT_PERMITTED: 'Operation not permitted'}
  204. """Status names for previous defined states"""
  205. AUTH_STATE_UNKNOWN_CLIENT = 0
  206. """Authentification Status for 'Unknown Client'"""
  207. AUTH_STATE_SEED_REQUESTED = 1
  208. """Authentification Status for 'Seed was requested'"""
  209. AUTH_STATE_SEED_TRANSFERRED = 2
  210. """Authentification Status for 'Seed has been sent'"""
  211. AUTH_STATE_KEY_TRANSFERRED = 3
  212. """Authentification Status for 'Key has been sent'"""
  213. AUTH_STATE_TRUSTED_CLIENT = 4
  214. """Authentification Status for 'Trusted Connection'"""
  215. AUTH_STATE__NAMES = {AUTH_STATE_UNKNOWN_CLIENT: 'Unknown Client',
  216. AUTH_STATE_SEED_REQUESTED: 'Seed was requested',
  217. AUTH_STATE_SEED_TRANSFERRED: 'Seed has been sent',
  218. AUTH_STATE_KEY_TRANSFERRED: 'Key has been sent',
  219. AUTH_STATE_TRUSTED_CLIENT: 'Trusted Connection'}
  220. """Authentification Status names for previous defined authentification states"""
  221. def __init__(self, comm_instance, secret=None, auto_auth=False, channel_name=None):
  222. self.__comm_inst__ = comm_instance
  223. self.__secret__ = secret
  224. self.__auto_auth__ = auto_auth
  225. #
  226. self.__callbacks__ = _callback_storage(channel_name)
  227. self.__init_channel_name__(channel_name)
  228. #
  229. self.__clean_receive_buffer__()
  230. self.__callbacks__.add(self.SID_AUTH_SEED_REQUEST, 0, self.__authentificate_create_seed__)
  231. self.__callbacks__.add(self.SID_AUTH_KEY_REQUEST, 0, self.__authentificate_create_key__)
  232. self.__callbacks__.add(self.SID_AUTH_KEY_CHECK_REQUEST, 0, self.__authentificate_check_key__)
  233. self.__callbacks__.add(self.SID_AUTH_KEY_CHECK_RESPONSE, 0, self.__authentificate_process_feedback__)
  234. self.__callbacks__.add(self.SID_CHANNEL_NAME_REQUEST, 0, self.__channel_name_request__)
  235. self.__callbacks__.add(self.SID_CHANNEL_NAME_RESPONSE, 0, self.__channel_name_response__)
  236. self.__authentification_state_reset__()
  237. self.__seed__ = None
  238. self.__comm_inst__.register_callback(self.__data_available_callback__)
  239. self.__comm_inst__.register_connect_callback(self.__connection_established__)
  240. self.__comm_inst__.register_disconnect_callback(self.__authentification_state_reset__)
  241. def __analyse_frame__(self, frame):
  242. if sys.version_info >= (3, 0):
  243. return data_storage(json.loads(frame[:-4].decode('utf-8')))
  244. else:
  245. return data_storage(json.loads(frame[:-4]))
  246. def __authentificate_check_key__(self, msg):
  247. key = msg.get_data()
  248. if key == self.__authentificate_salt_and_hash__(self.__seed__):
  249. self.__authentification_state__ = self.AUTH_STATE_TRUSTED_CLIENT
  250. self.logger.info("%s Got correct key, sending positive authentification feedback", self.__log_prefix__())
  251. return self.STATUS_OKAY, True
  252. else:
  253. self.__authentification_state__ = self.AUTH_STATE_UNKNOWN_CLIENT
  254. self.logger.info("%s Got incorrect key, sending negative authentification feedback", self.__log_prefix__())
  255. return self.STATUS_OKAY, False
  256. def __authentificate_create_key__(self, msg):
  257. self.logger.info("%s Got seed, sending key for authentification", self.__log_prefix__())
  258. self.__authentification_state__ = self.AUTH_STATE_KEY_TRANSFERRED
  259. seed = msg.get_data()
  260. key = self.__authentificate_salt_and_hash__(seed)
  261. return self.STATUS_OKAY, key
  262. def __authentificate_create_seed__(self, msg):
  263. self.logger.info("%s Got seed request, sending seed for authentification", self.__log_prefix__())
  264. self.__authentification_state__ = self.AUTH_STATE_SEED_TRANSFERRED
  265. if sys.version_info >= (3, 0):
  266. self.__seed__ = binascii.hexlify(os.urandom(32)).decode('utf-8')
  267. else:
  268. self.__seed__ = binascii.hexlify(os.urandom(32))
  269. return self.STATUS_OKAY, self.__seed__
  270. def __authentificate_process_feedback__(self, msg):
  271. feedback = msg.get_data()
  272. if feedback:
  273. self.__authentification_state__ = self.AUTH_STATE_TRUSTED_CLIENT
  274. self.logger.info("%s Got positive authentification feedback", self.__log_prefix__())
  275. else:
  276. self.__authentification_state__ = self.AUTH_STATE_UNKNOWN_CLIENT
  277. self.logger.warning("%s Got negative authentification feedback", self.__log_prefix__())
  278. return self.STATUS_OKAY, None
  279. def __authentificate_salt_and_hash__(self, seed):
  280. if sys.version_info >= (3, 0):
  281. return hashlib.sha512(bytes(seed, 'utf-8') + self.__secret__).hexdigest()
  282. else:
  283. return hashlib.sha512(seed.encode('utf-8') + self.__secret__.encode('utf-8')).hexdigest()
  284. def __authentification_state_reset__(self):
  285. self.logger.info("%s Resetting authentification state to AUTH_STATE_UNKNOWN_CLIENT", self.__log_prefix__())
  286. self.__authentification_state__ = self.AUTH_STATE_UNKNOWN_CLIENT
  287. def __buffer_received_data__(self, msg):
  288. if not msg.get_service_id() in self.__msg_buffer__:
  289. self.__msg_buffer__[msg.get_service_id()] = {}
  290. if not msg.get_data_id() in self.__msg_buffer__[msg.get_service_id()]:
  291. self.__msg_buffer__[msg.get_service_id()][msg.get_data_id()] = []
  292. self.__msg_buffer__[msg.get_service_id()][msg.get_data_id()].append(msg)
  293. self.logger.debug("%s Message data is stored in buffer and is now ready to be retrieved by receive method", self.__log_prefix__())
  294. def __build_frame__(self, service_id, data_id, data, status=STATUS_OKAY):
  295. data_frame = json.dumps(self.__mk_msg__(status, service_id, data_id, data))
  296. if sys.version_info >= (3, 0):
  297. data_frame = bytes(data_frame, 'utf-8')
  298. checksum = self.__calc_chksum__(data_frame)
  299. return data_frame + checksum
  300. def __calc_chksum__(self, raw_data):
  301. return struct.pack('>I', binascii.crc32(raw_data) & 0xffffffff)
  302. @property
  303. def __channel_name__(self):
  304. cn = self.logger.name.split('.')[-1]
  305. if cn != self.DEFAULT_CHANNEL_NAME:
  306. return cn
  307. def __channel_name_response__(self, msg):
  308. data = msg.get_data()
  309. if self.__channel_name__ is None and data is not None:
  310. self.__init_channel_name__(data)
  311. self.logger.info('%s channel name is now %s', self.__log_prefix__(), repr(self.__channel_name__))
  312. return self.STATUS_OKAY, None
  313. def __channel_name_request__(self, msg):
  314. data = msg.get_data()
  315. if data is None:
  316. return self.STATUS_OKAY, self.__channel_name__
  317. else:
  318. prev_channel_name = self.__channel_name__
  319. self.__init_channel_name__(data)
  320. if prev_channel_name is not None and prev_channel_name != data:
  321. self.logger.warning('%s overwriting user defined channel name from %s to %s', self.__log_prefix__(), repr(prev_channel_name), repr(data))
  322. elif prev_channel_name is None:
  323. self.logger.info('%s channel name is now %s', self.__log_prefix__(), repr(self.__channel_name__))
  324. return self.STATUS_OKAY, None
  325. def __check_frame_checksum__(self, frame):
  326. return self.__calc_chksum__(frame[:-4]) == frame[-4:]
  327. def __clean_receive_buffer__(self):
  328. self.logger.debug("%s Cleaning up receive-buffer", self.__log_prefix__())
  329. self.__msg_buffer__ = {}
  330. def __data_available_callback__(self, comm_inst):
  331. frame = comm_inst.receive()
  332. if not self.__check_frame_checksum__(frame):
  333. self.logger.warning("%s Received message has a wrong checksum and will be ignored: %s.", self.__log_prefix__(), stringtools.hexlify(frame))
  334. else:
  335. msg = self.__analyse_frame__(frame)
  336. self.logger.info(
  337. '%s RX <- status: %s, service_id: %s, data_id: %s, data: "%s"',
  338. self.__log_prefix__(),
  339. repr(msg.get_status()),
  340. repr(msg.get_service_id()),
  341. repr(msg.get_data_id()),
  342. repr(msg.get_data())
  343. )
  344. callback, args, kwargs = self.__callbacks__.get(msg.get_service_id(), msg.get_data_id())
  345. if msg.get_service_id() in self.SID__RESPONSE_DICT.keys():
  346. #
  347. # REQUEST RECEIVED
  348. #
  349. if self.__secret__ is not None and not self.check_authentification_state() and msg.get_service_id() not in self.SID__NO_AUTH_LIST:
  350. status = self.STATUS_AUTH_REQUIRED
  351. data = None
  352. 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!'))
  353. elif callback is None:
  354. self.logger.warning("%s Received message with no registered callback. Sending negative response.", self.__log_prefix__())
  355. status = self.STATUS_BUFFERING_UNHANDLED_REQUEST
  356. data = None
  357. else:
  358. try:
  359. self.logger.debug("%s Executing callback %s to process received data", self.__log_prefix__(), callback.__name__)
  360. status, data = callback(msg, *args, **kwargs)
  361. except TypeError:
  362. 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())))
  363. self.send(self.SID__RESPONSE_DICT[msg.get_service_id()], msg.get_data_id(), data, status=status)
  364. else:
  365. #
  366. # RESPONSE RECEIVED
  367. #
  368. if msg.get_status() not in [self.STATUS_OKAY]:
  369. self.logger.warning("%s Received message has a peculiar status: %s", self.__log_prefix__(), self.STATUS__NAMES.get(msg.get_status(), 'Unknown status response!'))
  370. if callback is None:
  371. status = self.STATUS_OKAY
  372. data = None
  373. self.__buffer_received_data__(msg)
  374. else:
  375. try:
  376. self.logger.debug("%s Executing callback %s to process received data", self.__log_prefix__(), callback.__name__)
  377. status, data = callback(msg, *args, **kwargs)
  378. except TypeError:
  379. 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())))
  380. def __connection_established__(self):
  381. self.__clean_receive_buffer__()
  382. if not self.__comm_inst__.IS_CLIENT:
  383. self.send(self.SID_CHANNEL_NAME_REQUEST, 0, self.__channel_name__)
  384. if self.__auto_auth__ and self.__comm_inst__.IS_CLIENT and self.__secret__ is not None:
  385. self.authentificate()
  386. def __init_channel_name__(self, channel_name):
  387. self.__comm_inst__.init_channel_name(channel_name)
  388. self.__callbacks__.init_channel_name(channel_name)
  389. if channel_name is None:
  390. self.logger = logging.getLogger(ROOT_LOGGER_NAME).getChild(__name__ + '.' + self.DEFAULT_CHANNEL_NAME)
  391. else:
  392. self.logger = logging.getLogger(ROOT_LOGGER_NAME).getChild(__name__ + '.' + channel_name)
  393. def __log_prefix__(self):
  394. return ' SP client:' if self.__comm_inst__.IS_CLIENT else ' SP server:'
  395. def __mk_msg__(self, status, service_id, data_id, data):
  396. 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})
  397. def authentificate(self, timeout=2):
  398. """
  399. :param timeout: The timeout for the authentification (requesting seed, sending key and getting authentification_feedback).
  400. :type timeout: float
  401. :returns: True, if authentification was successfull; False, if not.
  402. :rtype: bool
  403. This method authetificates the client at the server.
  404. .. note:: An authentification will only processed, if a secret had been given on initialisation.
  405. .. note:: Client and Server needs to use the same secret.
  406. """
  407. if self.__secret__ is not None:
  408. self.__authentification_state__ = self.AUTH_STATE_SEED_REQUESTED
  409. self.logger.info("%s Requesting seed for authentification", self.__log_prefix__())
  410. self.send(self.SID_AUTH_SEED_REQUEST, 0, None)
  411. cnt = 0
  412. while cnt < timeout * 10:
  413. time.sleep(0.1)
  414. if self.__authentification_state__ == self.AUTH_STATE_TRUSTED_CLIENT:
  415. return True
  416. elif self.__authentification_state__ == self.AUTH_STATE_UNKNOWN_CLIENT:
  417. break
  418. cnt += 1
  419. return False
  420. def check_authentification_state(self):
  421. """
  422. :return: True, if authentification state is okay, otherwise False
  423. :rtype: bool
  424. """
  425. return self.__authentification_state__ == self.AUTH_STATE_TRUSTED_CLIENT
  426. def connection_established(self):
  427. """
  428. :return: True, if the connection is established (incl. authentification, if a secret has been given)
  429. :rtype: bool
  430. """
  431. return self.is_connected() and (self.__secret__ is None or self.check_authentification_state())
  432. def is_connected(self):
  433. """
  434. :return: True if the :class:`comm_instance` is connected, otherwise False..
  435. :rtype: bool
  436. This methods returns the return value of :func:`comm_instance.is_connected`.
  437. """
  438. return self.__comm_inst__.is_connected()
  439. def receive(self, service_id, data_id, timeout=1):
  440. """
  441. :param service_id: The Service-ID for the message. See class definitions starting with ``SID_``.
  442. :type service_id: int
  443. :param data_id: The Data-ID for the message.
  444. :type data_id: int
  445. :param timeout: The timeout for receiving.
  446. :type timeout: float
  447. :returns: The received data storage object or None, if no data was received.
  448. :rtype: data_storage
  449. """
  450. data = None
  451. cnt = 0
  452. while data is None and cnt < timeout * 10:
  453. try:
  454. data = self.__msg_buffer__.get(service_id, {}).get(data_id, []).pop(0)
  455. except IndexError:
  456. data = None
  457. cnt += 1
  458. time.sleep(0.1)
  459. if data is None and cnt >= timeout * 10:
  460. self.logger.warning('%s TIMEOUT (%ss): Requested data (service_id: %s; data_id: %s) not in buffer.', self.__log_prefix__(), repr(timeout), repr(service_id), repr(data_id))
  461. return data
  462. def reconnect(self):
  463. """
  464. This methods initiates a reconnect by calling :func:`comm_instance.reconnect`.
  465. """
  466. return self.__comm_inst__.reconnect()
  467. def register_callback(self, service_id, data_id, callback, *args, **kwargs):
  468. """
  469. :param service_id: The Service-ID for the message. See class definitions starting with ``SID_``.
  470. :type service_id: int
  471. :param data_id: The Data-ID for the message.
  472. :type data_id: int
  473. :returns: True, if registration was successfull; False, if registration failed (e.g. existance of a callback for this configuration)
  474. :rtype: bool
  475. This method registers a callback for the given parameters. Givin ``None`` means, that all Service-IDs or all Data-IDs are used.
  476. If a message hitting these parameters has been received, the callback will be executed.
  477. .. note:: The :func:`callback` is priorised in the following order:
  478. * Callbacks with defined Service-ID and Data-ID.
  479. * Callbacks with a defined Data-ID.
  480. * Callbacks with a defined Service-ID.
  481. * Unspecific Callbacks
  482. .. note:: The :func:`callback` is executed with these arguments:
  483. :param msg: A :class:`data_storage` object containing all message information.
  484. :returns: (:const:`status`, :const:`response_data`)
  485. * :const:`status`: A status as defined as a constant of this class :const:`STA_*` to be used as status for the response.
  486. * :const:`response_data`: A JSON iterable object to be used as data for the response.
  487. .. 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.
  488. """
  489. self.__callbacks__.add(service_id, data_id, callback, *args, **kwargs)
  490. def send(self, service_id, data_id, data, status=STATUS_OKAY, timeout=2, log_lvl=logging.INFO):
  491. """
  492. :param service_id: The Service-ID for the message. See class definitions starting with ``SERVICE_``.
  493. :type service_id: int
  494. :param data_id: The Data-ID for the message.
  495. :type data_id: int
  496. :param data: The data to be transfered. The data needs to be json compatible.
  497. :type data: str
  498. :param status: The Status for the message. All requests should have ``STATUS_OKAY``.
  499. :type status: int
  500. :param timeout: The timeout for sending data (e.g. time to establish new connection).
  501. :type timeout: float
  502. :param rx_log_lvl: The log level to log outgoing TX-data
  503. :type rx_log_lvl: int
  504. :return: True if data had been sent, otherwise False.
  505. :rtype: bool
  506. This methods sends out a message with the given content.
  507. """
  508. 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))
  509. return self.__comm_inst__.send(self.__build_frame__(service_id, data_id, data, status), timeout=timeout, log_lvl=logging.DEBUG)
  510. class struct_json_protocol(pure_json_protocol):
  511. """
  512. 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.
  513. .. note::
  514. This class is depreceated and here for compatibility reasons (to support old clients or servers). Usage of :class:`pure_json_protocol` is recommended.
  515. """
  516. def __init__(self, *args, **kwargs):
  517. pure_json_protocol.__init__(self, *args, **kwargs)
  518. def __analyse_frame__(self, frame):
  519. status, service_id, data_id = struct.unpack('>III', frame[0:12])
  520. if sys.version_info >= (3, 0):
  521. data = json.loads(frame[12:-1].decode('utf-8'))
  522. else:
  523. data = json.loads(frame[12:-1])
  524. return self.__mk_msg__(status, service_id, data_id, data)
  525. def __build_frame__(self, service_id, data_id, data, status=pure_json_protocol.STATUS_OKAY):
  526. frame = struct.pack('>III', status, service_id, data_id)
  527. if sys.version_info >= (3, 0):
  528. frame += bytes(json.dumps(data), 'utf-8')
  529. frame += self.__calc_chksum__(frame)
  530. else:
  531. frame += json.dumps(data)
  532. frame += self.__calc_chksum__(frame)
  533. return frame
  534. def __calc_chksum__(self, raw_data):
  535. chksum = 0
  536. for b in raw_data:
  537. if sys.version_info >= (3, 0):
  538. chksum ^= b
  539. else:
  540. chksum ^= ord(b)
  541. if sys.version_info >= (3, 0):
  542. return bytes([chksum])
  543. else:
  544. return chr(chksum)
  545. def __check_frame_checksum__(self, frame):
  546. return self.__calc_chksum__(frame[:-1]) == frame[-1:]