1 /**
2  * @file cryptopp.cpp
3  * @brief Crypto layer using Crypto++
4  *
5  * (c) 2013-2014 by Mega Limited, Auckland, New Zealand
6  *
7  * This file is part of the MEGA SDK - Client Access Engine.
8  *
9  * Applications using the MEGA API must present a valid application key
10  * and comply with the the rules set forth in the Terms of Service.
11  *
12  * The MEGA SDK is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
15  *
16  * @copyright Simplified (2-clause) BSD License.
17  *
18  * You should have received a copy of the license along with this
19  * program.
20  */
21 
22 #include "mega.h"
23 
24 namespace mega {
25 #ifndef htobe64
26 #define htobe64(x) (((uint64_t)htonl((uint32_t)((x) >> 32))) | (((uint64_t)htonl((uint32_t)x)) << 32))
27 #endif
28 
29 using namespace CryptoPP;
30 
31 // cryptographically strong random byte sequence
genblock(byte * buf,size_t len)32 void PrnGen::genblock(byte* buf, size_t len)
33 {
34     GenerateBlock(buf, len);
35 }
36 
37 // random number from 0 ... max-1
genuint32(uint64_t max)38 uint32_t PrnGen::genuint32(uint64_t max)
39 {
40     uint32_t t;
41 
42     genblock((byte*)&t, sizeof t);
43 
44     return (uint32_t)(((uint64_t)t) / ((((uint64_t)(~(uint32_t)0)) + 1) / max));
45 }
46 
SymmCipher(const byte * key)47 SymmCipher::SymmCipher(const byte* key)
48 {
49     setkey(key);
50 }
51 
52 byte SymmCipher::zeroiv[BLOCKSIZE];
53 
setkey(const byte * newkey,int type)54 void SymmCipher::setkey(const byte* newkey, int type)
55 {
56     memcpy(key, newkey, KEYLENGTH);
57 
58     if (!type)
59     {
60         xorblock(newkey + KEYLENGTH, key);
61     }
62 
63     aesecb_e.SetKey(key, KEYLENGTH);
64     aesecb_d.SetKey(key, KEYLENGTH);
65 
66     aescbc_e.SetKeyWithIV(key, KEYLENGTH, zeroiv);
67     aescbc_d.SetKeyWithIV(key, KEYLENGTH, zeroiv);
68 
69     aesccm8_e.SetKeyWithIV(key, KEYLENGTH, zeroiv);
70     aesccm8_d.SetKeyWithIV(key, KEYLENGTH, zeroiv);
71 
72     aesccm16_e.SetKeyWithIV(key, KEYLENGTH, zeroiv);
73     aesccm16_d.SetKeyWithIV(key, KEYLENGTH, zeroiv);
74 
75     aesgcm_e.SetKeyWithIV(key, KEYLENGTH, zeroiv);
76     aesgcm_d.SetKeyWithIV(key, KEYLENGTH, zeroiv);
77 }
78 
setkey(const string * key)79 bool SymmCipher::setkey(const string* key)
80 {
81     if (key->size() == FILENODEKEYLENGTH || key->size() == FOLDERNODEKEYLENGTH)
82     {
83         setkey((const byte*)key->data(), (key->size() == FOLDERNODEKEYLENGTH) ? FOLDERNODE : FILENODE);
84 
85         return true;
86     }
87 
88     return false;
89 }
90 
cbc_encrypt(byte * data,size_t len,const byte * iv)91 void SymmCipher::cbc_encrypt(byte* data, size_t len, const byte* iv)
92 {
93     aescbc_e.Resynchronize(iv ? iv : zeroiv);
94     aescbc_e.ProcessData(data, data, len);
95 }
96 
cbc_decrypt(byte * data,size_t len,const byte * iv)97 void SymmCipher::cbc_decrypt(byte* data, size_t len, const byte* iv)
98 {
99     aescbc_d.Resynchronize(iv ? iv : zeroiv);
100     aescbc_d.ProcessData(data, data, len);
101 }
102 
cbc_encrypt_pkcs_padding(const string * data,const byte * iv,string * result)103 void SymmCipher::cbc_encrypt_pkcs_padding(const string *data, const byte *iv, string *result)
104 {
105     aescbc_e.Resynchronize(iv ? iv : zeroiv);
106     StringSource(*data, true,
107            new StreamTransformationFilter( aescbc_e, new StringSink( *result ),
108                                                      StreamTransformationFilter::PKCS_PADDING));
109 }
110 
cbc_decrypt_pkcs_padding(const std::string * data,const byte * iv,string * result)111 void SymmCipher::cbc_decrypt_pkcs_padding(const std::string *data, const byte *iv, string *result)
112 {
113     aescbc_d.Resynchronize(iv ? iv : zeroiv);
114     StringSource(*data, true,
115            new StreamTransformationFilter( aescbc_d, new StringSink( *result ),
116                                                      StreamTransformationFilter::PKCS_PADDING));
117 }
118 
ecb_encrypt(byte * data,byte * dst,size_t len)119 void SymmCipher::ecb_encrypt(byte* data, byte* dst, size_t len)
120 {
121     aesecb_e.ProcessData(dst ? dst : data, data, len);
122 }
123 
ecb_decrypt(byte * data,size_t len)124 void SymmCipher::ecb_decrypt(byte* data, size_t len)
125 {
126     aesecb_d.ProcessData(data, data, len);
127 }
128 
ccm_encrypt(const string * data,const byte * iv,unsigned ivlen,unsigned taglen,string * result)129 void SymmCipher::ccm_encrypt(const string *data, const byte *iv, unsigned ivlen, unsigned taglen, string *result)
130 {
131     if (taglen == 16)
132     {
133         aesccm16_e.Resynchronize(iv, ivlen);
134         aesccm16_e.SpecifyDataLengths(0, data->size(), 0);
135         StringSource(*data, true, new AuthenticatedEncryptionFilter(aesccm16_e, new StringSink(*result)));
136     }
137     else if (taglen == 8)
138     {
139         aesccm8_e.Resynchronize(iv, ivlen);
140         aesccm8_e.SpecifyDataLengths(0, data->size(), 0);
141         StringSource(*data, true, new AuthenticatedEncryptionFilter(aesccm8_e, new StringSink(*result)));
142     }
143 }
144 
ccm_decrypt(const string * data,const byte * iv,unsigned ivlen,unsigned taglen,string * result)145 bool SymmCipher::ccm_decrypt(const string *data, const byte *iv, unsigned ivlen, unsigned taglen, string *result)
146 {
147     try {
148         if (taglen == 16)
149         {
150             aesccm16_d.Resynchronize(iv, ivlen);
151             aesccm16_d.SpecifyDataLengths(0, data->size() - taglen, 0);
152             StringSource(*data, true, new AuthenticatedDecryptionFilter(aesccm16_d, new StringSink(*result)));
153         }
154         else if (taglen == 8)
155         {
156             aesccm8_d.Resynchronize(iv, ivlen);
157             aesccm8_d.SpecifyDataLengths(0, data->size() - taglen, 0);
158             StringSource(*data, true, new AuthenticatedDecryptionFilter(aesccm8_d, new StringSink(*result)));
159         }
160     } catch (HashVerificationFilter::HashVerificationFailed e)
161     {
162         result->clear();
163         LOG_err << "Failed AES-CCM decryption: " << e.GetWhat();
164         return false;
165     }
166     return true;
167 }
168 
gcm_encrypt(const string * data,const byte * iv,unsigned ivlen,unsigned taglen,string * result)169 void SymmCipher::gcm_encrypt(const string *data, const byte *iv, unsigned ivlen, unsigned taglen, string *result)
170 {
171     aesgcm_e.Resynchronize(iv, ivlen);
172     StringSource(*data, true, new AuthenticatedEncryptionFilter(aesgcm_e, new StringSink(*result), false, taglen));
173 }
174 
gcm_decrypt(const string * data,const byte * iv,unsigned ivlen,unsigned taglen,string * result)175 bool SymmCipher::gcm_decrypt(const string *data, const byte *iv, unsigned ivlen, unsigned taglen, string *result)
176 {
177     aesgcm_d.Resynchronize(iv, ivlen);
178     try {
179         StringSource(*data, true, new AuthenticatedDecryptionFilter(aesgcm_d, new StringSink(*result), taglen));
180     } catch (HashVerificationFilter::HashVerificationFailed e)
181     {
182         result->clear();
183         LOG_err << "Failed AES-GCM decryption: " << e.GetWhat();
184         return false;
185     }
186     return true;
187 }
188 
serializekeyforjs(string * d)189 void SymmCipher::serializekeyforjs(string *d)
190 {
191     char invertedkey[BLOCKSIZE];
192     std::stringstream ss;
193 
194     ss << "[";
195     for (int i=0; i<BLOCKSIZE; i++)
196     {
197         invertedkey[i] = key[BLOCKSIZE - i - 1];
198     }
199 
200     int32_t *k = (int32_t *)invertedkey;
201     for (int i = 3; i >= 0; i--)
202     {
203         ss << k[i];
204         if (i)
205         {
206             ss << ",";
207         }
208     }
209     ss << "]";
210     *d = ss.str();
211 }
212 
setint64(int64_t value,byte * data)213 void SymmCipher::setint64(int64_t value, byte* data)
214 {
215 #if __BYTE_ORDER == __LITTLE_ENDIAN
216     value = htobe64(value);
217 #else
218 #if __BYTE_ORDER != __BIG_ENDIAN
219 #error "Unknown or unsupported endianness"
220 #endif
221 #endif
222     memcpy(data, (char*)&value, sizeof value);
223 }
224 
xorblock(const byte * src,byte * dst)225 void SymmCipher::xorblock(const byte* src, byte* dst)
226 {
227     long* lsrc = (long*)src;
228     long* ldst = (long*)dst;
229 
230     for (int i = BLOCKSIZE / sizeof(long); i--;)
231     {
232         ldst[i] ^= lsrc[i];
233     }
234 }
235 
xorblock(const byte * src,byte * dst,int len)236 void SymmCipher::xorblock(const byte* src, byte* dst, int len)
237 {
238     while (len--)
239     {
240         dst[len] ^= src[len];
241     }
242 }
243 
incblock(byte * dst,unsigned len)244 void SymmCipher::incblock(byte* dst, unsigned len)
245 {
246     while (len)
247     {
248         if (++dst[--len])
249         {
250             break;
251         }
252     }
253 }
254 
SymmCipher(const SymmCipher & ref)255 SymmCipher::SymmCipher(const SymmCipher &ref)
256 {
257     setkey(ref.key);
258 }
259 
operator =(const SymmCipher & ref)260 SymmCipher& SymmCipher::operator=(const SymmCipher& ref)
261 {
262     setkey(ref.key);
263     return *this;
264 }
265 
266 // encryption: data must be NUL-padded to BLOCKSIZE
267 // decryption: data must be padded to BLOCKSIZE
268 // len must be < 2^31
ctr_crypt(byte * data,unsigned len,m_off_t pos,ctr_iv ctriv,byte * mac,bool encrypt,bool initmac)269 void SymmCipher::ctr_crypt(byte* data, unsigned len, m_off_t pos, ctr_iv ctriv, byte* mac, bool encrypt, bool initmac)
270 {
271     assert(!(pos & (KEYLENGTH - 1)));
272 
273     byte ctr[BLOCKSIZE], tmp[BLOCKSIZE];
274 
275     MemAccess::set<int64_t>(ctr,ctriv);
276     setint64(pos / BLOCKSIZE, ctr + sizeof ctriv);
277 
278     if (mac && initmac)
279     {
280         memcpy(mac, ctr, sizeof ctriv);
281         memcpy(mac + sizeof ctriv, ctr, sizeof ctriv);
282     }
283 
284     while ((int)len > 0)
285     {
286         if (encrypt)
287         {
288             if(mac)
289             {
290                 xorblock(data, mac);
291                 ecb_encrypt(mac);
292             }
293 
294             ecb_encrypt(ctr, tmp);
295             xorblock(tmp, data);
296         }
297         else
298         {
299             ecb_encrypt(ctr, tmp);
300             xorblock(tmp, data);
301 
302             if (mac)
303             {
304                 if (len >= (unsigned)BLOCKSIZE)
305                 {
306                     xorblock(data, mac);
307                 }
308                 else
309                 {
310                     xorblock(data, mac, len);
311                 }
312 
313                 ecb_encrypt(mac);
314             }
315         }
316 
317         len -= BLOCKSIZE;
318         data += BLOCKSIZE;
319 
320         incblock(ctr);
321     }
322 }
323 
rsaencrypt(Integer * key,Integer * m)324 static void rsaencrypt(Integer* key, Integer* m)
325 {
326     *m = a_exp_b_mod_c(*m, key[AsymmCipher::PUB_E], key[AsymmCipher::PUB_PQ]);
327 }
328 
rawencrypt(const byte * plain,size_t plainlen,byte * buf,size_t buflen)329 unsigned AsymmCipher::rawencrypt(const byte* plain, size_t plainlen, byte* buf, size_t buflen)
330 {
331     Integer t(plain, plainlen);
332 
333     rsaencrypt(key, &t);
334 
335     unsigned i = t.ByteCount();
336 
337     if (i > buflen)
338     {
339         return 0;
340     }
341 
342     while (i--)
343     {
344         *buf++ = t.GetByte(i);
345     }
346 
347     return t.ByteCount();
348 }
349 
encrypt(PrnGen & rng,const byte * plain,size_t plainlen,byte * buf,size_t buflen)350 int AsymmCipher::encrypt(PrnGen &rng, const byte* plain, size_t plainlen, byte* buf, size_t buflen)
351 {
352     if (key[PUB_PQ].ByteCount() + 2 > buflen)
353     {
354         return 0;
355     }
356 
357     if (buf != plain)
358     {
359         memcpy(buf, plain, plainlen);
360     }
361 
362     // add random padding
363     rng.genblock(buf + plainlen, key[PUB_PQ].ByteCount() - plainlen - 2);
364 
365     Integer t(buf, key[PUB_PQ].ByteCount() - 2);
366 
367     rsaencrypt(key, &t);
368 
369     int i = t.BitCount();
370 
371     byte* ptr = buf;
372 
373     *ptr++ = (byte)(i >> 8);
374     *ptr++ = (byte)i;
375 
376     i = t.ByteCount();
377 
378     while (i--)
379     {
380         *ptr++ = t.GetByte(i);
381     }
382 
383     return int(ptr - buf);
384 }
385 
rsadecrypt(Integer * key,Integer * m)386 static void rsadecrypt(Integer* key, Integer* m)
387 {
388     Integer xp = a_exp_b_mod_c(*m % key[AsymmCipher::PRIV_P],
389                                key[AsymmCipher::PRIV_D] % (key[AsymmCipher::PRIV_P] - Integer::One()),
390                                key[AsymmCipher::PRIV_P]);
391     Integer xq = a_exp_b_mod_c(*m % key[AsymmCipher::PRIV_Q],
392                                key[AsymmCipher::PRIV_D] % (key[AsymmCipher::PRIV_Q] - Integer::One()),
393                                key[AsymmCipher::PRIV_Q]);
394 
395     if (xp > xq)
396     {
397         *m = key[AsymmCipher::PRIV_Q] - (((xp - xq) * key[AsymmCipher::PRIV_U]) % key[AsymmCipher::PRIV_Q]);
398     }
399     else
400     {
401         *m = ((xq - xp) * key[AsymmCipher::PRIV_U]) % key[AsymmCipher::PRIV_Q];
402     }
403 
404     *m = *m * key[AsymmCipher::PRIV_P] + xp;
405 }
406 
rawdecrypt(const byte * cipher,size_t cipherlen,byte * buf,size_t buflen)407 unsigned AsymmCipher::rawdecrypt(const byte* cipher, size_t cipherlen, byte* buf, size_t buflen)
408 {
409     Integer m(cipher, cipherlen);
410 
411     rsadecrypt(key, &m);
412 
413     unsigned i = m.ByteCount();
414 
415     if (i > buflen)
416     {
417         return 0;
418     }
419 
420     while (i--)
421     {
422         *buf++ = m.GetByte(i);
423     }
424 
425     return m.ByteCount();
426 }
427 
decrypt(const byte * cipher,size_t cipherlen,byte * out,size_t numbytes)428 int AsymmCipher::decrypt(const byte* cipher, size_t cipherlen, byte* out, size_t numbytes)
429 {
430     Integer m;
431 
432     if (!decodeintarray(&m, 1, cipher, int(cipherlen)))
433     {
434         return 0;
435     }
436 
437     rsadecrypt(key, &m);
438 
439     size_t l = key[AsymmCipher::PRIV_P].ByteCount() + key[AsymmCipher::PRIV_Q].ByteCount() - 2;
440 
441     if (m.ByteCount() > l)
442     {
443         l = m.ByteCount();
444     }
445 
446     l -= numbytes;
447 
448     while (numbytes--)
449     {
450         out[numbytes] = m.GetByte(l++);
451     }
452 
453     return 1;
454 }
455 
setkey(int numints,const byte * data,int len)456 int AsymmCipher::setkey(int numints, const byte* data, int len)
457 {
458     int ret = decodeintarray(key, numints, data, len);
459     padding = (numints == PUBKEY && ret) ? (len - key[PUB_PQ].ByteCount() - key[PUB_E].ByteCount() - 4) : 0;
460     return ret;
461 }
462 
resetkey()463 void AsymmCipher::resetkey()
464 {
465     for (int i = 0; i < PRIVKEY; i++)
466     {
467         key[i] = Integer::Zero();
468         padding = 0;
469     }
470 }
471 
serializekeyforjs(string & d)472 void AsymmCipher::serializekeyforjs(string& d)
473 {
474     unsigned sizePQ = key[PUB_PQ].ByteCount();
475     unsigned sizeE = key[PUB_E].ByteCount();
476     char c;
477 
478     d.clear();
479     d.reserve(sizePQ + sizeE + padding);
480 
481     for (int j = key[PUB_PQ].ByteCount(); j--;)
482     {
483         c = key[PUB_PQ].GetByte(j);
484         d.append(&c, sizeof c);
485     }
486 
487     // accounts created by webclient use 4 bytes for serialization of exponent
488     // --> add left-padding up to 4 bytes for compatibility reasons
489     c = 0;
490     for (unsigned j = 0; j < padding; j++)
491     {
492         d.append(&c, sizeof c);
493     }
494 
495     for (int j = sizeE; j--;)
496     {
497         c = key[PUB_E].GetByte(j);  // returns 0 if out-of-range
498         d.append(&c, sizeof c);
499     }
500 }
501 
serializekey(string * d,int keytype)502 void AsymmCipher::serializekey(string* d, int keytype)
503 {
504     serializeintarray(key, keytype, d);
505 }
506 
serializeintarray(Integer * t,int numints,string * d,bool headers)507 void AsymmCipher::serializeintarray(Integer* t, int numints, string* d, bool headers)
508 {
509     unsigned size = 0;
510     char c;
511 
512     for (int i = numints; i--;)
513     {
514         size += t[i].ByteCount();
515 
516         if (headers)
517         {
518             size += 2;
519         }
520     }
521 
522     d->reserve(d->size() + size);
523 
524     for (int i = 0; i < numints; i++)
525     {
526         if (headers)
527         {
528             c = static_cast<char>(t[i].BitCount() >> 8);
529             d->append(&c, sizeof c);
530 
531             c = (char)t[i].BitCount();
532             d->append(&c, sizeof c);
533         }
534 
535         for (int j = t[i].ByteCount(); j--;)
536         {
537             c = t[i].GetByte(j);
538             d->append(&c, sizeof c);
539         }
540     }
541 }
542 
decodeintarray(Integer * t,int numints,const byte * data,int len)543 int AsymmCipher::decodeintarray(Integer* t, int numints, const byte* data, int len)
544 {
545     int p, i, n;
546 
547     p = 0;
548 
549     for (i = 0; i < numints; i++)
550     {
551         if (p + 2 > len)
552         {
553             break;
554         }
555 
556         n = ((data[p] << 8) + data[p + 1] + 7) >> 3;
557 
558         p += 2;
559         if (p + n > len)
560         {
561             break;
562         }
563 
564         t[i] = Integer(data + p, n);
565 
566         p += n;
567     }
568 
569     return i == numints && len - p < 16;
570 }
571 
isvalid(int keytype)572 int AsymmCipher::isvalid(int keytype)
573 {
574     if (keytype == PUBKEY)
575     {
576         return key[PUB_PQ].BitCount() && key[PUB_E].BitCount();
577     }
578 
579     if (keytype == PRIVKEY)
580     {
581         return key[PRIV_P].BitCount() &&
582                 key[PRIV_Q].BitCount() &&
583                 key[PRIV_D].BitCount() &&
584                 key[PRIV_U].BitCount();
585     }
586 
587     return 0;
588 }
589 
590 // adapted from CryptoPP, rsa.cpp
591 class RSAPrimeSelector : public PrimeSelector
592 {
593     Integer m_e;
594 
595 public:
RSAPrimeSelector(const Integer & e)596     RSAPrimeSelector(const Integer &e) : m_e(e) { }
597 
IsAcceptable(const Integer & candidate) const598     bool IsAcceptable(const Integer &candidate) const
599     {
600         return RelativelyPrime(m_e, candidate - Integer::One());
601     }
602 };
603 
604 // generate RSA keypair
genkeypair(PrnGen & rng,Integer * privk,Integer * pubk,int size)605 void AsymmCipher::genkeypair(PrnGen &rng, Integer* privk, Integer* pubk, int size)
606 {
607     pubk[PUB_E] = 17;
608 
609     RSAPrimeSelector selector(pubk[PUB_E]);
610     AlgorithmParameters primeParam
611             = MakeParametersForTwoPrimesOfEqualSize(size)
612                 (Name::PointerToPrimeSelector(), selector.GetSelectorPointer());
613 
614     privk[PRIV_P].GenerateRandom(rng, primeParam);
615     privk[PRIV_Q].GenerateRandom(rng, primeParam);
616 
617     privk[PRIV_D] = pubk[PUB_E].InverseMod(LCM(privk[PRIV_P] - Integer::One(), privk[PRIV_Q] - Integer::One()));
618     pubk[PUB_PQ] = privk[PRIV_P] * privk[PRIV_Q];
619     privk[PRIV_U] = privk[PRIV_P].InverseMod(privk[PRIV_Q]);
620 }
621 
add(const byte * data,unsigned len)622 void Hash::add(const byte* data, unsigned len)
623 {
624     hash.Update(data, len);
625 }
626 
get(string * out)627 void Hash::get(string* out)
628 {
629     out->resize(hash.DigestSize());
630     hash.Final((byte*)out->data());
631 }
632 
add(const byte * data,unsigned int len)633 void HashSHA256::add(const byte *data, unsigned int len)
634 {
635     hash.Update(data, len);
636 }
637 
get(std::string * retStr)638 void HashSHA256::get(std::string *retStr)
639 {
640     retStr->resize(hash.DigestSize());
641     hash.Final((byte*)retStr->data());
642 }
643 
add(const byte * data,unsigned len)644 void HashCRC32::add(const byte* data, unsigned len)
645 {
646     hash.Update(data, len);
647 }
648 
get(byte * out)649 void HashCRC32::get(byte* out)
650 {
651     hash.Final(out);
652 }
653 
HMACSHA256(const byte * key,size_t length)654 HMACSHA256::HMACSHA256(const byte *key, size_t length)
655     : hmac(key, length)
656 {
657 }
658 
add(const byte * data,size_t len)659 void HMACSHA256::add(const byte *data, size_t len)
660 {
661     hmac.Update(data, len);
662 }
663 
get(byte * out)664 void HMACSHA256::get(byte *out)
665 {
666     hmac.Final(out);
667 }
668 
PBKDF2_HMAC_SHA512()669 PBKDF2_HMAC_SHA512::PBKDF2_HMAC_SHA512()
670 {
671 }
672 
deriveKey(byte * derivedkey,size_t derivedkeyLen,byte * pwd,size_t pwdLen,byte * salt,size_t saltLen,unsigned int iterations)673 void PBKDF2_HMAC_SHA512::deriveKey(byte* derivedkey, size_t derivedkeyLen,
674                                    byte* pwd, size_t pwdLen,
675                                    byte* salt, size_t saltLen, unsigned int iterations)
676 {
677     pbkdf2.DeriveKey(
678             // buffer that holds the derived key
679             derivedkey, derivedkeyLen,
680             // purpose byte. unused by this PBKDF implementation.
681             0x00,
682             // password bytes. careful to be consistent with encoding...
683             pwd, pwdLen,
684             // salt bytes
685             salt, saltLen,
686             // iteration count. See SP 800-132 for details. You want this as large as you can tolerate.
687             // make sure to use the same iteration count on both sides...
688             iterations
689             );
690 }
691 
692 } // namespace
693