olm_inbound_group_session.cpp 19 KB
Newer Older
1
2
/*
 * Copyright 2016 OpenMarket Ltd
ylecollen's avatar
ylecollen committed
3
 * Copyright 2016 Vector Creations Ltd
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "olm_inbound_group_session.h"

pedroGitt's avatar
pedroGitt committed
20
using namespace AndroidOlmSdk;
21
22
23
24
25
26
27
28

/**
 * Release the session allocation made by initializeInboundGroupSessionMemory().<br>
 * This method MUST be called when java counter part account instance is done.
 *
 */
JNIEXPORT void OLM_INBOUND_GROUP_SESSION_FUNC_DEF(releaseSessionJni)(JNIEnv *env, jobject thiz)
{
29
    OlmInboundGroupSession* sessionPtr = NULL;
30

31
    LOGD("## releaseSessionJni(): InBound group session IN");
32

33
34
35
36
37
38
39
    if (!(sessionPtr = (OlmInboundGroupSession*)getInboundGroupSessionInstanceId(env,thiz)))
    {
        LOGE("## releaseSessionJni(): failure - invalid inbound group session instance");
    }
    else
    {
        LOGD(" ## releaseSessionJni(): sessionPtr=%p",sessionPtr);
40
#ifdef ENABLE_JNI_LOG
41
42
        size_t retCode = olm_clear_inbound_group_session(sessionPtr);
        LOGD(" ## releaseSessionJni(): clear_inbound_group_session=%lu",static_cast<long unsigned int>(retCode));
43
#else
44
        olm_clear_inbound_group_session(sessionPtr);
45
#endif
46

47
48
49
50
        LOGD(" ## releaseSessionJni(): free IN");
        free(sessionPtr);
        LOGD(" ## releaseSessionJni(): free OUT");
    }
51
52
53
54
55
56
57
58
}

/**
* Initialize a new inbound group session and return it to JAVA side.<br>
* Since a C prt is returned as a jlong, special care will be taken
* to make the cast (OlmInboundGroupSession* => jlong) platform independent.
* @return the initialized OlmInboundGroupSession* instance if init succeed, NULL otherwise
**/
59
JNIEXPORT jlong OLM_INBOUND_GROUP_SESSION_FUNC_DEF(createNewSessionJni)(JNIEnv *env, jobject thiz)
60
61
{
    OlmInboundGroupSession* sessionPtr = NULL;
62
63
    size_t sessionSize = 0;

64
    LOGD("## createNewSessionJni(): inbound group session IN");
65
    sessionSize = olm_inbound_group_session_size();
66

67
    if (!sessionSize)
68
    {
69
        LOGE(" ## createNewSessionJni(): failure - inbound group session size = 0");
70
    }
71
    else if ((sessionPtr = (OlmInboundGroupSession*)malloc(sessionSize)))
72
    {
73
74
        sessionPtr = olm_inbound_group_session(sessionPtr);
        LOGD(" ## createNewSessionJni(): success - inbound group session size=%lu",static_cast<long unsigned int>(sessionSize));
75
76
77
    }
    else
    {
78
        LOGE(" ## createNewSessionJni(): failure - inbound group session OOM");
79
80
81
82
83
84
85
86
87
88
    }

    return (jlong)(intptr_t)sessionPtr;
}

/**
 * Create a new in-bound session.<br>
 * @param aSessionKey session key from an outbound session
 * @return ERROR_CODE_OK if operation succeed, ERROR_CODE_KO otherwise
 */
