Python Library Socket Protocol
Nelze vybrat více než 25 témat Téma musí začínat písmenem nebo číslem, může obsahovat pomlčky („-“) a může být dlouhé až 35 znaků.

__init__.py 29KB

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