1 | /* Copyright 2015, 2016 OpenMarket Ltd |
2 | * |
3 | * Licensed under the Apache License, Version 2.0 (the "License"); |
4 | * you may not use this file except in compliance with the License. |
5 | * You may obtain a copy of the License at |
6 | * |
7 | * http://www.apache.org/licenses/LICENSE-2.0 |
8 | * |
9 | * Unless required by applicable law or agreed to in writing, software |
10 | * distributed under the License is distributed on an "AS IS" BASIS, |
11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
12 | * See the License for the specific language governing permissions and |
13 | * limitations under the License. |
14 | */ |
15 | #include "olm/account.hh" |
16 | #include "olm/base64.hh" |
17 | #include "olm/pickle.h" |
18 | #include "olm/pickle.hh" |
19 | #include "olm/memory.hh" |
20 | |
21 | olm::Account::Account( |
22 | ) : num_fallback_keys(0), |
23 | next_one_time_key_id(0), |
24 | last_error(OlmErrorCode::OLM_SUCCESS) { |
25 | } |
26 | |
27 | |
28 | olm::OneTimeKey const * olm::Account::lookup_key( |
29 | _olm_curve25519_public_key const & public_key |
30 | ) { |
31 | for (olm::OneTimeKey const & key : one_time_keys) { |
32 | if (olm::array_equal(array_a: key.key.public_key.public_key, array_b: public_key.public_key)) { |
33 | return &key; |
34 | } |
35 | } |
36 | if (num_fallback_keys >= 1 |
37 | && olm::array_equal( |
38 | array_a: current_fallback_key.key.public_key.public_key, array_b: public_key.public_key |
39 | ) |
40 | ) { |
41 | return ¤t_fallback_key; |
42 | } |
43 | if (num_fallback_keys >= 2 |
44 | && olm::array_equal( |
45 | array_a: prev_fallback_key.key.public_key.public_key, array_b: public_key.public_key |
46 | ) |
47 | ) { |
48 | return &prev_fallback_key; |
49 | } |
50 | return 0; |
51 | } |
52 | |
53 | std::size_t olm::Account::remove_key( |
54 | _olm_curve25519_public_key const & public_key |
55 | ) { |
56 | OneTimeKey * i; |
57 | for (i = one_time_keys.begin(); i != one_time_keys.end(); ++i) { |
58 | if (olm::array_equal(array_a: i->key.public_key.public_key, array_b: public_key.public_key)) { |
59 | std::uint32_t id = i->id; |
60 | one_time_keys.erase(pos: i); |
61 | return id; |
62 | } |
63 | } |
64 | // check if the key is a fallback key, to avoid returning an error, but |
65 | // don't actually remove it |
66 | if (num_fallback_keys >= 1 |
67 | && olm::array_equal( |
68 | array_a: current_fallback_key.key.public_key.public_key, array_b: public_key.public_key |
69 | ) |
70 | ) { |
71 | return current_fallback_key.id; |
72 | } |
73 | if (num_fallback_keys >= 2 |
74 | && olm::array_equal( |
75 | array_a: prev_fallback_key.key.public_key.public_key, array_b: public_key.public_key |
76 | ) |
77 | ) { |
78 | return prev_fallback_key.id; |
79 | } |
80 | return std::size_t(-1); |
81 | } |
82 | |
83 | std::size_t olm::Account::new_account_random_length() const { |
84 | return ED25519_RANDOM_LENGTH + CURVE25519_RANDOM_LENGTH; |
85 | } |
86 | |
87 | std::size_t olm::Account::new_account( |
88 | uint8_t const * random, std::size_t random_length |
89 | ) { |
90 | if (random_length < new_account_random_length()) { |
91 | last_error = OlmErrorCode::OLM_NOT_ENOUGH_RANDOM; |
92 | return std::size_t(-1); |
93 | } |
94 | |
95 | _olm_crypto_ed25519_generate_key(random_bytes: random, output: &identity_keys.ed25519_key); |
96 | random += ED25519_RANDOM_LENGTH; |
97 | _olm_crypto_curve25519_generate_key(random_32_bytes: random, output: &identity_keys.curve25519_key); |
98 | |
99 | return 0; |
100 | } |
101 | |
102 | namespace { |
103 | |
104 | uint8_t KEY_JSON_ED25519[] = "\"ed25519\":" ; |
105 | uint8_t KEY_JSON_CURVE25519[] = "\"curve25519\":" ; |
106 | |
107 | template<typename T> |
108 | static std::uint8_t * write_string( |
109 | std::uint8_t * pos, |
110 | T const & value |
111 | ) { |
112 | std::memcpy(dest: pos, src: value, n: sizeof(T) - 1); |
113 | return pos + (sizeof(T) - 1); |
114 | } |
115 | |
116 | } |
117 | |
118 | |
119 | std::size_t olm::Account::get_identity_json_length() const { |
120 | std::size_t length = 0; |
121 | length += 1; /* { */ |
122 | length += sizeof(KEY_JSON_CURVE25519) - 1; |
123 | length += 1; /* " */ |
124 | length += olm::encode_base64_length( |
125 | input_length: sizeof(identity_keys.curve25519_key.public_key) |
126 | ); |
127 | length += 2; /* ", */ |
128 | length += sizeof(KEY_JSON_ED25519) - 1; |
129 | length += 1; /* " */ |
130 | length += olm::encode_base64_length( |
131 | input_length: sizeof(identity_keys.ed25519_key.public_key) |
132 | ); |
133 | length += 2; /* "} */ |
134 | return length; |
135 | } |
136 | |
137 | |
138 | std::size_t olm::Account::get_identity_json( |
139 | std::uint8_t * identity_json, std::size_t identity_json_length |
140 | ) { |
141 | std::uint8_t * pos = identity_json; |
142 | size_t expected_length = get_identity_json_length(); |
143 | |
144 | if (identity_json_length < expected_length) { |
145 | last_error = OlmErrorCode::OLM_OUTPUT_BUFFER_TOO_SMALL; |
146 | return std::size_t(-1); |
147 | } |
148 | |
149 | *(pos++) = '{'; |
150 | pos = write_string(pos, value: KEY_JSON_CURVE25519); |
151 | *(pos++) = '\"'; |
152 | pos = olm::encode_base64( |
153 | input: identity_keys.curve25519_key.public_key.public_key, |
154 | input_length: sizeof(identity_keys.curve25519_key.public_key.public_key), |
155 | output: pos |
156 | ); |
157 | *(pos++) = '\"'; *(pos++) = ','; |
158 | pos = write_string(pos, value: KEY_JSON_ED25519); |
159 | *(pos++) = '\"'; |
160 | pos = olm::encode_base64( |
161 | input: identity_keys.ed25519_key.public_key.public_key, |
162 | input_length: sizeof(identity_keys.ed25519_key.public_key.public_key), |
163 | output: pos |
164 | ); |
165 | *(pos++) = '\"'; *(pos++) = '}'; |
166 | return pos - identity_json; |
167 | } |
168 | |
169 | |
170 | std::size_t olm::Account::signature_length( |
171 | ) const { |
172 | return ED25519_SIGNATURE_LENGTH; |
173 | } |
174 | |
175 | |
176 | std::size_t olm::Account::sign( |
177 | std::uint8_t const * message, std::size_t message_length, |
178 | std::uint8_t * signature, std::size_t signature_length |
179 | ) { |
180 | if (signature_length < this->signature_length()) { |
181 | last_error = OlmErrorCode::OLM_OUTPUT_BUFFER_TOO_SMALL; |
182 | return std::size_t(-1); |
183 | } |
184 | _olm_crypto_ed25519_sign( |
185 | our_key: &identity_keys.ed25519_key, message, message_length, output: signature |
186 | ); |
187 | return this->signature_length(); |
188 | } |
189 | |
190 | |
191 | std::size_t olm::Account::get_one_time_keys_json_length( |
192 | ) const { |
193 | std::size_t length = 0; |
194 | bool is_empty = true; |
195 | for (auto const & key : one_time_keys) { |
196 | if (key.published) { |
197 | continue; |
198 | } |
199 | is_empty = false; |
200 | length += 2; /* {" */ |
201 | length += olm::encode_base64_length(_olm_pickle_uint32_length(key.id)); |
202 | length += 3; /* ":" */ |
203 | length += olm::encode_base64_length(input_length: sizeof(key.key.public_key)); |
204 | length += 1; /* " */ |
205 | } |
206 | if (is_empty) { |
207 | length += 1; /* { */ |
208 | } |
209 | length += 3; /* }{} */ |
210 | length += sizeof(KEY_JSON_CURVE25519) - 1; |
211 | return length; |
212 | } |
213 | |
214 | |
215 | std::size_t olm::Account::get_one_time_keys_json( |
216 | std::uint8_t * one_time_json, std::size_t one_time_json_length |
217 | ) { |
218 | std::uint8_t * pos = one_time_json; |
219 | if (one_time_json_length < get_one_time_keys_json_length()) { |
220 | last_error = OlmErrorCode::OLM_OUTPUT_BUFFER_TOO_SMALL; |
221 | return std::size_t(-1); |
222 | } |
223 | *(pos++) = '{'; |
224 | pos = write_string(pos, value: KEY_JSON_CURVE25519); |
225 | std::uint8_t sep = '{'; |
226 | for (auto const & key : one_time_keys) { |
227 | if (key.published) { |
228 | continue; |
229 | } |
230 | *(pos++) = sep; |
231 | *(pos++) = '\"'; |
232 | std::uint8_t key_id[_olm_pickle_uint32_length(key.id)]; |
233 | _olm_pickle_uint32(pos: key_id, value: key.id); |
234 | pos = olm::encode_base64(input: key_id, input_length: sizeof(key_id), output: pos); |
235 | *(pos++) = '\"'; *(pos++) = ':'; *(pos++) = '\"'; |
236 | pos = olm::encode_base64( |
237 | input: key.key.public_key.public_key, input_length: sizeof(key.key.public_key.public_key), output: pos |
238 | ); |
239 | *(pos++) = '\"'; |
240 | sep = ','; |
241 | } |
242 | if (sep != ',') { |
243 | /* The list was empty */ |
244 | *(pos++) = sep; |
245 | } |
246 | *(pos++) = '}'; |
247 | *(pos++) = '}'; |
248 | return pos - one_time_json; |
249 | } |
250 | |
251 | |
252 | std::size_t olm::Account::mark_keys_as_published( |
253 | ) { |
254 | std::size_t count = 0; |
255 | for (auto & key : one_time_keys) { |
256 | if (!key.published) { |
257 | key.published = true; |
258 | count++; |
259 | } |
260 | } |
261 | current_fallback_key.published = true; |
262 | return count; |
263 | } |
264 | |
265 | |
266 | std::size_t olm::Account::max_number_of_one_time_keys( |
267 | ) const { |
268 | return olm::MAX_ONE_TIME_KEYS; |
269 | } |
270 | |
271 | std::size_t olm::Account::generate_one_time_keys_random_length( |
272 | std::size_t number_of_keys |
273 | ) const { |
274 | return CURVE25519_RANDOM_LENGTH * number_of_keys; |
275 | } |
276 | |
277 | std::size_t olm::Account::generate_one_time_keys( |
278 | std::size_t number_of_keys, |
279 | std::uint8_t const * random, std::size_t random_length |
280 | ) { |
281 | if (random_length < generate_one_time_keys_random_length(number_of_keys)) { |
282 | last_error = OlmErrorCode::OLM_NOT_ENOUGH_RANDOM; |
283 | return std::size_t(-1); |
284 | } |
285 | for (unsigned i = 0; i < number_of_keys; ++i) { |
286 | OneTimeKey & key = *one_time_keys.insert(pos: one_time_keys.begin()); |
287 | key.id = ++next_one_time_key_id; |
288 | key.published = false; |
289 | _olm_crypto_curve25519_generate_key(random_32_bytes: random, output: &key.key); |
290 | random += CURVE25519_RANDOM_LENGTH; |
291 | } |
292 | return number_of_keys; |
293 | } |
294 | |
295 | std::size_t olm::Account::generate_fallback_key_random_length() const { |
296 | return CURVE25519_RANDOM_LENGTH; |
297 | } |
298 | |
299 | std::size_t olm::Account::generate_fallback_key( |
300 | std::uint8_t const * random, std::size_t random_length |
301 | ) { |
302 | if (random_length < generate_fallback_key_random_length()) { |
303 | last_error = OlmErrorCode::OLM_NOT_ENOUGH_RANDOM; |
304 | return std::size_t(-1); |
305 | } |
306 | if (num_fallback_keys < 2) { |
307 | num_fallback_keys++; |
308 | } |
309 | prev_fallback_key = current_fallback_key; |
310 | current_fallback_key.id = ++next_one_time_key_id; |
311 | current_fallback_key.published = false; |
312 | _olm_crypto_curve25519_generate_key(random_32_bytes: random, output: ¤t_fallback_key.key); |
313 | return 1; |
314 | } |
315 | |
316 | |
317 | std::size_t olm::Account::get_fallback_key_json_length( |
318 | ) const { |
319 | std::size_t length = 4 + sizeof(KEY_JSON_CURVE25519) - 1; /* {"curve25519":{}} */ |
320 | if (num_fallback_keys >= 1) { |
321 | const OneTimeKey & key = current_fallback_key; |
322 | length += 1; /* " */ |
323 | length += olm::encode_base64_length(_olm_pickle_uint32_length(key.id)); |
324 | length += 3; /* ":" */ |
325 | length += olm::encode_base64_length(input_length: sizeof(key.key.public_key)); |
326 | length += 1; /* " */ |
327 | } |
328 | return length; |
329 | } |
330 | |
331 | std::size_t olm::Account::get_fallback_key_json( |
332 | std::uint8_t * fallback_json, std::size_t fallback_json_length |
333 | ) { |
334 | std::uint8_t * pos = fallback_json; |
335 | if (fallback_json_length < get_fallback_key_json_length()) { |
336 | last_error = OlmErrorCode::OLM_OUTPUT_BUFFER_TOO_SMALL; |
337 | return std::size_t(-1); |
338 | } |
339 | *(pos++) = '{'; |
340 | pos = write_string(pos, value: KEY_JSON_CURVE25519); |
341 | *(pos++) = '{'; |
342 | OneTimeKey & key = current_fallback_key; |
343 | if (num_fallback_keys >= 1) { |
344 | *(pos++) = '\"'; |
345 | std::uint8_t key_id[_olm_pickle_uint32_length(key.id)]; |
346 | _olm_pickle_uint32(pos: key_id, value: key.id); |
347 | pos = olm::encode_base64(input: key_id, input_length: sizeof(key_id), output: pos); |
348 | *(pos++) = '\"'; *(pos++) = ':'; *(pos++) = '\"'; |
349 | pos = olm::encode_base64( |
350 | input: key.key.public_key.public_key, input_length: sizeof(key.key.public_key.public_key), output: pos |
351 | ); |
352 | *(pos++) = '\"'; |
353 | } |
354 | *(pos++) = '}'; |
355 | *(pos++) = '}'; |
356 | return pos - fallback_json; |
357 | } |
358 | |
359 | std::size_t olm::Account::get_unpublished_fallback_key_json_length( |
360 | ) const { |
361 | std::size_t length = 4 + sizeof(KEY_JSON_CURVE25519) - 1; /* {"curve25519":{}} */ |
362 | const OneTimeKey & key = current_fallback_key; |
363 | if (num_fallback_keys >= 1 && !key.published) { |
364 | length += 1; /* " */ |
365 | length += olm::encode_base64_length(_olm_pickle_uint32_length(key.id)); |
366 | length += 3; /* ":" */ |
367 | length += olm::encode_base64_length(input_length: sizeof(key.key.public_key)); |
368 | length += 1; /* " */ |
369 | } |
370 | return length; |
371 | } |
372 | |
373 | std::size_t olm::Account::get_unpublished_fallback_key_json( |
374 | std::uint8_t * fallback_json, std::size_t fallback_json_length |
375 | ) { |
376 | std::uint8_t * pos = fallback_json; |
377 | if (fallback_json_length < get_unpublished_fallback_key_json_length()) { |
378 | last_error = OlmErrorCode::OLM_OUTPUT_BUFFER_TOO_SMALL; |
379 | return std::size_t(-1); |
380 | } |
381 | *(pos++) = '{'; |
382 | pos = write_string(pos, value: KEY_JSON_CURVE25519); |
383 | *(pos++) = '{'; |
384 | OneTimeKey & key = current_fallback_key; |
385 | if (num_fallback_keys >= 1 && !key.published) { |
386 | *(pos++) = '\"'; |
387 | std::uint8_t key_id[_olm_pickle_uint32_length(key.id)]; |
388 | _olm_pickle_uint32(pos: key_id, value: key.id); |
389 | pos = olm::encode_base64(input: key_id, input_length: sizeof(key_id), output: pos); |
390 | *(pos++) = '\"'; *(pos++) = ':'; *(pos++) = '\"'; |
391 | pos = olm::encode_base64( |
392 | input: key.key.public_key.public_key, input_length: sizeof(key.key.public_key.public_key), output: pos |
393 | ); |
394 | *(pos++) = '\"'; |
395 | } |
396 | *(pos++) = '}'; |
397 | *(pos++) = '}'; |
398 | return pos - fallback_json; |
399 | } |
400 | |
401 | void olm::Account::forget_old_fallback_key( |
402 | ) { |
403 | if (num_fallback_keys >= 2) { |
404 | num_fallback_keys = 1; |
405 | olm::unset(buffer: &prev_fallback_key, buffer_length: sizeof(prev_fallback_key)); |
406 | } |
407 | } |
408 | |
409 | namespace olm { |
410 | |
411 | static std::size_t pickle_length( |
412 | olm::IdentityKeys const & value |
413 | ) { |
414 | size_t length = 0; |
415 | length += _olm_pickle_ed25519_key_pair_length(value: &value.ed25519_key); |
416 | length += olm::pickle_length(value: value.curve25519_key); |
417 | return length; |
418 | } |
419 | |
420 | |
421 | static std::uint8_t * pickle( |
422 | std::uint8_t * pos, |
423 | olm::IdentityKeys const & value |
424 | ) { |
425 | pos = _olm_pickle_ed25519_key_pair(pos, value: &value.ed25519_key); |
426 | pos = olm::pickle(pos, value: value.curve25519_key); |
427 | return pos; |
428 | } |
429 | |
430 | |
431 | static std::uint8_t const * unpickle( |
432 | std::uint8_t const * pos, std::uint8_t const * end, |
433 | olm::IdentityKeys & value |
434 | ) { |
435 | pos = _olm_unpickle_ed25519_key_pair(pos, end, value: &value.ed25519_key); UNPICKLE_OK(pos); |
436 | pos = olm::unpickle(pos, end, value&: value.curve25519_key); UNPICKLE_OK(pos); |
437 | return pos; |
438 | } |
439 | |
440 | |
441 | static std::size_t pickle_length( |
442 | olm::OneTimeKey const & value |
443 | ) { |
444 | std::size_t length = 0; |
445 | length += olm::pickle_length(value: value.id); |
446 | length += olm::pickle_length(value: value.published); |
447 | length += olm::pickle_length(value: value.key); |
448 | return length; |
449 | } |
450 | |
451 | |
452 | static std::uint8_t * pickle( |
453 | std::uint8_t * pos, |
454 | olm::OneTimeKey const & value |
455 | ) { |
456 | pos = olm::pickle(pos, value: value.id); |
457 | pos = olm::pickle(pos, value: value.published); |
458 | pos = olm::pickle(pos, value: value.key); |
459 | return pos; |
460 | } |
461 | |
462 | |
463 | static std::uint8_t const * unpickle( |
464 | std::uint8_t const * pos, std::uint8_t const * end, |
465 | olm::OneTimeKey & value |
466 | ) { |
467 | pos = olm::unpickle(pos, end, value&: value.id); UNPICKLE_OK(pos); |
468 | pos = olm::unpickle(pos, end, value&: value.published); UNPICKLE_OK(pos); |
469 | pos = olm::unpickle(pos, end, value&: value.key); UNPICKLE_OK(pos); |
470 | return pos; |
471 | } |
472 | |
473 | } // namespace olm |
474 | |
475 | namespace { |
476 | // pickle version 1 used only 32 bytes for the ed25519 private key. |
477 | // Any keys thus used should be considered compromised. |
478 | // pickle version 2 does not have fallback keys. |
479 | // pickle version 3 does not store whether the current fallback key is published. |
480 | static const std::uint32_t ACCOUNT_PICKLE_VERSION = 4; |
481 | } |
482 | |
483 | |
484 | std::size_t olm::pickle_length( |
485 | olm::Account const & value |
486 | ) { |
487 | std::size_t length = 0; |
488 | length += olm::pickle_length(value: ACCOUNT_PICKLE_VERSION); |
489 | length += olm::pickle_length(value: value.identity_keys); |
490 | length += olm::pickle_length(list: value.one_time_keys); |
491 | length += olm::pickle_length(value: value.num_fallback_keys); |
492 | if (value.num_fallback_keys >= 1) { |
493 | length += olm::pickle_length(value: value.current_fallback_key); |
494 | if (value.num_fallback_keys >= 2) { |
495 | length += olm::pickle_length(value: value.prev_fallback_key); |
496 | } |
497 | } |
498 | length += olm::pickle_length(value: value.next_one_time_key_id); |
499 | return length; |
500 | } |
501 | |
502 | |
503 | std::uint8_t * olm::pickle( |
504 | std::uint8_t * pos, |
505 | olm::Account const & value |
506 | ) { |
507 | pos = olm::pickle(pos, value: ACCOUNT_PICKLE_VERSION); |
508 | pos = olm::pickle(pos, value: value.identity_keys); |
509 | pos = olm::pickle(pos, list: value.one_time_keys); |
510 | pos = olm::pickle(pos, value: value.num_fallback_keys); |
511 | if (value.num_fallback_keys >= 1) { |
512 | pos = olm::pickle(pos, value: value.current_fallback_key); |
513 | if (value.num_fallback_keys >= 2) { |
514 | pos = olm::pickle(pos, value: value.prev_fallback_key); |
515 | } |
516 | } |
517 | pos = olm::pickle(pos, value: value.next_one_time_key_id); |
518 | return pos; |
519 | } |
520 | |
521 | |
522 | std::uint8_t const * olm::unpickle( |
523 | std::uint8_t const * pos, std::uint8_t const * end, |
524 | olm::Account & value |
525 | ) { |
526 | uint32_t pickle_version; |
527 | |
528 | pos = olm::unpickle(pos, end, value&: pickle_version); UNPICKLE_OK(pos); |
529 | |
530 | switch (pickle_version) { |
531 | case ACCOUNT_PICKLE_VERSION: |
532 | case 3: |
533 | case 2: |
534 | break; |
535 | case 1: |
536 | value.last_error = OlmErrorCode::OLM_BAD_LEGACY_ACCOUNT_PICKLE; |
537 | return nullptr; |
538 | default: |
539 | value.last_error = OlmErrorCode::OLM_UNKNOWN_PICKLE_VERSION; |
540 | return nullptr; |
541 | } |
542 | |
543 | pos = olm::unpickle(pos, end, value&: value.identity_keys); UNPICKLE_OK(pos); |
544 | pos = olm::unpickle(pos, end, list&: value.one_time_keys); UNPICKLE_OK(pos); |
545 | |
546 | if (pickle_version <= 2) { |
547 | // version 2 did not have fallback keys |
548 | value.num_fallback_keys = 0; |
549 | } else if (pickle_version == 3) { |
550 | // version 3 used the published flag to indicate how many fallback keys |
551 | // were present (we'll have to assume that the keys were published) |
552 | pos = olm::unpickle(pos, end, value&: value.current_fallback_key); UNPICKLE_OK(pos); |
553 | pos = olm::unpickle(pos, end, value&: value.prev_fallback_key); UNPICKLE_OK(pos); |
554 | if (value.current_fallback_key.published) { |
555 | if (value.prev_fallback_key.published) { |
556 | value.num_fallback_keys = 2; |
557 | } else { |
558 | value.num_fallback_keys = 1; |
559 | } |
560 | } else { |
561 | value.num_fallback_keys = 0; |
562 | } |
563 | } else { |
564 | pos = olm::unpickle(pos, end, value&: value.num_fallback_keys); UNPICKLE_OK(pos); |
565 | if (value.num_fallback_keys >= 1) { |
566 | pos = olm::unpickle(pos, end, value&: value.current_fallback_key); UNPICKLE_OK(pos); |
567 | if (value.num_fallback_keys >= 2) { |
568 | pos = olm::unpickle(pos, end, value&: value.prev_fallback_key); UNPICKLE_OK(pos); |
569 | if (value.num_fallback_keys >= 3) { |
570 | value.last_error = OlmErrorCode::OLM_CORRUPTED_PICKLE; |
571 | return nullptr; |
572 | } |
573 | } |
574 | } |
575 | } |
576 | |
577 | pos = olm::unpickle(pos, end, value&: value.next_one_time_key_id); UNPICKLE_OK(pos); |
578 | |
579 | return pos; |
580 | } |
581 | |