89
JNIEXPORT jint OLM_INBOUND_GROUP_SESSION_FUNC_DEF(initInboundGroupSessionWithSessionKeyJni)(JNIEnv *env, jobject thiz, jbyteArray aSessionKeyBuffer)
90
91
92
{
    jint retCode = ERROR_CODE_KO;
    OlmInboundGroupSession *sessionPtr = NULL;
93
    jbyte* sessionKeyPtr = NULL;
94
95
    size_t sessionResult;

96
97
    LOGD("## initInboundGroupSessionWithSessionKeyJni(): inbound group session IN");

98
    if (!(sessionPtr = (OlmInboundGroupSession*)getInboundGroupSessionInstanceId(env,thiz)))
99
    {
100
        LOGE(" ## initInboundGroupSessionWithSessionKeyJni(): failure - invalid inbound group session instance");
101
    }
102
    else if (!aSessionKeyBuffer)
103
    {
104
        LOGE(" ## initInboundGroupSessionWithSessionKeyJni(): failure - invalid aSessionKey");
105
    }
106
    else if (!(sessionKeyPtr = env->GetByteArrayElements(aSessionKeyBuffer, 0)))
107
    {
108
        LOGE(" ## initInboundSessionFromIdKeyJni(): failure - session key JNI allocation OOM");
109
110
111
    }
    else
    {
112
        size_t sessionKeyLength = (size_t)env->GetArrayLength(aSessionKeyBuffer);
pedroGitt's avatar
pedroGitt committed
113
        LOGD(" ## initInboundSessionFromIdKeyJni(): sessionKeyLength=%lu",static_cast<long unsigned int>(sessionKeyLength));
114

115
        sessionResult = olm_init_inbound_group_session(sessionPtr, (const uint8_t*)sessionKeyPtr, sessionKeyLength);
116
        if (sessionResult == olm_error()) {
117
            const char *errorMsgPtr = olm_inbound_group_session_last_error(sessionPtr);
118
            LOGE(" ## initInboundSessionFromIdKeyJni(): failure - init inbound session creation Msg=%s",errorMsgPtr);
119
120
121
122
        }
        else
        {
            retCode = ERROR_CODE_OK;
pedroGitt's avatar
pedroGitt committed
123
            LOGD(" ## initInboundSessionFromIdKeyJni(): success - result=%lu", static_cast<long unsigned int>(sessionResult));
124
125
126
127
        }
     }

     // free local alloc
128
     if (sessionKeyPtr)
129
     {
130
         env->ReleaseByteArrayElements(aSessionKeyBuffer, sessionKeyPtr, JNI_ABORT);
131
132
133
134
135
136
     }

    return retCode;
}


137
138
139
/**
* Get a base64-encoded identifier for this inbound group session.
*/
140
JNIEXPORT jbyteArray OLM_INBOUND_GROUP_SESSION_FUNC_DEF(sessionIdentifierJni)(JNIEnv *env, jobject thiz)
141
142
{
    OlmInboundGroupSession *sessionPtr = NULL;
143
    jbyteArray returnValue = 0;
144

145
    LOGD("## sessionIdentifierJni(): inbound group session IN");
146

147
    if (!(sessionPtr = (OlmInboundGroupSession*)getInboundGroupSessionInstanceId(env,thiz)))
148
    {
149
        LOGE(" ## sessionIdentifierJni(): failure - invalid inbound group session instance");
150
151
152
    }
    else
    {
153
154
        // get the size to alloc
        size_t lengthSessionId = olm_inbound_group_session_id_length(sessionPtr);
pedroGitt's avatar
pedroGitt committed
155
        LOGD(" ## sessionIdentifierJni(): inbound group session lengthSessionId=%lu",static_cast<long unsigned int>(lengthSessionId));
156

157
158
159
        uint8_t *sessionIdPtr = (uint8_t*)malloc((lengthSessionId+1)*sizeof(uint8_t));

        if (!sessionIdPtr)
160
        {
161
           LOGE(" ## sessionIdentifierJni(): failure - inbound group session identifier allocation OOM");
162
163
164
        }
        else
        {
165
            size_t result = olm_inbound_group_session_id(sessionPtr, sessionIdPtr, lengthSessionId);
166

167
168
            if (result == olm_error())
            {
169
                LOGE(" ## sessionIdentifierJni(): failure - get inbound group session identifier failure Msg=%s",(const char *)olm_inbound_group_session_last_error(sessionPtr));
170
171
172
            }
            else
            {
173
            
174
                sessionIdPtr[result] = static_cast<char>('\0');
pedroGitt's avatar
pedroGitt committed
175
                LOGD(" ## sessionIdentifierJni(): success - inbound group session result=%lu sessionId=%s",static_cast<long unsigned int>(result), (char*)sessionIdPtr);
176
177
178

                returnValue = env->NewByteArray(result);
                env->SetByteArrayRegion(returnValue, 0 , result, (jbyte*)sessionIdPtr);
179
            }
180

181
            free(sessionIdPtr);
182
183
184
        }
    }

185
    return returnValue;
186
187
}

