/src/open62541_15/src/server/ua_session.c
Line | Count | Source |
1 | | /* This Source Code Form is subject to the terms of the Mozilla Public |
2 | | * License, v. 2.0. If a copy of the MPL was not distributed with this |
3 | | * file, You can obtain one at http://mozilla.org/MPL/2.0/. |
4 | | * |
5 | | * Copyright 2018 (c) Fraunhofer IOSB (Author: Julius Pfrommer) |
6 | | * Copyright 2018 (c) Thomas Stalder, Blue Time Concept SA |
7 | | * Copyright 2019 (c) HMS Industrial Networks AB (Author: Jonas Green) |
8 | | */ |
9 | | |
10 | | #include "ua_session.h" |
11 | | #include "open62541/types.h" |
12 | | #include "ua_server_internal.h" |
13 | | #ifdef UA_ENABLE_SUBSCRIPTIONS |
14 | | #include "ua_subscription.h" |
15 | | #endif |
16 | | |
17 | 28.3k | void UA_Session_init(UA_Session *session) { |
18 | 28.3k | memset(session, 0, sizeof(UA_Session)); |
19 | 28.3k | TAILQ_INIT(&session->continuationPoints); |
20 | 28.3k | #ifdef UA_ENABLE_SUBSCRIPTIONS |
21 | 28.3k | SIMPLEQ_INIT(&session->responseQueue); |
22 | 28.3k | TAILQ_INIT(&session->subscriptions); |
23 | 28.3k | #endif |
24 | 28.3k | } |
25 | | |
26 | 27.9k | void UA_Session_clear(UA_Session *session, UA_Server* server) { |
27 | 27.9k | UA_LOCK_ASSERT(&server->serviceMutex); |
28 | | |
29 | | /* Remove all Subscriptions. This may send out remaining publish |
30 | | * responses. */ |
31 | 27.9k | #ifdef UA_ENABLE_SUBSCRIPTIONS |
32 | 27.9k | UA_Subscription *sub, *tempsub; |
33 | 27.9k | TAILQ_FOREACH_SAFE(sub, &session->subscriptions, sessionListEntry, tempsub) { |
34 | 16.4k | UA_Subscription_delete(server, sub); |
35 | 16.4k | } |
36 | 27.9k | #endif |
37 | | |
38 | 27.9k | #ifdef UA_ENABLE_DIAGNOSTICS |
39 | 27.9k | deleteNode(server, session->sessionId, true); |
40 | 27.9k | #endif |
41 | | |
42 | 27.9k | UA_Session_detachFromSecureChannel(server, session); |
43 | 27.9k | UA_ApplicationDescription_clear(&session->clientDescription); |
44 | 27.9k | UA_ByteString_clear(&session->clientCertificate); |
45 | 27.9k | UA_NodeId_clear(&session->authenticationToken); |
46 | 27.9k | UA_String_clear(&session->clientUserIdOfSession); |
47 | 27.9k | UA_NodeId_clear(&session->sessionId); |
48 | 27.9k | UA_String_clear(&session->sessionName); |
49 | 27.9k | UA_ByteString_clear(&session->serverNonce); |
50 | 27.9k | UA_ByteString_clear(&session->clientNonce); |
51 | 27.9k | ContinuationPointQueue_clear(&session->continuationPoints); |
52 | 27.9k | session->continuationPointsSize = 0; |
53 | | |
54 | 27.9k | UA_KeyValueMap_clear(&session->attributes); |
55 | | |
56 | 27.9k | UA_Array_delete(session->localeIds, session->localeIdsSize, |
57 | 27.9k | &UA_TYPES[UA_TYPES_STRING]); |
58 | 27.9k | session->localeIds = NULL; |
59 | 27.9k | session->localeIdsSize = 0; |
60 | | |
61 | 27.9k | #ifdef UA_ENABLE_DIAGNOSTICS |
62 | 27.9k | UA_SessionDiagnosticsDataType_clear(&session->diagnostics); |
63 | 27.9k | UA_SessionSecurityDiagnosticsDataType_clear(&session->securityDiagnostics); |
64 | 27.9k | #endif |
65 | | |
66 | 27.9k | if(session->sessionSp && session->sessionSpContext) { |
67 | 0 | session->sessionSp-> |
68 | 0 | deleteChannelContext(session->sessionSp, session->sessionSpContext); |
69 | 0 | session->sessionSp = NULL; |
70 | 0 | session->sessionSpContext = NULL; |
71 | 0 | } |
72 | 27.9k | } |
73 | | |
74 | | void |
75 | | UA_Session_attachToSecureChannel(UA_Server *server, UA_Session *session, |
76 | 7.76k | UA_SecureChannel *channel) { |
77 | | /* Ensure the Session is not attached to another SecureChannel */ |
78 | 7.76k | UA_Session_detachFromSecureChannel(server, session); |
79 | | |
80 | | /* Add to singly-linked list */ |
81 | 7.76k | session->next = channel->sessions; |
82 | 7.76k | channel->sessions = session; |
83 | | |
84 | | /* Add backpointer */ |
85 | 7.76k | session->channel = channel; |
86 | 7.76k | } |
87 | | |
88 | | void |
89 | 51.1k | UA_Session_detachFromSecureChannel(UA_Server *server, UA_Session *session) { |
90 | | /* Clean up the response queue. Their RequestId is bound to the |
91 | | * SecureChannel so they cannot be reused. */ |
92 | 51.1k | #ifdef UA_ENABLE_SUBSCRIPTIONS |
93 | 51.1k | UA_PublishResponseEntry *pre; |
94 | 51.1k | while((pre = UA_Session_dequeuePublishReq(session))) { |
95 | 0 | UA_PublishResponse_clear(&pre->response); |
96 | 0 | UA_free(pre); |
97 | 0 | } |
98 | 51.1k | #endif |
99 | | |
100 | | /* Remove from singly-linked list */ |
101 | 51.1k | UA_SecureChannel *channel = session->channel; |
102 | 51.1k | if(!channel) |
103 | 43.3k | return; |
104 | | |
105 | 7.76k | if(channel->sessions == session) { |
106 | 7.76k | channel->sessions = session->next; |
107 | 7.76k | } else { |
108 | 0 | UA_Session *elm = channel->sessions; |
109 | 0 | while(elm->next != session) |
110 | 0 | elm = elm->next; |
111 | 0 | elm->next = session->next; |
112 | 0 | } |
113 | | |
114 | | /* Reset the backpointer */ |
115 | 7.76k | session->channel = NULL; |
116 | | |
117 | | /* Notify the application */ |
118 | 7.76k | notifySession(server, session, |
119 | 7.76k | UA_APPLICATIONNOTIFICATIONTYPE_SESSION_DEACTIVATED); |
120 | 7.76k | } |
121 | | |
122 | | UA_StatusCode |
123 | 9.76k | UA_Session_generateNonce(UA_Session *session) { |
124 | 9.76k | UA_SecureChannel *channel = session->channel; |
125 | 9.76k | if(!channel || !channel->securityPolicy) |
126 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
127 | | |
128 | | /* The nonce needs to be between 32 and 128 byte. |
129 | | * The #Nonce SecurityPolicy might not do that. */ |
130 | 9.76k | UA_SecurityPolicy *sp = channel->securityPolicy; |
131 | 9.76k | void *spC = channel->channelContext; |
132 | 9.76k | if(session->sessionSp && session->sessionSpContext) { |
133 | 0 | sp = session->sessionSp; |
134 | 0 | spC = session->sessionSpContext; |
135 | 0 | } |
136 | | |
137 | | /* The session ServerNonce is a 32-byte application nonce. This is |
138 | | * independent of the SecurityPolicy's SecureChannel nonce length (for ECC |
139 | | * policies that is the 64-byte ephemeral key, which is NOT the session |
140 | | * nonce). */ |
141 | 9.76k | size_t nonceLength = 32; |
142 | | |
143 | | /* Is the length of the previous nonce correct? */ |
144 | 9.76k | if(session->serverNonce.length != nonceLength) { |
145 | 7.76k | UA_ByteString_clear(&session->serverNonce); |
146 | 7.76k | UA_StatusCode res = |
147 | 7.76k | UA_ByteString_allocBuffer(&session->serverNonce, nonceLength); |
148 | 7.76k | if(res != UA_STATUSCODE_GOOD) |
149 | 0 | return res; |
150 | 7.76k | } |
151 | | |
152 | | /* Generate plain random data. Ensure data[0] is not the 'e' of the "eph" |
153 | | * trigger that some ECC policies use to produce an ephemeral key. */ |
154 | 9.76k | session->serverNonce.data[0] = 0; |
155 | 9.76k | return sp->generateNonce(sp, spC, &session->serverNonce); |
156 | 9.76k | } |
157 | | |
158 | | void |
159 | | UA_Session_updateLifetime(UA_Session *session, UA_DateTime now, |
160 | 11.1k | UA_DateTime nowMonotonic) { |
161 | 11.1k | session->validTill = nowMonotonic + |
162 | 11.1k | (UA_DateTime)(session->timeout * UA_DATETIME_MSEC); |
163 | 11.1k | #ifdef UA_ENABLE_DIAGNOSTICS |
164 | 11.1k | session->diagnostics.clientLastContactTime = now; |
165 | 11.1k | #endif |
166 | 11.1k | } |
167 | | |
168 | | #ifdef UA_ENABLE_SUBSCRIPTIONS |
169 | | |
170 | | void |
171 | 16.4k | UA_Session_attachSubscription(UA_Session *session, UA_Subscription *sub) { |
172 | | /* Attach to the session */ |
173 | 16.4k | sub->session = session; |
174 | | |
175 | | /* Increase the count */ |
176 | 16.4k | session->subscriptionsSize++; |
177 | | |
178 | | /* Increase the number of outstanding retransmissions */ |
179 | 16.4k | session->totalRetransmissionQueueSize += sub->retransmissionQueueSize; |
180 | | |
181 | | /* Insert at the end of the subscriptions of the same priority / just before |
182 | | * the subscriptions with the next lower priority. */ |
183 | 16.4k | UA_Subscription *after = NULL; |
184 | 16.4k | TAILQ_FOREACH(after, &session->subscriptions, sessionListEntry) { |
185 | 0 | if(after->priority < sub->priority) { |
186 | 0 | TAILQ_INSERT_BEFORE(after, sub, sessionListEntry); |
187 | 0 | return; |
188 | 0 | } |
189 | 0 | } |
190 | 16.4k | TAILQ_INSERT_TAIL(&session->subscriptions, sub, sessionListEntry); |
191 | 16.4k | } |
192 | | |
193 | | void |
194 | | UA_Session_detachSubscription(UA_Server *server, UA_Session *session, |
195 | 16.4k | UA_Subscription *sub, UA_Boolean releasePublishResponses) { |
196 | | /* Detach from the session */ |
197 | 16.4k | sub->session = NULL; |
198 | 16.4k | TAILQ_REMOVE(&session->subscriptions, sub, sessionListEntry); |
199 | | |
200 | | /* Reduce the count */ |
201 | 16.4k | UA_assert(session->subscriptionsSize > 0); |
202 | 16.4k | session->subscriptionsSize--; |
203 | | |
204 | | /* Reduce the number of outstanding retransmissions */ |
205 | 16.4k | session->totalRetransmissionQueueSize -= sub->retransmissionQueueSize; |
206 | | |
207 | | /* Send remaining publish responses if the last subscription was removed */ |
208 | 16.4k | if(!releasePublishResponses || !TAILQ_EMPTY(&session->subscriptions)) |
209 | 0 | return; |
210 | 16.4k | UA_PublishResponseEntry *pre; |
211 | 16.4k | while((pre = UA_Session_dequeuePublishReq(session))) { |
212 | 0 | UA_PublishResponse *response = &pre->response; |
213 | 0 | response->responseHeader.serviceResult = UA_STATUSCODE_BADNOSUBSCRIPTION; |
214 | 0 | sendResponse(server, session->channel, pre->requestId, |
215 | 0 | (UA_Response*)response, &UA_TYPES[UA_TYPES_PUBLISHRESPONSE]); |
216 | 0 | UA_PublishResponse_clear(response); |
217 | 0 | UA_free(pre); |
218 | 0 | } |
219 | 16.4k | } |
220 | | |
221 | | UA_Subscription * |
222 | 1.91k | UA_Session_getSubscriptionById(UA_Session *session, UA_UInt32 subscriptionId) { |
223 | 1.91k | UA_Subscription *sub; |
224 | 1.91k | TAILQ_FOREACH(sub, &session->subscriptions, sessionListEntry) { |
225 | | /* Prevent lookup of subscriptions that are to be deleted with a statuschange */ |
226 | 0 | if(sub->statusChange != UA_STATUSCODE_GOOD) |
227 | 0 | continue; |
228 | 0 | if(sub->subscriptionId == subscriptionId) |
229 | 0 | break; |
230 | 0 | } |
231 | 1.91k | return sub; |
232 | 1.91k | } |
233 | | |
234 | | UA_Subscription * |
235 | 52 | getSubscriptionById(UA_Server *server, UA_UInt32 subscriptionId) { |
236 | 52 | UA_Subscription *sub; |
237 | 52 | LIST_FOREACH(sub, &server->subscriptions, serverListEntry) { |
238 | | /* Prevent lookup of subscriptions that are to be deleted with a statuschange */ |
239 | 0 | if(sub->statusChange != UA_STATUSCODE_GOOD) |
240 | 0 | continue; |
241 | 0 | if(sub->subscriptionId == subscriptionId) |
242 | 0 | break; |
243 | 0 | } |
244 | 52 | return sub; |
245 | 52 | } |
246 | | |
247 | | UA_PublishResponseEntry* |
248 | 75.2k | UA_Session_dequeuePublishReq(UA_Session *session) { |
249 | 75.2k | UA_PublishResponseEntry *entry = SIMPLEQ_FIRST(&session->responseQueue); |
250 | 75.2k | if(entry) { |
251 | 0 | SIMPLEQ_REMOVE_HEAD(&session->responseQueue, listEntry); |
252 | 0 | session->responseQueueSize--; |
253 | 0 | } |
254 | 75.2k | return entry; |
255 | 75.2k | } |
256 | | |
257 | | void |
258 | | UA_Session_queuePublishReq(UA_Session *session, UA_PublishResponseEntry* entry, |
259 | 0 | UA_Boolean head) { |
260 | 0 | if(!head) |
261 | 0 | SIMPLEQ_INSERT_TAIL(&session->responseQueue, entry, listEntry); |
262 | 0 | else |
263 | 0 | SIMPLEQ_INSERT_HEAD(&session->responseQueue, entry, listEntry); |
264 | 0 | session->responseQueueSize++; |
265 | 0 | } |
266 | | |
267 | | #endif |
268 | | |
269 | | /* Session Handling */ |
270 | | |
271 | | UA_StatusCode |
272 | 0 | UA_Server_closeSession(UA_Server *server, const UA_NodeId *sessionId) { |
273 | 0 | lockServer(server); |
274 | 0 | UA_Session *session = getSessionById(server, sessionId); |
275 | 0 | if(!session) { |
276 | 0 | unlockServer(server); |
277 | 0 | return UA_STATUSCODE_BADSESSIONIDINVALID; |
278 | 0 | } |
279 | 0 | UA_Session_remove(server, session, UA_SHUTDOWNREASON_CLOSE); |
280 | 0 | unlockServer(server); |
281 | 0 | return UA_STATUSCODE_GOOD; |
282 | 0 | } |
283 | | |
284 | | /* Session Attributes */ |
285 | | |
286 | 10.0k | #define UA_PROTECTEDATTRIBUTESSIZE 4 |
287 | | static const UA_QualifiedName protectedAttributes[UA_PROTECTEDATTRIBUTESSIZE] = { |
288 | | {0, UA_STRING_STATIC("localeIds")}, |
289 | | {0, UA_STRING_STATIC("clientDescription")}, |
290 | | {0, UA_STRING_STATIC("sessionName")}, |
291 | | {0, UA_STRING_STATIC("clientUserId")} |
292 | | }; |
293 | | |
294 | | static UA_Boolean |
295 | 2.00k | protectedAttribute(const UA_QualifiedName key) { |
296 | 10.0k | for(size_t i = 0; i < UA_PROTECTEDATTRIBUTESSIZE; i++) { |
297 | 8.00k | if(UA_QualifiedName_equal(&key, &protectedAttributes[i])) |
298 | 0 | return true; |
299 | 8.00k | } |
300 | 2.00k | return false; |
301 | 2.00k | } |
302 | | |
303 | | UA_StatusCode |
304 | | UA_Server_setSessionAttribute(UA_Server *server, const UA_NodeId *sessionId, |
305 | 2.00k | const UA_QualifiedName key, const UA_Variant *value) { |
306 | 2.00k | if(protectedAttribute(key)) |
307 | 0 | return UA_STATUSCODE_BADNOTWRITABLE; |
308 | 2.00k | if(!sessionId) |
309 | 0 | return UA_STATUSCODE_BADINVALIDARGUMENT; |
310 | 2.00k | lockServer(server); |
311 | 2.00k | UA_Session *session = getSessionById(server, sessionId); |
312 | 2.00k | UA_StatusCode res = UA_STATUSCODE_BADSESSIONIDINVALID; |
313 | 2.00k | if(session) |
314 | 2.00k | res = UA_KeyValueMap_set(&session->attributes, key, value); |
315 | 2.00k | unlockServer(server); |
316 | 2.00k | return res; |
317 | 2.00k | } |
318 | | |
319 | | UA_StatusCode |
320 | | UA_Server_deleteSessionAttribute(UA_Server *server, const UA_NodeId *sessionId, |
321 | 0 | const UA_QualifiedName key) { |
322 | 0 | if(protectedAttribute(key)) |
323 | 0 | return UA_STATUSCODE_BADNOTWRITABLE; |
324 | 0 | if(!sessionId) |
325 | 0 | return UA_STATUSCODE_BADINVALIDARGUMENT; |
326 | 0 | lockServer(server); |
327 | 0 | UA_Session *session = getSessionById(server, sessionId); |
328 | 0 | if(!session) { |
329 | 0 | unlockServer(server); |
330 | 0 | return UA_STATUSCODE_BADSESSIONIDINVALID; |
331 | 0 | } |
332 | 0 | UA_StatusCode res = UA_KeyValueMap_remove(&session->attributes, key); |
333 | 0 | unlockServer(server); |
334 | 0 | return res; |
335 | 0 | } |
336 | | |
337 | | static UA_StatusCode |
338 | | getSessionAttribute(UA_Server *server, const UA_NodeId *sessionId, |
339 | | const UA_QualifiedName key, UA_Variant *outValue, |
340 | 0 | UA_Boolean copy) { |
341 | 0 | if(!outValue) |
342 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
343 | 0 | if(!sessionId) |
344 | 0 | return UA_STATUSCODE_BADINVALIDARGUMENT; |
345 | | |
346 | 0 | UA_Session *session = getSessionById(server, sessionId); |
347 | 0 | if(!session) |
348 | 0 | return UA_STATUSCODE_BADSESSIONIDINVALID; |
349 | | |
350 | 0 | const UA_Variant *attr; |
351 | 0 | UA_Variant localAttr; |
352 | |
|
353 | 0 | if(UA_QualifiedName_equal(&key, &protectedAttributes[0])) { |
354 | | /* Return LocaleIds */ |
355 | 0 | UA_Variant_setArray(&localAttr, session->localeIds, |
356 | 0 | session->localeIdsSize, &UA_TYPES[UA_TYPES_STRING]); |
357 | 0 | attr = &localAttr; |
358 | 0 | } else if(UA_QualifiedName_equal(&key, &protectedAttributes[1])) { |
359 | | /* Return client description */ |
360 | 0 | UA_Variant_setScalar(&localAttr, &session->clientDescription, |
361 | 0 | &UA_TYPES[UA_TYPES_APPLICATIONDESCRIPTION]); |
362 | 0 | attr = &localAttr; |
363 | 0 | } else if(UA_QualifiedName_equal(&key, &protectedAttributes[2])) { |
364 | | /* Return session name */ |
365 | 0 | UA_Variant_setScalar(&localAttr, &session->sessionName, |
366 | 0 | &UA_TYPES[UA_TYPES_STRING]); |
367 | 0 | attr = &localAttr; |
368 | 0 | } else if(UA_QualifiedName_equal(&key, &protectedAttributes[3])) { |
369 | | /* Return client user id */ |
370 | 0 | UA_Variant_setScalar(&localAttr, &session->clientUserIdOfSession, |
371 | 0 | &UA_TYPES[UA_TYPES_STRING]); |
372 | 0 | attr = &localAttr; |
373 | 0 | } else { |
374 | | /* Get from the actual key-value list */ |
375 | 0 | attr = UA_KeyValueMap_get(&session->attributes, key); |
376 | 0 | if(!attr) |
377 | 0 | return UA_STATUSCODE_BADNOTFOUND; |
378 | 0 | } |
379 | | |
380 | 0 | if(copy) |
381 | 0 | return UA_Variant_copy(attr, outValue); |
382 | | |
383 | 0 | *outValue = *attr; |
384 | 0 | outValue->storageType = UA_VARIANT_DATA_NODELETE; |
385 | 0 | return UA_STATUSCODE_GOOD; |
386 | 0 | } |
387 | | |
388 | | UA_StatusCode |
389 | | UA_Server_getSessionAttribute(UA_Server *server, const UA_NodeId *sessionId, |
390 | 0 | const UA_QualifiedName key, UA_Variant *outValue) { |
391 | 0 | lockServer(server); |
392 | 0 | UA_StatusCode res = getSessionAttribute(server, sessionId, key, outValue, false); |
393 | 0 | unlockServer(server); |
394 | 0 | return res; |
395 | 0 | } |
396 | | |
397 | | UA_StatusCode |
398 | | UA_Server_getSessionAttributeCopy(UA_Server *server, const UA_NodeId *sessionId, |
399 | 0 | const UA_QualifiedName key, UA_Variant *outValue) { |
400 | 0 | lockServer(server); |
401 | 0 | UA_StatusCode res = getSessionAttribute(server, sessionId, key, outValue, true); |
402 | 0 | unlockServer(server); |
403 | 0 | return res; |
404 | 0 | } |
405 | | |
406 | | UA_StatusCode |
407 | | UA_Server_getSessionAttribute_scalar(UA_Server *server, |
408 | | const UA_NodeId *sessionId, |
409 | | const UA_QualifiedName key, |
410 | | const UA_DataType *type, |
411 | 0 | void *outValue) { |
412 | 0 | lockServer(server); |
413 | |
|
414 | 0 | UA_Variant attr; |
415 | 0 | UA_StatusCode res = getSessionAttribute(server, sessionId, key, &attr, false); |
416 | 0 | if(res != UA_STATUSCODE_GOOD) { |
417 | 0 | unlockServer(server); |
418 | 0 | return res; |
419 | 0 | } |
420 | | |
421 | 0 | if(!UA_Variant_hasScalarType(&attr, type)) { |
422 | 0 | unlockServer(server); |
423 | 0 | return UA_STATUSCODE_BADNOTFOUND; |
424 | 0 | } |
425 | | |
426 | 0 | memcpy(outValue, attr.data, type->memSize); |
427 | |
|
428 | 0 | unlockServer(server); |
429 | 0 | return UA_STATUSCODE_GOOD; |
430 | 0 | } |