|
@@ -15,12 +15,13 @@ socket_protocol (Socket Protocol)
|
15
|
15
|
|
16
|
16
|
**Submodules:**
|
17
|
17
|
|
|
18
|
+* :class:`socket_protocol.data_storage`
|
18
|
19
|
* :class:`socket_protocol.pure_json_protocol`
|
19
|
20
|
* :class:`socket_protocol.struct_json_protocol`
|
20
|
21
|
|
21
|
22
|
**Unittest:**
|
22
|
23
|
|
23
|
|
- See also the :download:`unittest <../pylibs/socket_protocol/_testresults_/unittest.pdf>` documentation.
|
|
24
|
+ See also the :download:`unittest <socket_protocol/_testresults_/unittest.pdf>` documentation.
|
24
|
25
|
|
25
|
26
|
**Module Documentation:**
|
26
|
27
|
|
|
@@ -52,12 +53,80 @@ For more Information read the sphinx documentation.""" % __name__.replace('_', '
|
52
|
53
|
__INTERPRETER__ = (2, 3)
|
53
|
54
|
"""The Tested Interpreter-Versions"""
|
54
|
55
|
|
|
56
|
+SID_AUTH_REQUEST = 0
|
|
57
|
+"""SID for authentification request"""
|
|
58
|
+SID_AUTH_RESPONSE = 1
|
|
59
|
+"""SID for authentification response"""
|
|
60
|
+DID_AUTH_SEED = 0
|
|
61
|
+"""DID for authentification (seed)"""
|
|
62
|
+DID_AUTH_KEY = 1
|
|
63
|
+"""DID for authentification (key)"""
|
|
64
|
+SID_CHANNEL_NAME_REQUEST = 8
|
|
65
|
+"""SID for channel name exchange request """
|
|
66
|
+SID_CHANNEL_NAME_RESPONSE = 9
|
|
67
|
+"""SID for channel name exchange response"""
|
|
68
|
+DID_CHANNEL_NAME = 0
|
|
69
|
+"""DID for channel name """
|
|
70
|
+SID_READ_REQUEST = 10
|
|
71
|
+"""SID for a read data request"""
|
|
72
|
+SID_READ_RESPONSE = 11
|
|
73
|
+"""SID for read data response"""
|
|
74
|
+SID_WRITE_REQUEST = 20
|
|
75
|
+"""SID for a write data request"""
|
|
76
|
+SID_WRITE_RESPONSE = 21
|
|
77
|
+"""SID for a write data response"""
|
|
78
|
+SID_EXECUTE_REQUEST = 30
|
|
79
|
+"""SID for a execute request"""
|
|
80
|
+SID_EXECUTE_RESPONSE = 31
|
|
81
|
+"""SID for a execute response"""
|
|
82
|
+
|
|
83
|
+STATUS_OKAY = 0
|
|
84
|
+"""Status for 'okay'"""
|
|
85
|
+STATUS_BUFFERING_UNHANDLED_REQUEST = 1
|
|
86
|
+"""Status for 'unhandled request'"""
|
|
87
|
+STATUS_CALLBACK_ERROR = 2
|
|
88
|
+"""Status for 'callback errors'"""
|
|
89
|
+STATUS_AUTH_REQUIRED = 3
|
|
90
|
+"""Status for 'authentification is required'"""
|
|
91
|
+STATUS_SERVICE_OR_DATA_UNKNOWN = 4
|
|
92
|
+"""Status for 'service or data unknown'"""
|
|
93
|
+STATUS_CHECKSUM_ERROR = 5
|
|
94
|
+"""Status for 'checksum error'"""
|
|
95
|
+STATUS_OPERATION_NOT_PERMITTED = 6
|
|
96
|
+"""Status for 'operation not permitted'"""
|
|
97
|
+
|
|
98
|
+AUTH_STATE_UNTRUSTED_CONNECTION = 0
|
|
99
|
+"""Authentification Status for an 'Untrusted Connection'"""
|
|
100
|
+AUTH_STATE_SEED_REQUESTED = 1
|
|
101
|
+"""Authentification Status for 'Seed was requested'"""
|
|
102
|
+AUTH_STATE_SEED_TRANSFERRED = 2
|
|
103
|
+"""Authentification Status for 'Seed has been sent'"""
|
|
104
|
+AUTH_STATE_KEY_TRANSFERRED = 3
|
|
105
|
+"""Authentification Status for 'Key has been sent'"""
|
|
106
|
+AUTH_STATE_TRUSTED_CONNECTION = 4
|
|
107
|
+"""Authentification Status for a 'Trusted Connection'"""
|
|
108
|
+AUTH_STATE__NAMES = {AUTH_STATE_UNTRUSTED_CONNECTION: 'Untrusted Connection',
|
|
109
|
+ AUTH_STATE_SEED_REQUESTED: 'Seed was requested',
|
|
110
|
+ AUTH_STATE_SEED_TRANSFERRED: 'Seed has been sent',
|
|
111
|
+ AUTH_STATE_KEY_TRANSFERRED: 'Key has been sent',
|
|
112
|
+ AUTH_STATE_TRUSTED_CONNECTION: 'Trusted Connection'}
|
|
113
|
+"""Authentification Status names for previous defined authentification states"""
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+class RequestSidExistsError(Exception):
|
|
117
|
+ pass
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+class ResponseSidExistsError(Exception):
|
|
121
|
+ pass
|
|
122
|
+
|
55
|
123
|
|
56
|
124
|
class _callback_storage(dict):
|
57
|
125
|
DEFAULT_CHANNEL_NAME = 'all_others'
|
58
|
126
|
|
59
|
|
- def __init__(self, channel_name):
|
|
127
|
+ def __init__(self, channel_name, log_prefix):
|
60
|
128
|
self.init_channel_name(channel_name)
|
|
129
|
+ self.__log_prefix__ = log_prefix
|
61
|
130
|
dict.__init__(self)
|
62
|
131
|
|
63
|
132
|
def init_channel_name(self, channel_name):
|
|
@@ -67,31 +136,28 @@ class _callback_storage(dict):
|
67
|
136
|
self.logger = logging.getLogger(ROOT_LOGGER_NAME).getChild(__name__ + '.' + channel_name)
|
68
|
137
|
|
69
|
138
|
def get(self, service_id, data_id):
|
70
|
|
- if service_id is not None and data_id is not None:
|
71
|
|
- try:
|
72
|
|
- return self[service_id][data_id]
|
73
|
|
- except KeyError:
|
74
|
|
- pass # nothing to append
|
75
|
|
- if data_id is not None:
|
76
|
|
- try:
|
77
|
|
- return self[None][data_id]
|
78
|
|
- except KeyError:
|
79
|
|
- pass # nothing to append
|
80
|
|
- if service_id is not None:
|
81
|
|
- try:
|
82
|
|
- return self[service_id][None]
|
83
|
|
- except KeyError:
|
84
|
|
- pass # nothing to append
|
85
|
|
- try:
|
|
139
|
+ if dict.get(self, service_id, {}).get(data_id, None) is not None:
|
|
140
|
+ return self[service_id][data_id]
|
|
141
|
+ elif dict.get(self, service_id, {}).get(None, None) is not None:
|
|
142
|
+ return self[service_id][None]
|
|
143
|
+ elif dict.get(self, None, {}).get(data_id, None) is not None:
|
|
144
|
+ return self[None][data_id]
|
|
145
|
+ elif dict.get(self, None, {}).get(None, None) is not None:
|
86
|
146
|
return self[None][None]
|
87
|
|
- except KeyError:
|
88
|
|
- pass # nothing to append
|
89
|
|
- return (None, None, None)
|
|
147
|
+ else:
|
|
148
|
+ return (None, None, None)
|
90
|
149
|
|
91
|
150
|
def add(self, service_id, data_id, callback, *args, **kwargs):
|
92
|
151
|
cb_data = self.get(service_id, data_id)
|
93
|
|
- if cb_data != (None, None, None):
|
94
|
|
- 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__))
|
|
152
|
+ if dict.get(self, service_id, {}).get(data_id, None) is not None:
|
|
153
|
+ if callback is None:
|
|
154
|
+ self.logger.warning("%s Deleting existing callback %s for service_id (%s) and data_id (%s)!", self.__log_prefix__(), repr(cb_data[0].__name__), repr(service_id), repr(data_id))
|
|
155
|
+ del(self[service_id][data_id])
|
|
156
|
+ return
|
|
157
|
+ else:
|
|
158
|
+ self.logger.warning("%s Overwriting existing callback %s for service_id (%s) and data_id (%s) to %s!", self.__log_prefix__(), repr(cb_data[0].__name__), repr(service_id), repr(data_id), repr(callback.__name__))
|
|
159
|
+ else:
|
|
160
|
+ self.logger.debug("%s Adding callback %s for SID=%s and DID=%s", self.__log_prefix__(), repr(callback.__name__), repr(service_id), repr(data_id))
|
95
|
161
|
if service_id not in self:
|
96
|
162
|
self[service_id] = {}
|
97
|
163
|
self[service_id][data_id] = (callback, args, kwargs)
|
|
@@ -99,6 +165,8 @@ class _callback_storage(dict):
|
99
|
165
|
|
100
|
166
|
class data_storage(dict):
|
101
|
167
|
"""
|
|
168
|
+ This is a storage object for socket_protocol messages.
|
|
169
|
+
|
102
|
170
|
:param status: The message status.
|
103
|
171
|
:type status: int
|
104
|
172
|
:param service_id: The Service-ID.
|
|
@@ -107,70 +175,74 @@ class data_storage(dict):
|
107
|
175
|
:type data_id: int
|
108
|
176
|
:param data: The transfered data.
|
109
|
177
|
:type data: any
|
110
|
|
-
|
111
|
|
- This is a storage object for socket_protocol messages.
|
112
|
178
|
"""
|
113
|
179
|
|
114
|
180
|
KEY_STATUS = 'status'
|
115
|
181
|
KEY_SERVICE_ID = 'service_id'
|
116
|
182
|
KEY_DATA_ID = 'data_id'
|
117
|
183
|
KEY_DATA = 'data'
|
|
184
|
+ ALL_KEYS = [KEY_DATA, KEY_DATA_ID, KEY_SERVICE_ID, KEY_STATUS]
|
118
|
185
|
|
119
|
186
|
def __init__(self, *args, **kwargs):
|
120
|
187
|
dict.__init__(self, *args, **kwargs)
|
|
188
|
+ for key in self.ALL_KEYS:
|
|
189
|
+ if key not in self:
|
|
190
|
+ self[key] = None
|
121
|
191
|
|
122
|
192
|
def get_status(self, default=None):
|
123
|
193
|
"""
|
124
|
|
- :param default: The default value, if no data is available.
|
125
|
|
-
|
126
|
194
|
This Method returns the message status.
|
|
195
|
+
|
|
196
|
+ :param default: The default value, if no data is available.
|
127
|
197
|
"""
|
128
|
198
|
return self.get(self.KEY_STATUS, default)
|
129
|
199
|
|
130
|
200
|
def get_service_id(self, default=None):
|
131
|
201
|
"""
|
132
|
|
- :param default: The default value, if no data is available.
|
133
|
|
-
|
134
|
202
|
This Method returns the message Service-ID.
|
|
203
|
+
|
|
204
|
+ :param default: The default value, if no data is available.
|
135
|
205
|
"""
|
136
|
206
|
return self.get(self.KEY_SERVICE_ID, default)
|
137
|
207
|
|
138
|
208
|
def get_data_id(self, default=None):
|
139
|
209
|
"""
|
140
|
|
- :param default: The default value, if no data is available.
|
141
|
|
-
|
142
|
210
|
This Method returns the message Data-ID.
|
|
211
|
+
|
|
212
|
+ :param default: The default value, if no data is available.
|
143
|
213
|
"""
|
144
|
214
|
return self.get(self.KEY_DATA_ID, default)
|
145
|
215
|
|
146
|
216
|
def get_data(self, default=None):
|
147
|
217
|
"""
|
148
|
|
- :param default: The default value, if no data is available.
|
149
|
|
-
|
150
|
218
|
This Method returns the message data.
|
|
219
|
+
|
|
220
|
+ :param default: The default value, if no data is available.
|
151
|
221
|
"""
|
152
|
222
|
return self.get(self.KEY_DATA, default)
|
153
|
223
|
|
154
|
224
|
|
155
|
225
|
class pure_json_protocol(object):
|
156
|
226
|
"""
|
|
227
|
+ This `class` supports to transfer a message and it's data.
|
|
228
|
+
|
157
|
229
|
:param comm_instance: A communication instance.
|
158
|
230
|
:type comm_instance: instance
|
159
|
231
|
:param secret: An optinal secret (e.g. created by ``binascii.hexlify(os.urandom(24))``).
|
160
|
232
|
:type secret: str
|
161
|
|
- :param auto_auth: An optional parameter (True) to enable automatic authentification, otherwise you need to do it manually, if needed.
|
|
233
|
+ :param auto_auth: An optional parameter to enable (True) automatic authentification, otherwise you need to do it manually, if needed.
|
162
|
234
|
:type auto_auth: bool
|
163
|
235
|
:param channel_name: An optional parameter to set a channel name for logging of the communication.
|
164
|
236
|
:type channel_name: str
|
165
|
237
|
|
166
|
|
- .. hint:: This `class` supports to transfer a Service-ID, Data-ID and Data.
|
|
238
|
+ .. hint::
|
167
|
239
|
|
168
|
|
- * The Service-ID is designed to identify the type of the communication (e.g. READ_REQUEST, WRITE_REQUEST, READ_RESPONSE, WRITE_RESPONSE, ...)
|
|
240
|
+ * The Service-ID is designed to identify the type of the communication (e.g. :const:`READ_REQUEST`, :const:`WRITE_REQUEST`, :const:`READ_RESPONSE`, :const:`WRITE_RESPONSE`, ...)
|
169
|
241
|
* The Data-ID is designed to identify the requests / responses using the same Service_ID.
|
170
|
242
|
|
171
|
243
|
.. note:: The :class:`comm_instance` needs to have at least the following interface:
|
172
|
244
|
|
173
|
|
- * A Method :func:`comm_instance.init_channel_name` to set the channel name if needed.
|
|
245
|
+ * A Method :func:`comm_instance.init_channel_name` to set the channel name.
|
174
|
246
|
* A Constant :const:`comm_instance.IS_CLIENT` to identify that the :class:`comm_instance` is a client (True) or a server (False).
|
175
|
247
|
* A Method :func:`comm_instance.is_connected` to identify if the instance is connected (True) or not (False).
|
176
|
248
|
* A Method :func:`comm_instance.reconnect` to initiate a reconnect.
|
|
@@ -187,107 +259,60 @@ class pure_json_protocol(object):
|
187
|
259
|
"""
|
188
|
260
|
DEFAULT_CHANNEL_NAME = 'all_others'
|
189
|
261
|
|
190
|
|
- SID_AUTH_SEED_REQUEST = 1
|
191
|
|
- """SID for requesting a seed for authentification"""
|
192
|
|
- SID_AUTH_KEY_REQUEST = 2
|
193
|
|
- """SID for requesting a key for the given seed"""
|
194
|
|
- SID_AUTH_KEY_CHECK_REQUEST = 3
|
195
|
|
- """SID for request for checking a key"""
|
196
|
|
- SID_AUTH_KEY_CHECK_RESPONSE = 4
|
197
|
|
- """SID for the authentification response"""
|
198
|
|
- SID_CHANNEL_NAME_REQUEST = 5
|
199
|
|
- """SID for requesting a channel name exchange"""
|
200
|
|
- SID_CHANNEL_NAME_RESPONSE = 6
|
201
|
|
- """SID for the channel name response"""
|
202
|
|
- SID_READ_REQUEST = 10
|
203
|
|
- """SID for a read data request"""
|
204
|
|
- SID_READ_RESPONSE = 11
|
205
|
|
- """SID for read data response"""
|
206
|
|
- SID_WRITE_REQUEST = 20
|
207
|
|
- """SID for a write data request"""
|
208
|
|
- SID_WRITE_RESPONSE = 21
|
209
|
|
- """SID for a write data response"""
|
210
|
|
- SID_EXECUTE_REQUEST = 30
|
211
|
|
- """SID for a execute request"""
|
212
|
|
- SID_EXECUTE_RESPONSE = 31
|
213
|
|
- """SID for a execute response"""
|
214
|
|
-
|
215
|
|
- SID__RESPONSE_DICT = {SID_AUTH_SEED_REQUEST: SID_AUTH_KEY_REQUEST,
|
216
|
|
- SID_AUTH_KEY_REQUEST: SID_AUTH_KEY_CHECK_REQUEST,
|
217
|
|
- SID_AUTH_KEY_CHECK_REQUEST: SID_AUTH_KEY_CHECK_RESPONSE,
|
218
|
|
- SID_CHANNEL_NAME_REQUEST: SID_CHANNEL_NAME_RESPONSE,
|
219
|
|
- SID_READ_REQUEST: SID_READ_RESPONSE,
|
220
|
|
- SID_WRITE_REQUEST: SID_WRITE_RESPONSE,
|
221
|
|
- SID_EXECUTE_REQUEST: SID_EXECUTE_RESPONSE}
|
222
|
|
- """Dictionary to get the SID for the response by the key which is the SID for the request"""
|
223
|
|
-
|
224
|
|
- SID__NO_AUTH_LIST = [
|
225
|
|
- SID_AUTH_SEED_REQUEST,
|
226
|
|
- SID_AUTH_KEY_REQUEST,
|
227
|
|
- SID_AUTH_KEY_CHECK_REQUEST,
|
228
|
|
- SID_AUTH_KEY_CHECK_RESPONSE,
|
229
|
|
- SID_CHANNEL_NAME_REQUEST,
|
230
|
|
- SID_CHANNEL_NAME_RESPONSE
|
231
|
|
- ]
|
232
|
|
- """List of SIDs without need of an authentification"""
|
233
|
|
-
|
234
|
|
- STATUS_OKAY = 0
|
235
|
|
- """Status for 'okay'"""
|
236
|
|
- STATUS_BUFFERING_UNHANDLED_REQUEST = 1
|
237
|
|
- """Status for 'unhandled request'"""
|
238
|
|
- STATUS_AUTH_REQUIRED = 2
|
239
|
|
- """Status for 'authentification is required'"""
|
240
|
|
- STATUS_SERVICE_OR_DATA_UNKNOWN = 3
|
241
|
|
- """Status for 'service or data unknown'"""
|
242
|
|
- STATUS_CHECKSUM_ERROR = 4
|
243
|
|
- """Status for 'checksum error'"""
|
244
|
|
- STATUS_OPERATION_NOT_PERMITTED = 5
|
245
|
|
- """Status for 'operation not permitted'"""
|
246
|
|
- STATUS__NAMES = {STATUS_OKAY: 'Okay',
|
247
|
|
- STATUS_BUFFERING_UNHANDLED_REQUEST: 'Request has no callback. Data buffered.',
|
248
|
|
- STATUS_AUTH_REQUIRED: 'Authentification required',
|
249
|
|
- STATUS_SERVICE_OR_DATA_UNKNOWN: 'Service or Data unknown',
|
250
|
|
- STATUS_CHECKSUM_ERROR: 'Checksum Error',
|
251
|
|
- STATUS_OPERATION_NOT_PERMITTED: 'Operation not permitted'}
|
252
|
|
- """Status names for previous defined states"""
|
253
|
|
-
|
254
|
|
- AUTH_STATE_UNKNOWN_CLIENT = 0
|
255
|
|
- """Authentification Status for 'Unknown Client'"""
|
256
|
|
- AUTH_STATE_SEED_REQUESTED = 1
|
257
|
|
- """Authentification Status for 'Seed was requested'"""
|
258
|
|
- AUTH_STATE_SEED_TRANSFERRED = 2
|
259
|
|
- """Authentification Status for 'Seed has been sent'"""
|
260
|
|
- AUTH_STATE_KEY_TRANSFERRED = 3
|
261
|
|
- """Authentification Status for 'Key has been sent'"""
|
262
|
|
- AUTH_STATE_TRUSTED_CLIENT = 4
|
263
|
|
- """Authentification Status for 'Trusted Connection'"""
|
264
|
|
- AUTH_STATE__NAMES = {AUTH_STATE_UNKNOWN_CLIENT: 'Unknown Client',
|
265
|
|
- AUTH_STATE_SEED_REQUESTED: 'Seed was requested',
|
266
|
|
- AUTH_STATE_SEED_TRANSFERRED: 'Seed has been sent',
|
267
|
|
- AUTH_STATE_KEY_TRANSFERRED: 'Key has been sent',
|
268
|
|
- AUTH_STATE_TRUSTED_CLIENT: 'Trusted Connection'}
|
269
|
|
- """Authentification Status names for previous defined authentification states"""
|
270
|
|
-
|
271
|
262
|
def __init__(self, comm_instance, secret=None, auto_auth=False, channel_name=None):
|
272
|
263
|
self.__comm_inst__ = comm_instance
|
273
|
264
|
self.__secret__ = secret
|
274
|
265
|
self.__auto_auth__ = auto_auth
|
275
|
266
|
#
|
276
|
|
- self.__callbacks__ = _callback_storage(channel_name)
|
|
267
|
+ self.__auth_whitelist__ = {}
|
|
268
|
+ self.__sid_response_dict__ = {}
|
|
269
|
+ self.__sid_name_dict__ = {}
|
|
270
|
+ self.__did_name_dict__ = {}
|
|
271
|
+ #
|
|
272
|
+ self.__status_name_dict = {}
|
|
273
|
+ self.add_status(STATUS_OKAY, 'okay')
|
|
274
|
+ self.add_status(STATUS_BUFFERING_UNHANDLED_REQUEST, 'no callback for service, data buffered.')
|
|
275
|
+ self.add_status(STATUS_CALLBACK_ERROR, 'callback error.')
|
|
276
|
+ self.add_status(STATUS_AUTH_REQUIRED, 'authentification required')
|
|
277
|
+ self.add_status(STATUS_SERVICE_OR_DATA_UNKNOWN, 'service or data unknown')
|
|
278
|
+ self.add_status(STATUS_CHECKSUM_ERROR, 'checksum error')
|
|
279
|
+ self.add_status(STATUS_OPERATION_NOT_PERMITTED, 'operation not permitted')
|
|
280
|
+ #
|
|
281
|
+ self.__callbacks__ = _callback_storage(channel_name, self.__log_prefix__)
|
277
|
282
|
self.__init_channel_name__(channel_name)
|
278
|
283
|
#
|
279
|
284
|
self.__clean_receive_buffer__()
|
280
|
|
- self.__callbacks__.add(self.SID_AUTH_SEED_REQUEST, 0, self.__authentificate_create_seed__)
|
281
|
|
- self.__callbacks__.add(self.SID_AUTH_KEY_REQUEST, 0, self.__authentificate_create_key__)
|
282
|
|
- self.__callbacks__.add(self.SID_AUTH_KEY_CHECK_REQUEST, 0, self.__authentificate_check_key__)
|
283
|
|
- self.__callbacks__.add(self.SID_AUTH_KEY_CHECK_RESPONSE, 0, self.__authentificate_process_feedback__)
|
284
|
|
- self.__callbacks__.add(self.SID_CHANNEL_NAME_REQUEST, 0, self.__channel_name_request__)
|
285
|
|
- self.__callbacks__.add(self.SID_CHANNEL_NAME_RESPONSE, 0, self.__channel_name_response__)
|
|
285
|
+
|
|
286
|
+ self.add_service(SID_AUTH_REQUEST, SID_AUTH_RESPONSE, 'authentification request', 'authentification response')
|
|
287
|
+ self.add_data((SID_AUTH_REQUEST, SID_AUTH_RESPONSE), DID_AUTH_SEED, 'seed')
|
|
288
|
+ self.add_data(SID_AUTH_REQUEST, DID_AUTH_KEY, 'key')
|
|
289
|
+ self.add_data(SID_AUTH_RESPONSE, DID_AUTH_KEY, 'key')
|
|
290
|
+ self.add_msg_to_auth_whitelist_(SID_AUTH_REQUEST, DID_AUTH_SEED)
|
|
291
|
+ self.add_msg_to_auth_whitelist_(SID_AUTH_RESPONSE, DID_AUTH_SEED)
|
|
292
|
+ self.add_msg_to_auth_whitelist_(SID_AUTH_REQUEST, DID_AUTH_KEY)
|
|
293
|
+ self.add_msg_to_auth_whitelist_(SID_AUTH_RESPONSE, DID_AUTH_KEY)
|
|
294
|
+ self.__callbacks__.add(SID_AUTH_REQUEST, DID_AUTH_SEED, self.__authentificate_create_seed__)
|
|
295
|
+ self.__callbacks__.add(SID_AUTH_RESPONSE, DID_AUTH_SEED, self.__authentificate_create_key__)
|
|
296
|
+ self.__callbacks__.add(SID_AUTH_REQUEST, DID_AUTH_KEY, self.__authentificate_check_key__)
|
|
297
|
+ self.__callbacks__.add(SID_AUTH_RESPONSE, DID_AUTH_KEY, self.__authentificate_process_feedback__)
|
286
|
298
|
self.__authentification_state_reset__()
|
|
299
|
+
|
|
300
|
+ self.add_service(SID_CHANNEL_NAME_REQUEST, SID_CHANNEL_NAME_RESPONSE, 'channel name request', 'channel name response')
|
|
301
|
+ self.add_data((SID_CHANNEL_NAME_REQUEST, SID_CHANNEL_NAME_RESPONSE), DID_CHANNEL_NAME, 'name')
|
|
302
|
+ self.add_msg_to_auth_whitelist_(SID_CHANNEL_NAME_REQUEST, DID_CHANNEL_NAME)
|
|
303
|
+ self.add_msg_to_auth_whitelist_(SID_CHANNEL_NAME_RESPONSE, DID_CHANNEL_NAME)
|
|
304
|
+ self.__callbacks__.add(SID_CHANNEL_NAME_REQUEST, DID_CHANNEL_NAME, self.__channel_name_request__)
|
|
305
|
+ self.__callbacks__.add(SID_CHANNEL_NAME_RESPONSE, DID_CHANNEL_NAME, self.__channel_name_response__)
|
|
306
|
+
|
|
307
|
+ self.add_service(SID_READ_REQUEST, SID_READ_RESPONSE, 'read data request', 'read data response')
|
|
308
|
+ self.add_service(SID_WRITE_REQUEST, SID_WRITE_RESPONSE, 'write data request', 'write data response')
|
|
309
|
+ self.add_service(SID_EXECUTE_REQUEST, SID_EXECUTE_RESPONSE, 'execute request', 'execute response')
|
|
310
|
+
|
287
|
311
|
self.__seed__ = None
|
288
|
312
|
self.__comm_inst__.register_callback(self.__data_available_callback__)
|
289
|
313
|
self.__comm_inst__.register_connect_callback(self.__connection_established__)
|
290
|
314
|
self.__comm_inst__.register_disconnect_callback(self.__authentification_state_reset__)
|
|
315
|
+ logger.info('%s Initialisation finished.', self.__log_prefix__())
|
291
|
316
|
|
292
|
317
|
def __analyse_frame__(self, frame):
|
293
|
318
|
if sys.version_info >= (3, 0):
|
|
@@ -298,39 +323,35 @@ class pure_json_protocol(object):
|
298
|
323
|
def __authentificate_check_key__(self, msg):
|
299
|
324
|
key = msg.get_data()
|
300
|
325
|
if key == self.__authentificate_salt_and_hash__(self.__seed__):
|
301
|
|
- self.__authentification_state__ = self.AUTH_STATE_TRUSTED_CLIENT
|
302
|
|
- self.logger.info("%s Got correct key, sending positive authentification feedback", self.__log_prefix__())
|
303
|
|
- return self.STATUS_OKAY, True
|
|
326
|
+ self.__authentification_state__ = AUTH_STATE_TRUSTED_CONNECTION
|
|
327
|
+ return STATUS_OKAY, True
|
304
|
328
|
else:
|
305
|
|
- self.__authentification_state__ = self.AUTH_STATE_UNKNOWN_CLIENT
|
306
|
|
- self.logger.info("%s Got incorrect key, sending negative authentification feedback", self.__log_prefix__())
|
307
|
|
- return self.STATUS_OKAY, False
|
|
329
|
+ self.__authentification_state__ = AUTH_STATE_UNTRUSTED_CONNECTION
|
|
330
|
+ return STATUS_OKAY, False
|
308
|
331
|
|
309
|
332
|
def __authentificate_create_key__(self, msg):
|
310
|
|
- self.logger.info("%s Got seed, sending key for authentification", self.__log_prefix__())
|
311
|
|
- self.__authentification_state__ = self.AUTH_STATE_KEY_TRANSFERRED
|
|
333
|
+ self.__authentification_state__ = AUTH_STATE_KEY_TRANSFERRED
|
312
|
334
|
seed = msg.get_data()
|
313
|
335
|
key = self.__authentificate_salt_and_hash__(seed)
|
314
|
|
- return self.STATUS_OKAY, key
|
|
336
|
+ self.send(SID_AUTH_REQUEST, DID_AUTH_KEY, key)
|
315
|
337
|
|
316
|
338
|
def __authentificate_create_seed__(self, msg):
|
317
|
|
- self.logger.info("%s Got seed request, sending seed for authentification", self.__log_prefix__())
|
318
|
|
- self.__authentification_state__ = self.AUTH_STATE_SEED_TRANSFERRED
|
|
339
|
+ self.__authentification_state__ = AUTH_STATE_SEED_TRANSFERRED
|
319
|
340
|
if sys.version_info >= (3, 0):
|
320
|
341
|
self.__seed__ = binascii.hexlify(os.urandom(32)).decode('utf-8')
|
321
|
342
|
else:
|
322
|
343
|
self.__seed__ = binascii.hexlify(os.urandom(32))
|
323
|
|
- return self.STATUS_OKAY, self.__seed__
|
|
344
|
+ return STATUS_OKAY, self.__seed__
|
324
|
345
|
|
325
|
346
|
def __authentificate_process_feedback__(self, msg):
|
326
|
347
|
feedback = msg.get_data()
|
327
|
348
|
if feedback:
|
328
|
|
- self.__authentification_state__ = self.AUTH_STATE_TRUSTED_CLIENT
|
|
349
|
+ self.__authentification_state__ = AUTH_STATE_TRUSTED_CONNECTION
|
329
|
350
|
self.logger.info("%s Got positive authentification feedback", self.__log_prefix__())
|
330
|
351
|
else:
|
331
|
|
- self.__authentification_state__ = self.AUTH_STATE_UNKNOWN_CLIENT
|
|
352
|
+ self.__authentification_state__ = AUTH_STATE_UNTRUSTED_CONNECTION
|
332
|
353
|
self.logger.warning("%s Got negative authentification feedback", self.__log_prefix__())
|
333
|
|
- return self.STATUS_OKAY, None
|
|
354
|
+ return STATUS_OKAY, None
|
334
|
355
|
|
335
|
356
|
def __authentificate_salt_and_hash__(self, seed):
|
336
|
357
|
if sys.version_info >= (3, 0):
|
|
@@ -339,8 +360,11 @@ class pure_json_protocol(object):
|
339
|
360
|
return hashlib.sha512(seed.encode('utf-8') + self.__secret__.encode('utf-8')).hexdigest()
|
340
|
361
|
|
341
|
362
|
def __authentification_state_reset__(self):
|
342
|
|
- self.logger.info("%s Resetting authentification state to AUTH_STATE_UNKNOWN_CLIENT", self.__log_prefix__())
|
343
|
|
- self.__authentification_state__ = self.AUTH_STATE_UNKNOWN_CLIENT
|
|
363
|
+ self.logger.info("%s Resetting authentification state to AUTH_STATE_UNTRUSTED_CONNECTION", self.__log_prefix__())
|
|
364
|
+ self.__authentification_state__ = AUTH_STATE_UNTRUSTED_CONNECTION
|
|
365
|
+
|
|
366
|
+ def __authentification_required__(self, service_id, data_id):
|
|
367
|
+ return data_id not in self.__auth_whitelist__.get(service_id, [])
|
344
|
368
|
|
345
|
369
|
def __buffer_received_data__(self, msg):
|
346
|
370
|
if not msg.get_service_id() in self.__msg_buffer__:
|
|
@@ -371,12 +395,12 @@ class pure_json_protocol(object):
|
371
|
395
|
if self.__channel_name__ is None and data is not None:
|
372
|
396
|
self.__init_channel_name__(data)
|
373
|
397
|
self.logger.info('%s channel name is now %s', self.__log_prefix__(), repr(self.__channel_name__))
|
374
|
|
- return self.STATUS_OKAY, None
|
|
398
|
+ return STATUS_OKAY, None
|
375
|
399
|
|
376
|
400
|
def __channel_name_request__(self, msg):
|
377
|
401
|
data = msg.get_data()
|
378
|
402
|
if data is None:
|
379
|
|
- return self.STATUS_OKAY, self.__channel_name__
|
|
403
|
+ return STATUS_OKAY, self.__channel_name__
|
380
|
404
|
else:
|
381
|
405
|
prev_channel_name = self.__channel_name__
|
382
|
406
|
self.__init_channel_name__(data)
|
|
@@ -384,7 +408,7 @@ class pure_json_protocol(object):
|
384
|
408
|
self.logger.warning('%s overwriting user defined channel name from %s to %s', self.__log_prefix__(), repr(prev_channel_name), repr(data))
|
385
|
409
|
elif prev_channel_name is None:
|
386
|
410
|
self.logger.info('%s channel name is now %s', self.__log_prefix__(), repr(self.__channel_name__))
|
387
|
|
- return self.STATUS_OKAY, None
|
|
411
|
+ return STATUS_OKAY, None
|
388
|
412
|
|
389
|
413
|
def __check_frame_checksum__(self, frame):
|
390
|
414
|
return self.__calc_chksum__(frame[:-4]) == frame[-4:]
|
|
@@ -393,63 +417,77 @@ class pure_json_protocol(object):
|
393
|
417
|
self.logger.debug("%s Cleaning up receive-buffer", self.__log_prefix__())
|
394
|
418
|
self.__msg_buffer__ = {}
|
395
|
419
|
|
|
420
|
+ def __connection_established__(self):
|
|
421
|
+ self.__clean_receive_buffer__()
|
|
422
|
+ if self.__comm_inst__.IS_CLIENT:
|
|
423
|
+ self.send(SID_CHANNEL_NAME_REQUEST, 0, self.__channel_name__)
|
|
424
|
+ if self.__auto_auth__ and self.__comm_inst__.IS_CLIENT and self.__secret__ is not None:
|
|
425
|
+ self.authentificate()
|
|
426
|
+
|
396
|
427
|
def __data_available_callback__(self, comm_inst):
|
397
|
428
|
frame = comm_inst.receive()
|
|
429
|
+ msg = self.__analyse_frame__(frame)
|
398
|
430
|
if not self.__check_frame_checksum__(frame):
|
399
|
|
- self.logger.warning("%s Received message has a wrong checksum and will be ignored: %s.", self.__log_prefix__(), stringtools.hexlify(frame))
|
|
431
|
+ # Wrong Checksum
|
|
432
|
+ self.logger.warning("%s RX <- Received message has a wrong checksum. Message will be ignored.", self.__log_prefix__())
|
|
433
|
+ return # No response needed
|
|
434
|
+ elif not self.check_authentification_state() and self.__authentification_required__(msg.get_service_id(), msg.get_data_id()):
|
|
435
|
+ # Authentification required
|
|
436
|
+ if msg.get_service_id() in self.__sid_response_dict__.keys():
|
|
437
|
+ self.logger.warning("%s RX <- Authentification is required. Just sending negative response.", self.__log_prefix__())
|
|
438
|
+ status = STATUS_AUTH_REQUIRED
|
|
439
|
+ data = None
|
|
440
|
+ else:
|
|
441
|
+ self.logger.warning("%s RX <- Authentification is required. Message will be ignored.", self.__log_prefix__())
|
|
442
|
+ return # No response needed
|
400
|
443
|
else:
|
401
|
|
- msg = self.__analyse_frame__(frame)
|
|
444
|
+ # Valid message
|
402
|
445
|
self.logger.info(
|
403
|
|
- '%s RX <- status: %s, service_id: %s, data_id: %s, data: "%s"',
|
|
446
|
+ '%s RX <- %s, %s, data: "%s"',
|
404
|
447
|
self.__log_prefix__(),
|
405
|
|
- repr(msg.get_status()),
|
406
|
|
- repr(msg.get_service_id()),
|
407
|
|
- repr(msg.get_data_id()),
|
|
448
|
+ self.__get_message_name__(msg.get_service_id(), msg.get_data_id()),
|
|
449
|
+ self.__get_status_name__(msg.get_status()),
|
408
|
450
|
repr(msg.get_data())
|
409
|
451
|
)
|
|
452
|
+ if msg.get_status() not in [STATUS_OKAY]:
|
|
453
|
+ self.logger.warning("%s RX <- Message has a peculiar status: %s", self.__log_prefix__(), self.__get_status_name__(msg.get_status()))
|
410
|
454
|
callback, args, kwargs = self.__callbacks__.get(msg.get_service_id(), msg.get_data_id())
|
411
|
|
- if msg.get_service_id() in self.SID__RESPONSE_DICT.keys():
|
|
455
|
+ if msg.get_service_id() in self.__sid_response_dict__.keys():
|
412
|
456
|
#
|
413
|
457
|
# REQUEST RECEIVED
|
414
|
458
|
#
|
415
|
|
- if self.__secret__ is not None and not self.check_authentification_state() and msg.get_service_id() not in self.SID__NO_AUTH_LIST:
|
416
|
|
- status = self.STATUS_AUTH_REQUIRED
|
417
|
|
- data = None
|
418
|
|
- 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!'))
|
419
|
|
- elif callback is None:
|
420
|
|
- self.logger.warning("%s Received message with no registered callback. Sending negative response.", self.__log_prefix__())
|
421
|
|
- status = self.STATUS_BUFFERING_UNHANDLED_REQUEST
|
|
459
|
+ if callback is None:
|
|
460
|
+ self.logger.warning("%s RX <- Message with no registered callback. Sending negative response.", self.__log_prefix__())
|
|
461
|
+ status = STATUS_BUFFERING_UNHANDLED_REQUEST
|
422
|
462
|
data = None
|
423
|
463
|
else:
|
424
|
464
|
try:
|
425
|
|
- self.logger.debug("%s Executing callback %s to process received data", self.__log_prefix__(), callback.__name__)
|
|
465
|
+ self.logger.debug("%s RX <- Executing callback %s to process received data", self.__log_prefix__(), callback.__name__)
|
426
|
466
|
status, data = callback(msg, *args, **kwargs)
|
427
|
|
- except TypeError:
|
428
|
|
- 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())))
|
429
|
|
- self.send(self.SID__RESPONSE_DICT[msg.get_service_id()], msg.get_data_id(), data, status=status)
|
|
467
|
+ except Exception:
|
|
468
|
+ logger.error('{lp} RX <- Exception raised. Check callback {callback_name} and it\'s return values for service_id {service_id} and data_id {data_id}'.format(lp=self.__log_prefix__(), callback_name=callback.__name__, service_id=repr(msg.get_service_id()), data_id=repr(msg.get_data_id())))
|
|
469
|
+ status = STATUS_CALLBACK_ERROR
|
|
470
|
+ data = None
|
430
|
471
|
else:
|
431
|
472
|
#
|
432
|
473
|
# RESPONSE RECEIVED
|
433
|
474
|
#
|
434
|
|
- if msg.get_status() not in [self.STATUS_OKAY]:
|
435
|
|
- self.logger.warning("%s Received message has a peculiar status: %s", self.__log_prefix__(), self.STATUS__NAMES.get(msg.get_status(), 'Unknown status response!'))
|
436
|
475
|
if callback is None:
|
437
|
|
- status = self.STATUS_OKAY
|
438
|
|
- data = None
|
439
|
476
|
self.__buffer_received_data__(msg)
|
440
|
477
|
else:
|
441
|
|
- try:
|
442
|
|
- self.logger.debug("%s Executing callback %s to process received data", self.__log_prefix__(), callback.__name__)
|
443
|
|
- status, data = callback(msg, *args, **kwargs)
|
444
|
|
- except TypeError:
|
445
|
|
- 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())))
|
|
478
|
+ self.logger.debug("%s Executing callback %s to process received data", self.__log_prefix__(), callback.__name__)
|
|
479
|
+ callback(msg, *args, **kwargs)
|
|
480
|
+ return # No response needed
|
|
481
|
+ self.send(self.__sid_response_dict__[msg.get_service_id()], msg.get_data_id(), data, status=status)
|
446
|
482
|
|
447
|
|
- def __connection_established__(self):
|
448
|
|
- self.__clean_receive_buffer__()
|
449
|
|
- if not self.__comm_inst__.IS_CLIENT:
|
450
|
|
- self.send(self.SID_CHANNEL_NAME_REQUEST, 0, self.__channel_name__)
|
451
|
|
- if self.__auto_auth__ and self.__comm_inst__.IS_CLIENT and self.__secret__ is not None:
|
452
|
|
- self.authentificate()
|
|
483
|
+ def __get_message_name__(self, service_id, data_id):
|
|
484
|
+ return 'service: %s, data_id: %s' % (
|
|
485
|
+ self.__sid_name_dict__.get(service_id, repr(service_id)),
|
|
486
|
+ self.__did_name_dict__.get(service_id, {}).get(data_id, repr(data_id)),
|
|
487
|
+ )
|
|
488
|
+
|
|
489
|
+ def __get_status_name__(self, status):
|
|
490
|
+ return 'status: %s' % (self.__status_name_dict.get(status, 'unknown status: %s' % repr(status)))
|
453
|
491
|
|
454
|
492
|
def __init_channel_name__(self, channel_name):
|
455
|
493
|
self.__comm_inst__.init_channel_name(channel_name)
|
|
@@ -460,47 +498,119 @@ class pure_json_protocol(object):
|
460
|
498
|
self.logger = logging.getLogger(ROOT_LOGGER_NAME).getChild(__name__ + '.' + channel_name)
|
461
|
499
|
|
462
|
500
|
def __log_prefix__(self):
|
463
|
|
- return ' SP client:' if self.__comm_inst__.IS_CLIENT else ' SP server:'
|
|
501
|
+ return 'SP client:' if self.__comm_inst__.IS_CLIENT else 'SP server:'
|
464
|
502
|
|
465
|
503
|
def __mk_msg__(self, status, service_id, data_id, data):
|
466
|
504
|
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})
|
467
|
505
|
|
|
506
|
+ def add_data(self, service_id, data_id, name):
|
|
507
|
+ """
|
|
508
|
+ Method to add a name for a specific message.
|
|
509
|
+
|
|
510
|
+ :param service_id: The Service-ID of the message. See class definitions starting with ``SID_``.
|
|
511
|
+ :type service_id: int or list of ints
|
|
512
|
+ :param data_id: The Data-ID of the message.
|
|
513
|
+ :type data_id: int
|
|
514
|
+ :param name: The Name for the transfered message.
|
|
515
|
+ :type name: str
|
|
516
|
+ """
|
|
517
|
+ try:
|
|
518
|
+ iter(service_id)
|
|
519
|
+ except Exception:
|
|
520
|
+ service_id = (service_id, )
|
|
521
|
+
|
|
522
|
+ for sid in service_id:
|
|
523
|
+ if sid not in self.__did_name_dict__:
|
|
524
|
+ self.__did_name_dict__[sid] = {}
|
|
525
|
+ self.__did_name_dict__[sid][data_id] = name
|
|
526
|
+
|
|
527
|
+ def add_msg_to_auth_whitelist_(self, service_id, data_id):
|
|
528
|
+ """
|
|
529
|
+ Method to add a specific message to the list, where no authentification is required.
|
|
530
|
+
|
|
531
|
+ :param service_id: The Service-ID of the message. See class definitions starting with ``SID_``.
|
|
532
|
+ :type service_id: int
|
|
533
|
+ :param data_id: The Data-ID of the message.
|
|
534
|
+ :type data_id: int
|
|
535
|
+ """
|
|
536
|
+ if service_id not in self.__auth_whitelist__:
|
|
537
|
+ self.__auth_whitelist__[service_id] = []
|
|
538
|
+ self.__auth_whitelist__[service_id].append(data_id)
|
|
539
|
+ logger.debug('%s Adding Message (%s) to the authentification whitelist', self.__log_prefix__(), self.__get_message_name__(service_id, data_id))
|
|
540
|
+
|
|
541
|
+ def add_service(self, req_sid, resp_sid, req_name=None, resp_name=None):
|
|
542
|
+ """
|
|
543
|
+ Method to add a Service defined by Request- and Response Serivce-ID.
|
|
544
|
+
|
|
545
|
+ :param req_sid: The Request Service-ID.
|
|
546
|
+ :type req_sid: int
|
|
547
|
+ :param resp_sid: The Response Service-ID.
|
|
548
|
+ :type resp_sid: int
|
|
549
|
+ """
|
|
550
|
+ if req_sid in self.__sid_response_dict__:
|
|
551
|
+ logger.error('%s Service with Request-SID=%d and Response-SID=%d not added, because request SID is already registered', self.__log_prefix__(), req_sid, resp_sid)
|
|
552
|
+ raise RequestSidExistsError("Request for this Service is already registered")
|
|
553
|
+ elif resp_sid in self.__sid_response_dict__.values():
|
|
554
|
+ logger.error('%s Service with Request-SID=%d and Response-SID=%d not added, because response SID is already registered', self.__log_prefix__(), req_sid, resp_sid)
|
|
555
|
+ raise ResponseSidExistsError("Response for this Service is already registered")
|
|
556
|
+ else:
|
|
557
|
+ self.__sid_response_dict__[req_sid] = resp_sid
|
|
558
|
+ if req_name is not None:
|
|
559
|
+ self.__sid_name_dict__[req_sid] = req_name
|
|
560
|
+ if resp_name is not None:
|
|
561
|
+ self.__sid_name_dict__[resp_sid] = resp_name
|
|
562
|
+ logger.debug('%s Adding Service with Request=%s and Response=%s', self.__log_prefix__(), req_name or repr(req_sid), resp_name or repr(resp_sid))
|
|
563
|
+
|
|
564
|
+ def add_status(self, status, name):
|
|
565
|
+ """
|
|
566
|
+ Method to add a name for a status.
|
|
567
|
+
|
|
568
|
+ :param status: The Status. See class definitions starting with ``STATUS_``.
|
|
569
|
+ :type status: int
|
|
570
|
+ :param name: The Name for the Status.
|
|
571
|
+ :type name: str
|
|
572
|
+ """
|
|
573
|
+ self.__status_name_dict[status] = name
|
|
574
|
+
|
468
|
575
|
def authentificate(self, timeout=2):
|
469
|
576
|
"""
|
|
577
|
+ This method authetificates the client at the server.
|
|
578
|
+
|
470
|
579
|
:param timeout: The timeout for the authentification (requesting seed, sending key and getting authentification_feedback).
|
471
|
580
|
:type timeout: float
|
472
|
581
|
:returns: True, if authentification was successfull; False, if not.
|
473
|
582
|
:rtype: bool
|
474
|
583
|
|
475
|
|
- This method authetificates the client at the server.
|
476
|
|
-
|
477
|
584
|
.. note:: An authentification will only processed, if a secret had been given on initialisation.
|
478
|
585
|
|
479
|
586
|
.. note:: Client and Server needs to use the same secret.
|
480
|
587
|
"""
|
481
|
588
|
if self.__secret__ is not None:
|
482
|
|
- self.__authentification_state__ = self.AUTH_STATE_SEED_REQUESTED
|
483
|
|
- self.logger.info("%s Requesting seed for authentification", self.__log_prefix__())
|
484
|
|
- self.send(self.SID_AUTH_SEED_REQUEST, 0, None)
|
|
589
|
+ self.__authentification_state__ = AUTH_STATE_SEED_REQUESTED
|
|
590
|
+ self.send(SID_AUTH_REQUEST, DID_AUTH_SEED, None)
|
485
|
591
|
cnt = 0
|
486
|
592
|
while cnt < timeout * 10:
|
487
|
593
|
time.sleep(0.1)
|
488
|
|
- if self.__authentification_state__ == self.AUTH_STATE_TRUSTED_CLIENT:
|
|
594
|
+ if self.__authentification_state__ == AUTH_STATE_TRUSTED_CONNECTION:
|
489
|
595
|
return True
|
490
|
|
- elif self.__authentification_state__ == self.AUTH_STATE_UNKNOWN_CLIENT:
|
|
596
|
+ elif self.__authentification_state__ == AUTH_STATE_UNTRUSTED_CONNECTION:
|
491
|
597
|
break
|
492
|
598
|
cnt += 1
|
493
|
599
|
return False
|
494
|
600
|
|
495
|
601
|
def check_authentification_state(self):
|
496
|
602
|
"""
|
|
603
|
+ This Method return the Authitification State as boolean value.
|
|
604
|
+
|
497
|
605
|
:return: True, if authentification state is okay, otherwise False
|
498
|
606
|
:rtype: bool
|
499
|
607
|
"""
|
500
|
|
- return self.__authentification_state__ == self.AUTH_STATE_TRUSTED_CLIENT
|
|
608
|
+ return self.__secret__ is None or self.__authentification_state__ == AUTH_STATE_TRUSTED_CONNECTION
|
501
|
609
|
|
502
|
610
|
def connection_established(self):
|
503
|
611
|
"""
|
|
612
|
+ This Method returns the Connection state including authentification as a boolean value.
|
|
613
|
+
|
504
|
614
|
:return: True, if the connection is established (incl. authentification, if a secret has been given)
|
505
|
615
|
:rtype: bool
|
506
|
616
|
"""
|
|
@@ -508,15 +618,17 @@ class pure_json_protocol(object):
|
508
|
618
|
|
509
|
619
|
def is_connected(self):
|
510
|
620
|
"""
|
|
621
|
+ This Methods returns Connection state of the Communication Instance :func:`comm_instance.is_connected`.
|
|
622
|
+
|
511
|
623
|
:return: True if the :class:`comm_instance` is connected, otherwise False..
|
512
|
624
|
:rtype: bool
|
513
|
|
-
|
514
|
|
- This methods returns the return value of :func:`comm_instance.is_connected`.
|
515
|
625
|
"""
|
516
|
626
|
return self.__comm_inst__.is_connected()
|
517
|
627
|
|
518
|
628
|
def receive(self, service_id, data_id, timeout=1):
|
519
|
629
|
"""
|
|
630
|
+ This Method returns a message object for a defined message or None, if this message is not available after the given timout.
|
|
631
|
+
|
520
|
632
|
:param service_id: The Service-ID for the message. See class definitions starting with ``SID_``.
|
521
|
633
|
:type service_id: int
|
522
|
634
|
:param data_id: The Data-ID for the message.
|
|
@@ -547,37 +659,44 @@ class pure_json_protocol(object):
|
547
|
659
|
|
548
|
660
|
def register_callback(self, service_id, data_id, callback, *args, **kwargs):
|
549
|
661
|
"""
|
|
662
|
+ This method registers a callback for the given parameters. Giving ``None`` means, that all Service-IDs or all Data-IDs are used.
|
|
663
|
+ If a message hitting these parameters has been received, the callback will be executed.
|
|
664
|
+
|
550
|
665
|
:param service_id: The Service-ID for the message. See class definitions starting with ``SID_``.
|
551
|
666
|
:type service_id: int
|
552
|
667
|
:param data_id: The Data-ID for the message.
|
553
|
668
|
:type data_id: int
|
554
|
|
- :returns: True, if registration was successfull; False, if registration failed (e.g. existance of a callback for this configuration)
|
555
|
|
- :rtype: bool
|
556
|
|
-
|
557
|
|
- This method registers a callback for the given parameters. Givin ``None`` means, that all Service-IDs or all Data-IDs are used.
|
558
|
|
- If a message hitting these parameters has been received, the callback will be executed.
|
559
|
669
|
|
560
|
670
|
.. note:: The :func:`callback` is priorised in the following order:
|
561
|
671
|
|
562
|
672
|
* Callbacks with defined Service-ID and Data-ID.
|
563
|
|
- * Callbacks with a defined Data-ID.
|
564
|
|
- * Callbacks with a defined Service-ID.
|
565
|
|
- * Unspecific Callbacks
|
|
673
|
+ * Callbacks with a defined Service-ID and all Data-IDs.
|
|
674
|
+ * Callbacks with a defined Data-ID and all Service-IDs.
|
|
675
|
+ * Unspecific Callbacks.
|
566
|
676
|
|
567
|
677
|
.. note:: The :func:`callback` is executed with these arguments:
|
568
|
678
|
|
569
|
|
- :param msg: A :class:`data_storage` object containing all message information.
|
570
|
|
- :returns: (:const:`status`, :const:`response_data`)
|
|
679
|
+ **Parameters given at the callback call:**
|
571
|
680
|
|
572
|
|
- * :const:`status`: A status as defined as a constant of this class :const:`STA_*` to be used as status for the response.
|
|
681
|
+ * The first Arguments is the received message as :class:`data_storage` object.
|
|
682
|
+ * Further arguments given at registration.
|
|
683
|
+ * Further keyword arguments given at registration.
|
|
684
|
+
|
|
685
|
+ **Return value of the callback:**
|
|
686
|
+
|
|
687
|
+ If the Callback is a Request Callback for a registered Service, the return value has to be a tuple or list with
|
|
688
|
+
|
|
689
|
+ * :const:`response_status`: The response status (see class definitions starting with :const:`STA_*`.
|
573
|
690
|
* :const:`response_data`: A JSON iterable object to be used as data for the response.
|
574
|
691
|
|
575
|
|
- .. 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.
|
|
692
|
+ .. note:: Only registered services will respond via the callbacks return values with the same data_id.
|
576
|
693
|
"""
|
577
|
694
|
self.__callbacks__.add(service_id, data_id, callback, *args, **kwargs)
|
578
|
695
|
|
579
|
|
- def send(self, service_id, data_id, data, status=STATUS_OKAY, timeout=2, log_lvl=logging.INFO):
|
|
696
|
+ def send(self, service_id, data_id, data, status=STATUS_OKAY, timeout=2):
|
580
|
697
|
"""
|
|
698
|
+ This methods sends out a message with the given content.
|
|
699
|
+
|
581
|
700
|
:param service_id: The Service-ID for the message. See class definitions starting with ``SERVICE_``.
|
582
|
701
|
:type service_id: int
|
583
|
702
|
:param data_id: The Data-ID for the message.
|
|
@@ -588,15 +707,22 @@ class pure_json_protocol(object):
|
588
|
707
|
:type status: int
|
589
|
708
|
:param timeout: The timeout for sending data (e.g. time to establish new connection).
|
590
|
709
|
:type timeout: float
|
591
|
|
- :param rx_log_lvl: The log level to log outgoing TX-data
|
592
|
|
- :type rx_log_lvl: int
|
593
|
710
|
:return: True if data had been sent, otherwise False.
|
594
|
711
|
:rtype: bool
|
595
|
|
-
|
596
|
|
- This methods sends out a message with the given content.
|
597
|
712
|
"""
|
598
|
|
- 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))
|
599
|
|
- return self.__comm_inst__.send(self.__build_frame__(service_id, data_id, data, status), timeout=timeout, log_lvl=logging.DEBUG)
|
|
713
|
+ if (self.check_authentification_state() or not self.__authentification_required__(service_id, data_id)) or (service_id in self.__sid_response_dict__.values() and status == STATUS_AUTH_REQUIRED and data is None):
|
|
714
|
+ self.logger.info(
|
|
715
|
+ '%s TX <- %s, %s, data: "%s"',
|
|
716
|
+ self.__log_prefix__(),
|
|
717
|
+ self.__get_message_name__(service_id, data_id),
|
|
718
|
+ self.__get_status_name__(status),
|
|
719
|
+ repr(data)
|
|
720
|
+ )
|
|
721
|
+ return self.__comm_inst__.send(self.__build_frame__(service_id, data_id, data, status), timeout=timeout, log_lvl=logging.DEBUG)
|
|
722
|
+ else:
|
|
723
|
+ # Authentification required
|
|
724
|
+ self.logger.warning("%s TX -> Authentification is required. Message %s, %s, data: %s will be ignored.", self.__log_prefix__(), self.__get_message_name__(service_id, data_id), self.__get_status_name__(status), repr(data))
|
|
725
|
+ return False
|
600
|
726
|
|
601
|
727
|
|
602
|
728
|
class struct_json_protocol(pure_json_protocol):
|
|
@@ -617,7 +743,7 @@ class struct_json_protocol(pure_json_protocol):
|
617
|
743
|
data = json.loads(frame[12:-1])
|
618
|
744
|
return self.__mk_msg__(status, service_id, data_id, data)
|
619
|
745
|
|
620
|
|
- def __build_frame__(self, service_id, data_id, data, status=pure_json_protocol.STATUS_OKAY):
|
|
746
|
+ def __build_frame__(self, service_id, data_id, data, status=STATUS_OKAY):
|
621
|
747
|
frame = struct.pack('>III', status, service_id, data_id)
|
622
|
748
|
if sys.version_info >= (3, 0):
|
623
|
749
|
frame += bytes(json.dumps(data), 'utf-8')
|