188

189
JNIEXPORT jbyteArray OLM_INBOUND_GROUP_SESSION_FUNC_DEF(decryptMessageJni)(JNIEnv *env, jobject thiz, jbyteArray aEncryptedMsgBuffer, jobject aDecryptionResult, jobject aErrorMsg)
190
{
191
192
    jbyteArray decryptedMsgBuffer = 0;

193
    OlmInboundGroupSession *sessionPtr = NULL;
194
    jbyte *encryptedMsgPtr = NULL;
195
196
    jclass indexObjJClass = 0;
    jfieldID indexMsgFieldId;
197
198
199
    jclass errorMsgJClass = 0;
    jmethodID errorMsgMethodId = 0;
    const char *errorMsgPtr = NULL;
200

201
    LOGD("## decryptMessageJni(): inbound group session IN");
202

ylecollen's avatar
ylecollen committed
203
    if (!(sessionPtr = (OlmInboundGroupSession*)getInboundGroupSessionInstanceId(env,thiz)))
204
    {
205
        LOGE(" ## decryptMessageJni(): failure - invalid inbound group session ptr=NULL");
206
    }
207
    else if (!aEncryptedMsgBuffer)
208
    {
209
        LOGE(" ## decryptMessageJni(): failure - invalid encrypted message");
210
    }
211
    else if (!aDecryptionResult)
212
213
214
    {
        LOGE(" ## decryptMessageJni(): failure - invalid index object");
    }
ylecollen's avatar
ylecollen committed
215
    else if (!aErrorMsg)
216
217
218
    {
        LOGE(" ## decryptMessageJni(): failure - invalid error object");
    }
ylecollen's avatar
ylecollen committed
219
    else if (!(errorMsgJClass = env->GetObjectClass(aErrorMsg)))
220
221
222
    {
        LOGE(" ## decryptMessageJni(): failure - unable to get error class");
    }
ylecollen's avatar
ylecollen committed
223
    else if (!(errorMsgMethodId = env->GetMethodID(errorMsgJClass, "append", "(Ljava/lang/String;)Ljava/lang/StringBuffer;")))
224
225
226
    {
        LOGE(" ## decryptMessageJni(): failure - unable to get error method ID");
    }
227
    else if (!(encryptedMsgPtr = env->GetByteArrayElements(aEncryptedMsgBuffer, 0)))
228
    {
229
        LOGE(" ## decryptMessageJni(): failure - encrypted message JNI allocation OOM");
230
    }
231
    else if (!(indexObjJClass = env->GetObjectClass(aDecryptionResult)))
232
233
234
    {
        LOGE("## decryptMessageJni(): failure - unable to get index class");
    }
ylecollen's avatar
ylecollen committed
235
    else if (!(indexMsgFieldId = env->GetFieldID(indexObjJClass,"mIndex","J")))
236
237
238
    {
        LOGE("## decryptMessageJni(): failure - unable to get index type field");
    }
239
240
241
    else
    {
        // get encrypted message length
242
        size_t encryptedMsgLength = (size_t)env->GetArrayLength(aEncryptedMsgBuffer);
ylecollen's avatar
ylecollen committed
243
        uint8_t *tempEncryptedPtr = static_cast<uint8_t*>(malloc(encryptedMsgLength*sizeof(uint8_t)));
244
245

        // create a dedicated temp buffer to be used in next Olm API calls
ylecollen's avatar
ylecollen committed
246
        if (!tempEncryptedPtr)
247
        {
248
            LOGE(" ## decryptMessageJni(): failure - tempEncryptedPtr allocation OOM");
249
250
251
252
        }
        else
        {
            memcpy(tempEncryptedPtr, encryptedMsgPtr, encryptedMsgLength);
pedroGitt's avatar
pedroGitt committed
253
            LOGD(" ## decryptMessageJni(): encryptedMsgLength=%lu encryptedMsg=%s",static_cast<long unsigned int>(encryptedMsgLength),encryptedMsgPtr);
254
255
256
257
258

            // get max plaintext length
            size_t maxPlainTextLength = olm_group_decrypt_max_plaintext_length(sessionPtr,
                                                                               tempEncryptedPtr,
                                                                               encryptedMsgLength);
ylecollen's avatar
ylecollen committed
259
            if (maxPlainTextLength == olm_error())
260
            {
261
262
                errorMsgPtr = olm_inbound_group_session_last_error(sessionPtr);
                LOGE(" ## decryptMessageJni(): failure - olm_group_decrypt_max_plaintext_length Msg=%s",errorMsgPtr);
ylecollen's avatar
ylecollen committed
263
264
265
266

                jstring errorJstring = env->NewStringUTF(errorMsgPtr);

                if (errorJstring)
267
268
269
                {
                    env->CallObjectMethod(aErrorMsg, errorMsgMethodId, errorJstring);
                }
270
271
272
            }
            else
            {
pedroGitt's avatar
pedroGitt committed
273
                LOGD(" ## decryptMessageJni(): maxPlaintextLength=%lu",static_cast<long unsigned int>(maxPlainTextLength));
274

ylecollen's avatar
ylecollen committed
275
276
                uint32_t messageIndex = 0;

277
                // allocate output decrypted message
ylecollen's avatar
ylecollen committed
278
                uint8_t *plainTextMsgPtr = static_cast<uint8_t*>(malloc((maxPlainTextLength+1)*sizeof(uint8_t)));
279
280
281
282
283
284
285

                // decrypt, but before reload encrypted buffer (previous one was destroyed)
                memcpy(tempEncryptedPtr, encryptedMsgPtr, encryptedMsgLength);
                size_t plaintextLength = olm_group_decrypt(sessionPtr,
                                                           tempEncryptedPtr,
                                                           encryptedMsgLength,
                                                           plainTextMsgPtr,
286
287
                                                           maxPlainTextLength,
                                                           &messageIndex);
ylecollen's avatar
ylecollen committed
288
                if (plaintextLength == olm_error())
289
                {
290
291
292
                    errorMsgPtr = olm_inbound_group_session_last_error(sessionPtr);
                    LOGE(" ## decryptMessageJni(): failure - olm_group_decrypt Msg=%s",errorMsgPtr);

ylecollen's avatar
ylecollen committed
293
294
295
                    jstring errorJstring = env->NewStringUTF(errorMsgPtr);

                    if (errorJstring)
296
297
298
                    {
                        env->CallObjectMethod(aErrorMsg, errorMsgMethodId, errorJstring);
                    }
299
300
301
                }
                else
                {
302
                    // update index
303
                    env->SetLongField(aDecryptionResult, indexMsgFieldId, (jlong)messageIndex);
304

305
306
                    decryptedMsgBuffer = env->NewByteArray(plaintextLength);
                    env->SetByteArrayRegion(decryptedMsgBuffer, 0 , plaintextLength, (jbyte*)plainTextMsgPtr);
307

308
                    LOGD(" ## decryptMessageJni(): UTF-8 Conversion - decrypted returnedLg=%lu OK",static_cast<long unsigned int>(plaintextLength));
309
                }
ylecollen's avatar
ylecollen committed
310
311
312
313
314
315
316
317
318
319

                if (plainTextMsgPtr)
                {
                    free(plainTextMsgPtr);
                }
            }

            if (tempEncryptedPtr)
            {
                free(tempEncryptedPtr);
320
321
322
323
324
            }
        }
    }

    // free alloc
ylecollen's avatar
ylecollen committed
325
    if (encryptedMsgPtr)
326
    {
327
        env->ReleaseByteArrayElements(aEncryptedMsgBuffer, encryptedMsgPtr, JNI_ABORT);
328
329
    }

330
    return decryptedMsgBuffer;
331
332
333
}


334
335
/**
* Serialize and encrypt session instance into a base64 string.<br>
336
* @param aKeyBuffer key used to encrypt the serialized session data
337
338
339
* @param[out] aErrorMsg error message set if operation failed
* @return a base64 string if operation succeed, null otherwise
**/
340
JNIEXPORT jbyteArray OLM_INBOUND_GROUP_SESSION_FUNC_DEF(serializeDataWithKeyJni)(JNIEnv *env, jobject thiz, jbyteArray aKeyBuffer, jobject aErrorMsg)
341
{
342
343
    jbyteArray pickledDataRet = 0;

344
345
    jclass errorMsgJClass = 0;
    jmethodID errorMsgMethodId = 0;
346
    jbyte* keyPtr = NULL;
347
348
349
350
    OlmInboundGroupSession* sessionPtr = NULL;

    LOGD("## inbound group session serializeDataWithKeyJni(): IN");

ylecollen's avatar
ylecollen committed
351
    if (!(sessionPtr = (OlmInboundGroupSession*)getInboundGroupSessionInstanceId(env,thiz)))
352
353
354
    {
        LOGE(" ## serializeDataWithKeyJni(): failure - invalid session ptr");
    }
355
    else if (!aKeyBuffer)
356
357
358
    {
        LOGE(" ## serializeDataWithKeyJni(): failure - invalid key");
    }
ylecollen's avatar
ylecollen committed
359
    else if (!aErrorMsg)
360
361
362
    {
        LOGE(" ## serializeDataWithKeyJni(): failure - invalid error object");
    }
ylecollen's avatar
ylecollen committed
363
    else if (!(errorMsgJClass = env->GetObjectClass(aErrorMsg)))
364
365
366
    {
        LOGE(" ## serializeDataWithKeyJni(): failure - unable to get error class");
    }
ylecollen's avatar
ylecollen committed
367
    else if (!(errorMsgMethodId = env->GetMethodID(errorMsgJClass, "append", "(Ljava/lang/String;)Ljava/lang/StringBuffer;")))
368
369
370
    {
        LOGE(" ## serializeDataWithKeyJni(): failure - unable to get error method ID");
    }
371
    else if (!(keyPtr = env->GetByteArrayElements(aKeyBuffer, 0)))
372
373
374
375
376
377
    {
        LOGE(" ## serializeDataWithKeyJni(): failure - keyPtr JNI allocation OOM");
    }
    else
    {
        size_t pickledLength = olm_pickle_inbound_group_session_length(sessionPtr);
378
        size_t keyLength = (size_t)env->GetArrayLength(aKeyBuffer);
pedroGitt's avatar
pedroGitt committed
379
        LOGD(" ## serializeDataWithKeyJni(): pickledLength=%lu keyLength=%lu", static_cast<long unsigned int>(pickledLength), static_cast<long unsigned int>(keyLength));
380
381
        LOGD(" ## serializeDataWithKeyJni(): key=%s",(char const *)keyPtr);

ylecollen's avatar
ylecollen committed
382
383
384
        void *pickledPtr = malloc((pickledLength+1)*sizeof(uint8_t));

        if (!pickledPtr)
385
386
387
388
389
390
391
392
393
394
        {
            LOGE(" ## serializeDataWithKeyJni(): failure - pickledPtr buffer OOM");
        }
        else
        {
            size_t result = olm_pickle_inbound_group_session(sessionPtr,
                                                             (void const *)keyPtr,
                                                              keyLength,
                                                              (void*)pickledPtr,
                                                              pickledLength);
395
            if (result == olm_error())
396
397
398
399
            {
                const char *errorMsgPtr = olm_inbound_group_session_last_error(sessionPtr);
                LOGE(" ## serializeDataWithKeyJni(): failure - olm_pickle_outbound_group_session() Msg=%s",errorMsgPtr);

ylecollen's avatar
ylecollen committed
400
401
402
                jstring errorJstring = env->NewStringUTF(errorMsgPtr);

                if (errorJstring)
403
404
405
406
407
408
                {
                    env->CallObjectMethod(aErrorMsg, errorMsgMethodId, errorJstring);
                }
            }
            else
            {
409
             	(static_cast<char*>(pickledPtr))[pickledLength] = static_cast<char>('\0');
pedroGitt's avatar
pedroGitt committed
410
                LOGD(" ## serializeDataWithKeyJni(): success - result=%lu pickled=%s", static_cast<long unsigned int>(result), static_cast<char*>(pickledPtr));
411
412
413

                pickledDataRet = env->NewByteArray(pickledLength);
                env->SetByteArrayRegion(pickledDataRet, 0 , pickledLength, (jbyte*)pickledPtr);
414
            }
ylecollen's avatar
ylecollen committed
415
416

            free(pickledPtr);
417
418
419
420
        }
    }

    // free alloc
ylecollen's avatar
ylecollen committed
421
    if (keyPtr)
422
    {
423
        env->ReleaseByteArrayElements(aKeyBuffer, keyPtr, JNI_ABORT);
424
425
    }

426
    return pickledDataRet;
427
428
429
}


430
JNIEXPORT jstring OLM_INBOUND_GROUP_SESSION_FUNC_DEF(initWithSerializedDataJni)(JNIEnv *env, jobject thiz, jbyteArray aSerializedDataBuffer, jbyteArray aKeyBuffer)
431
432
433
{
    OlmInboundGroupSession* sessionPtr = NULL;
    jstring errorMessageRetValue = 0;
434
435
    jbyte* keyPtr = NULL;
    jbyte* pickledPtr = NULL;
436

437
438
    LOGD("## initWithSerializedDataJni(): IN");

ylecollen's avatar
ylecollen committed
439
    if (!(sessionPtr = (OlmInboundGroupSession*)getInboundGroupSessionInstanceId(env,thiz)))
440
441
442
    {
        LOGE(" ## initWithSerializedDataJni(): failure - session failure OOM");
    }
443
    else if (!aKeyBuffer)
444
445
446
    {
        LOGE(" ## initWithSerializedDataJni(): failure - invalid key");
    }
447
    else if (!aSerializedDataBuffer)
448
449
450
    {
        LOGE(" ## initWithSerializedDataJni(): failure - serialized data");
    }
451
    else if (!(keyPtr = env->GetByteArrayElements(aKeyBuffer, 0)))
452
453
454
    {
        LOGE(" ## initWithSerializedDataJni(): failure - keyPtr JNI allocation OOM");
    }
455
    else if (!(pickledPtr = env->GetByteArrayElements(aSerializedDataBuffer, 0)))
456
457
458
459
460
    {
        LOGE(" ## initWithSerializedDataJni(): failure - pickledPtr JNI allocation OOM");
    }
    else
    {
461
462
        size_t pickledLength = (size_t)env->GetArrayLength(aSerializedDataBuffer);
        size_t keyLength = (size_t)env->GetArrayLength(aKeyBuffer);
pedroGitt's avatar
pedroGitt committed
463
        LOGD(" ## initWithSerializedDataJni(): pickledLength=%lu keyLength=%lu",static_cast<long unsigned int>(pickledLength), static_cast<long unsigned int>(keyLength));
464
465
466
467
468
469
470
471
        LOGD(" ## initWithSerializedDataJni(): key=%s",(char const *)keyPtr);
        LOGD(" ## initWithSerializedDataJni(): pickled=%s",(char const *)pickledPtr);

        size_t result = olm_unpickle_inbound_group_session(sessionPtr,
                                                           (void const *)keyPtr,
                                                           keyLength,
                                                           (void*)pickledPtr,
                                                           pickledLength);
ylecollen's avatar
ylecollen committed
472
        if (result == olm_error())
473
474
475
476
477
478
479
        {
            const char *errorMsgPtr = olm_inbound_group_session_last_error(sessionPtr);
            LOGE(" ## initWithSerializedDataJni(): failure - olm_unpickle_inbound_group_session() Msg=%s",errorMsgPtr);
            errorMessageRetValue = env->NewStringUTF(errorMsgPtr);
        }
        else
        {
pedroGitt's avatar
pedroGitt committed
480
            LOGD(" ## initWithSerializedDataJni(): success - result=%lu ", static_cast<long unsigned int>(result));
481
482
483
484
        }
    }

    // free alloc
ylecollen's avatar
ylecollen committed
485
    if (keyPtr)
486
    {
487
        env->ReleaseByteArrayElements(aKeyBuffer, keyPtr, JNI_ABORT);
488
489
    }

ylecollen's avatar
ylecollen committed
490
    if (pickledPtr)
491
    {
492
        env->ReleaseByteArrayElements(aSerializedDataBuffer, pickledPtr, JNI_ABORT);
493
494
495
496
    }

    return errorMessageRetValue;
}