RecurrenceState.cpp 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549
  1. /*
  2. * Copyright 2005 - 2016 Zarafa and its licensors
  3. *
  4. * This program is free software: you can redistribute it and/or modify
  5. * it under the terms of the GNU Affero General Public License, version 3,
  6. * as published by the Free Software Foundation.
  7. *
  8. * This program is distributed in the hope that it will be useful,
  9. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. * GNU Affero General Public License for more details.
  12. *
  13. * You should have received a copy of the GNU Affero General Public License
  14. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  15. *
  16. */
  17. #include <kopano/zcdefs.h>
  18. #include <kopano/platform.h>
  19. #include <utility>
  20. #include <cstdio>
  21. #include <mapi.h>
  22. #include <mapix.h>
  23. #include <mapicode.h>
  24. #include <kopano/stringutil.h>
  25. #include <kopano/RecurrenceState.h>
  26. #include <kopano/charset/convert.h>
  27. #ifndef WIN32
  28. #define DEBUGREAD 0
  29. #if DEBUGREAD
  30. #include <arpa/inet.h> // nasty hack to display write bytes as read bytes using hton.()
  31. #define DEBUGPRINT(x, args...) fprintf(stderr, x, ##args)
  32. #else
  33. #define DEBUGPRINT(x, args...)
  34. #endif
  35. #else
  36. // Testing for both WIN32 && LINUX makes no f sense
  37. #define DEBUGPRINT(...)
  38. #endif
  39. namespace KC {
  40. class BinReader _kc_final {
  41. public:
  42. BinReader(const char *lpData, unsigned int ulLen) {
  43. this->m_lpData = lpData;
  44. this->m_ulLen = ulLen;
  45. this->m_ulCursor = 0;
  46. };
  47. int ReadByte(unsigned int *lpData) {
  48. if(m_ulCursor + 1 > m_ulLen)
  49. return -1;
  50. DEBUGPRINT("%s ", bin2hex(1, (BYTE *)m_lpData+m_ulCursor).c_str());
  51. *lpData = m_lpData[m_ulCursor];
  52. m_ulCursor+=1;
  53. DEBUGPRINT("%10u %08X ", *lpData, *lpData);
  54. return 1;
  55. };
  56. int ReadShort(unsigned int *lpData) {
  57. if(m_ulCursor + 2 > m_ulLen)
  58. return -1;
  59. DEBUGPRINT("%s ", bin2hex(2, (BYTE *)m_lpData+m_ulCursor).c_str());
  60. *lpData = *(unsigned short *)&m_lpData[m_ulCursor];
  61. m_ulCursor+=2;
  62. DEBUGPRINT("%10u %08X ", *lpData, *lpData);
  63. return 2;
  64. };
  65. int ReadLong(unsigned int *lpData) {
  66. if(m_ulCursor + 4 > m_ulLen)
  67. return -1;
  68. DEBUGPRINT("%s ", bin2hex(4, (BYTE *)m_lpData+m_ulCursor).c_str());
  69. *lpData = *(unsigned int *)&m_lpData[m_ulCursor];
  70. m_ulCursor+=4;
  71. DEBUGPRINT("%10u %08X ", *lpData, *lpData);
  72. return 4;
  73. };
  74. int ReadString(std::string *lpData, unsigned int len) {
  75. unsigned int reallen = len > m_ulLen - m_ulCursor ? m_ulLen - m_ulCursor : len;
  76. if(m_ulCursor + reallen > m_ulLen)
  77. return -1;
  78. if(reallen)
  79. DEBUGPRINT("%s ", bin2hex(len > m_ulLen - m_ulCursor ? m_ulLen - m_ulCursor : len, (BYTE *)m_lpData+m_ulCursor).c_str());
  80. lpData->assign(&m_lpData[m_ulCursor], reallen);
  81. lpData->substr(0, reallen);
  82. m_ulCursor+=reallen;
  83. if(reallen)
  84. DEBUGPRINT("\"%s\" ", lpData->c_str());
  85. return reallen == len ? reallen : -1;
  86. };
  87. int GetCursorPos(void) const { return m_ulCursor; }
  88. private:
  89. const char *m_lpData;
  90. unsigned int m_ulLen;
  91. unsigned int m_ulCursor;
  92. };
  93. class BinWriter _kc_final {
  94. public:
  95. void GetData(char **lppData, unsigned int *lpulLen, void *base) {
  96. char *lpData;
  97. HRESULT hr = hrSuccess;
  98. if (base)
  99. hr = MAPIAllocateMore(m_strData.size(), base, (void **)&lpData);
  100. else
  101. hr = MAPIAllocateBuffer(m_strData.size(), (void **)&lpData);
  102. if (hr == hrSuccess)
  103. memcpy(lpData, m_strData.c_str(), m_strData.size());
  104. *lppData = lpData;
  105. *lpulLen = m_strData.size();
  106. }
  107. int WriteByte(unsigned int b) {
  108. m_strData.append((char *)&b, 1);
  109. return 1;
  110. }
  111. int WriteShort(unsigned short s) {
  112. m_strData.append((char *)&s, 2);
  113. return 2;
  114. }
  115. int WriteLong(unsigned int l) {
  116. m_strData.append((char *)&l, 4);
  117. return 4;
  118. }
  119. int WriteString(const char *data, unsigned int len) {
  120. std::string s(data, len);
  121. m_strData += s;
  122. return len;
  123. }
  124. private:
  125. std::string m_strData;
  126. };
  127. #define READDATA(x, type) \
  128. do { \
  129. if (data.Read##type(&(x)) < 0) { \
  130. hr = MAPI_E_NOT_FOUND; \
  131. goto exit; \
  132. } \
  133. DEBUGPRINT("%s\n", #x); \
  134. } while (false)
  135. #define READSTRING(x, len) \
  136. do { \
  137. if (data.ReadString(&(x), len) < 0) { \
  138. hr = MAPI_E_NOT_FOUND; \
  139. goto exit; \
  140. } \
  141. if (len > 0) \
  142. DEBUGPRINT("%s\n", #x); \
  143. } while (false)
  144. #define READSHORT(x) READDATA(x, Short)
  145. #define READLONG(x) READDATA(x, Long)
  146. #define WRITESHORT(x) do { \
  147. DEBUGPRINT("%04X %10u %08X %s\n", htons(x), (x), (x), #x); \
  148. data.WriteShort(x); \
  149. } while (false)
  150. #define WRITELONG(x) do { \
  151. DEBUGPRINT("%08X %10u %08X %s\n", htonl(x), (x), (x), #x); \
  152. data.WriteLong(x); \
  153. } while (false)
  154. #define WRITESTRING(x, l) do { \
  155. DEBUGPRINT("%d\t%s\t%s\n", (l), (x), #x); \
  156. data.WriteString((x), (l)); \
  157. } while (false)
  158. /**
  159. * Reads exception data from outlook blob.
  160. *
  161. * If extended version is not available, it will be synced here. If it is available, it will overwrite the subject and location exceptions (if any) from extended to normal.
  162. *
  163. * @param[in] lpData blob data
  164. * @param[in] ulLen length of lpData
  165. * @param[in] ulFlags possible task flag
  166. */
  167. HRESULT RecurrenceState::ParseBlob(const char *lpData, unsigned int ulLen,
  168. ULONG ulFlags)
  169. {
  170. HRESULT hr = hrSuccess;
  171. unsigned int ulReservedBlock1Size;
  172. unsigned int ulReservedBlock2Size;
  173. bool bReadValid = false; // Read is valid if first set of exceptions was read ok
  174. bool bExtended = false; // false if we need to sync extended data from "normal" data
  175. convert_context converter;
  176. BinReader data(lpData, ulLen);
  177. unsigned int i;
  178. lstDeletedInstanceDates.clear();
  179. lstModifiedInstanceDates.clear();
  180. lstExceptions.clear();
  181. lstExtendedExceptions.clear();
  182. READSHORT(ulReaderVersion); READSHORT(ulWriterVersion);
  183. READSHORT(ulRecurFrequency); READSHORT(ulPatternType);
  184. READSHORT(ulCalendarType);
  185. READLONG(ulFirstDateTime);
  186. READLONG(ulPeriod);
  187. READLONG(ulSlidingFlag);
  188. if (ulPatternType == PT_DAY) {
  189. // No patterntype specific
  190. } else if (ulPatternType == PT_WEEK) {
  191. READLONG(ulWeekDays);
  192. } else if (ulPatternType == PT_MONTH || ulPatternType == PT_MONTH_END ||
  193. ulPatternType == PT_HJ_MONTH || ulPatternType == PT_HJ_MONTH_END) {
  194. READLONG(ulDayOfMonth);
  195. } else if (ulPatternType == PT_MONTH_NTH ||
  196. ulPatternType == PT_HJ_MONTH_NTH) {
  197. READLONG(ulWeekDays);
  198. READLONG(ulWeekNumber);
  199. }
  200. READLONG(ulEndType);
  201. READLONG(ulOccurrenceCount);
  202. READLONG(ulFirstDOW);
  203. READLONG(ulDeletedInstanceCount);
  204. for (i = 0; i < ulDeletedInstanceCount; ++i) {
  205. unsigned int ulDeletedInstanceDate;
  206. READLONG(ulDeletedInstanceDate);
  207. lstDeletedInstanceDates.push_back(ulDeletedInstanceDate);
  208. }
  209. READLONG(ulModifiedInstanceCount);
  210. for (i = 0; i < ulModifiedInstanceCount; ++i) {
  211. unsigned int ulModifiedInstanceDate;
  212. READLONG(ulModifiedInstanceDate);
  213. lstModifiedInstanceDates.push_back(ulModifiedInstanceDate);
  214. }
  215. READLONG(ulStartDate);
  216. READLONG(ulEndDate);
  217. if (ulFlags & RECURRENCE_STATE_TASKS)
  218. bReadValid = true; // Task recurrence
  219. READLONG(ulReaderVersion2);
  220. READLONG(ulWriterVersion2);
  221. READLONG(ulStartTimeOffset);
  222. READLONG(ulEndTimeOffset);
  223. READSHORT(ulExceptionCount);
  224. for (i = 0; i < ulExceptionCount; ++i) {
  225. unsigned int ulSubjectLength;
  226. unsigned int ulSubjectLength2;
  227. unsigned int ulLocationLength;
  228. unsigned int ulLocationLength2;
  229. Exception sException;
  230. READLONG(sException.ulStartDateTime);
  231. READLONG(sException.ulEndDateTime);
  232. READLONG(sException.ulOriginalStartDate);
  233. READSHORT(sException.ulOverrideFlags);
  234. if(sException.ulOverrideFlags & ARO_SUBJECT) {
  235. READSHORT(ulSubjectLength);
  236. READSHORT(ulSubjectLength2);
  237. READSTRING(sException.strSubject, ulSubjectLength2);
  238. }
  239. if (sException.ulOverrideFlags & ARO_MEETINGTYPE)
  240. READLONG(sException.ulApptStateFlags);
  241. if (sException.ulOverrideFlags & ARO_REMINDERDELTA)
  242. READLONG(sException.ulReminderDelta);
  243. if (sException.ulOverrideFlags & ARO_REMINDERSET)
  244. READLONG(sException.ulReminderSet);
  245. if(sException.ulOverrideFlags & ARO_LOCATION) {
  246. READSHORT(ulLocationLength);
  247. READSHORT(ulLocationLength2);
  248. READSTRING(sException.strLocation, ulLocationLength2);
  249. }
  250. if (sException.ulOverrideFlags & ARO_BUSYSTATUS)
  251. READLONG(sException.ulBusyStatus);
  252. if (sException.ulOverrideFlags & ARO_ATTACHMENT)
  253. READLONG(sException.ulAttachment);
  254. if (sException.ulOverrideFlags & ARO_SUBTYPE)
  255. READLONG(sException.ulSubType);
  256. if (sException.ulOverrideFlags & ARO_APPTCOLOR)
  257. READLONG(sException.ulAppointmentColor);
  258. lstExceptions.push_back(std::move(sException));
  259. }
  260. bReadValid = true;
  261. READLONG(ulReservedBlock1Size);
  262. READSTRING(strReservedBlock1, ulReservedBlock1Size);
  263. for (auto &exc : lstExceptions) {
  264. ExtendedException sExtendedException;
  265. unsigned int ulReservedBlock1Size;
  266. unsigned int ulReservedBlock2Size;
  267. unsigned int ulWideCharSubjectLength;
  268. unsigned int ulWideCharLocationLength;
  269. unsigned int ulChangeHighlightSize;
  270. if(ulWriterVersion2 >= 0x00003009) {
  271. READLONG(ulChangeHighlightSize);
  272. READLONG(sExtendedException.ulChangeHighlightValue);
  273. READSTRING(sExtendedException.strReserved, ulChangeHighlightSize-4);
  274. }
  275. READLONG(ulReservedBlock1Size);
  276. READSTRING(sExtendedException.strReservedBlock1, ulReservedBlock1Size);
  277. // According to the docs, these are condition depending on the OverrideFlags field. But that's wrong.
  278. if (exc.ulOverrideFlags & ARO_SUBJECT ||
  279. exc.ulOverrideFlags & ARO_LOCATION) {
  280. READLONG(sExtendedException.ulStartDateTime);
  281. READLONG(sExtendedException.ulEndDateTime);
  282. READLONG(sExtendedException.ulOriginalStartDate);
  283. }
  284. if (exc.ulOverrideFlags & ARO_SUBJECT) {
  285. std::string strBytes;
  286. READSHORT(ulWideCharSubjectLength);
  287. READSTRING(strBytes, ulWideCharSubjectLength * sizeof(short));
  288. TryConvert(converter, strBytes, ulWideCharSubjectLength * sizeof(short), "UCS-2LE", sExtendedException.strWideCharSubject);
  289. }
  290. if (exc.ulOverrideFlags & ARO_LOCATION) {
  291. std::string strBytes;
  292. READSHORT(ulWideCharLocationLength);
  293. READSTRING(strBytes, ulWideCharLocationLength * sizeof(short));
  294. TryConvert(converter, strBytes, ulWideCharLocationLength * sizeof(short), "UCS-2LE", sExtendedException.strWideCharLocation);
  295. }
  296. if (exc.ulOverrideFlags & ARO_SUBJECT ||
  297. exc.ulOverrideFlags & ARO_LOCATION) {
  298. READLONG(ulReservedBlock2Size);
  299. READSTRING(sExtendedException.strReservedBlock2, ulReservedBlock2Size);
  300. }
  301. lstExtendedExceptions.push_back(std::move(sExtendedException));
  302. }
  303. bExtended = true;
  304. READLONG(ulReservedBlock2Size);
  305. READSTRING(strReservedBlock2, ulReservedBlock2Size);
  306. DEBUGPRINT("%d Bytes left\n", ulLen - data.GetCursorPos());
  307. if(ulLen - data.GetCursorPos() != 0) {
  308. hr = MAPI_E_NOT_FOUND;
  309. }
  310. exit:
  311. if (hr != hrSuccess && bReadValid) {
  312. hr = MAPI_W_ERRORS_RETURNED;
  313. // sync normal exceptions to extended exceptions, it those aren't present
  314. if (!bExtended) {
  315. lstExtendedExceptions.clear(); // remove any half exception maybe read
  316. for (ULONG i = 0; i < ulExceptionCount; ++i) {
  317. ExtendedException cEx;
  318. cEx.ulChangeHighlightValue = 0;
  319. cEx.ulStartDateTime = lstExceptions[i].ulStartDateTime;
  320. cEx.ulEndDateTime = lstExceptions[i].ulEndDateTime;
  321. cEx.ulOriginalStartDate = lstExceptions[i].ulOriginalStartDate;
  322. // subject & location in UCS2
  323. if (lstExceptions[i].ulOverrideFlags & ARO_SUBJECT)
  324. TryConvert(converter, lstExceptions[i].strSubject, rawsize(lstExceptions[i].strSubject), "windows-1252", cEx.strWideCharSubject);
  325. if (lstExceptions[i].ulOverrideFlags & ARO_LOCATION)
  326. TryConvert(converter, lstExceptions[i].strLocation, rawsize(lstExceptions[i].strLocation), "windows-1252", cEx.strWideCharLocation);
  327. lstExtendedExceptions.push_back(cEx);
  328. // clear for next exception
  329. cEx.strWideCharSubject.clear();
  330. cEx.strWideCharLocation.clear();
  331. }
  332. }
  333. }
  334. return hr;
  335. }
  336. /**
  337. * Write exception data.
  338. *
  339. * All exception data, including extended data, must be available in this class.
  340. *
  341. * @param[out] lppData output blob
  342. * @param[out] lpulLen lenght of lppData
  343. * @parampin] base base pointer for allocation, may be NULL to start new chainn of MAPIAllocateBuffer
  344. */
  345. HRESULT RecurrenceState::GetBlob(char **lppData, unsigned int *lpulLen, void *base)
  346. {
  347. BinWriter data;
  348. std::vector<Exception>::const_iterator j = lstExceptions.begin();
  349. // There is one hard requirement: there must be as many Exceptions as there are ExtendedExceptions. Other
  350. // inconstencies are also bad, but we need at least that to even write the stream
  351. if (lstExceptions.size() != lstExtendedExceptions.size())
  352. return MAPI_E_CORRUPT_DATA;
  353. WRITESHORT(ulReaderVersion); WRITESHORT(ulWriterVersion);
  354. WRITESHORT(ulRecurFrequency); WRITESHORT(ulPatternType);
  355. WRITESHORT(ulCalendarType);
  356. WRITELONG(ulFirstDateTime);
  357. WRITELONG(ulPeriod);
  358. WRITELONG(ulSlidingFlag);
  359. if (ulPatternType == PT_DAY) {
  360. // No data
  361. } else if (ulPatternType == PT_WEEK) {
  362. WRITELONG(ulWeekDays);
  363. } else if (ulPatternType == PT_MONTH || ulPatternType == PT_MONTH_END ||
  364. ulPatternType == PT_HJ_MONTH || ulPatternType == PT_HJ_MONTH_END) {
  365. WRITELONG(ulDayOfMonth);
  366. } else if (ulPatternType == PT_MONTH_NTH ||
  367. ulPatternType == PT_HJ_MONTH_NTH) {
  368. WRITELONG(ulWeekDays);
  369. WRITELONG(ulWeekNumber);
  370. }
  371. WRITELONG(ulEndType);
  372. WRITELONG(ulOccurrenceCount);
  373. WRITELONG(ulFirstDOW);
  374. WRITELONG(ulDeletedInstanceCount);
  375. for (const auto i : lstDeletedInstanceDates)
  376. WRITELONG(i);
  377. WRITELONG(ulModifiedInstanceCount);
  378. for (const auto i : lstModifiedInstanceDates)
  379. WRITELONG(i);
  380. WRITELONG(ulStartDate);
  381. WRITELONG(ulEndDate);
  382. WRITELONG(ulReaderVersion2);
  383. WRITELONG(ulWriterVersion2);
  384. WRITELONG(ulStartTimeOffset);
  385. WRITELONG(ulEndTimeOffset);
  386. WRITESHORT(ulExceptionCount);
  387. for (const auto &i : lstExceptions) {
  388. WRITELONG(i.ulStartDateTime);
  389. WRITELONG(i.ulEndDateTime);
  390. WRITELONG(i.ulOriginalStartDate);
  391. WRITESHORT(i.ulOverrideFlags);
  392. if (i.ulOverrideFlags & ARO_SUBJECT) {
  393. WRITESHORT(static_cast<ULONG>(i.strSubject.size() + 1));
  394. WRITESHORT(static_cast<ULONG>(i.strSubject.size()));
  395. WRITESTRING(i.strSubject.c_str(), static_cast<ULONG>(i.strSubject.size()));
  396. }
  397. if (i.ulOverrideFlags & ARO_MEETINGTYPE)
  398. WRITELONG(i.ulApptStateFlags);
  399. if (i.ulOverrideFlags & ARO_REMINDERDELTA)
  400. WRITELONG(i.ulReminderDelta);
  401. if (i.ulOverrideFlags & ARO_REMINDERSET)
  402. WRITELONG(i.ulReminderSet);
  403. if (i.ulOverrideFlags & ARO_LOCATION) {
  404. WRITESHORT(static_cast<ULONG>(i.strLocation.size()) + 1);
  405. WRITESHORT(static_cast<ULONG>(i.strLocation.size()));
  406. WRITESTRING(i.strLocation.c_str(), static_cast<ULONG>(i.strLocation.size()));
  407. }
  408. if (i.ulOverrideFlags & ARO_BUSYSTATUS)
  409. WRITELONG(i.ulBusyStatus);
  410. if (i.ulOverrideFlags & ARO_ATTACHMENT)
  411. WRITELONG(i.ulAttachment);
  412. if (i.ulOverrideFlags & ARO_SUBTYPE)
  413. WRITELONG(i.ulSubType);
  414. if (i.ulOverrideFlags & ARO_APPTCOLOR)
  415. WRITELONG(i.ulAppointmentColor);
  416. }
  417. WRITELONG((ULONG)strReservedBlock1.size());
  418. WRITESTRING(strReservedBlock1.c_str(), (ULONG)strReservedBlock1.size());
  419. for (const auto &i : lstExtendedExceptions) {
  420. if (ulWriterVersion2 >= 0x00003009) {
  421. WRITELONG(static_cast<ULONG>(i.strReserved.size() + 4));
  422. WRITELONG(i.ulChangeHighlightValue);
  423. WRITESTRING(i.strReserved.c_str(), static_cast<ULONG>(i.strReserved.size()));
  424. }
  425. WRITELONG(static_cast<ULONG>(i.strReservedBlock1.size()));
  426. WRITESTRING(i.strReservedBlock1.c_str(), static_cast<ULONG>(i.strReservedBlock1.size()));
  427. if ((j->ulOverrideFlags & ARO_SUBJECT) || (j->ulOverrideFlags & ARO_LOCATION)) {
  428. WRITELONG(i.ulStartDateTime);
  429. WRITELONG(i.ulEndDateTime);
  430. WRITELONG(i.ulOriginalStartDate);
  431. }
  432. if (j->ulOverrideFlags & ARO_SUBJECT) {
  433. auto strWide = convert_to<std::u16string>(i.strWideCharSubject);
  434. WRITESHORT(static_cast<ULONG>(strWide.size()));
  435. WRITESTRING(reinterpret_cast<const char *>(strWide.c_str()), static_cast<ULONG>(strWide.size()) * 2);
  436. }
  437. if (j->ulOverrideFlags & ARO_LOCATION) {
  438. auto strWide = convert_to<std::u16string>(i.strWideCharLocation);
  439. WRITESHORT(static_cast<ULONG>(strWide.size()));
  440. WRITESTRING(reinterpret_cast<const char *>(strWide.c_str()), static_cast<ULONG>(strWide.size()) * 2);
  441. }
  442. if ((j->ulOverrideFlags & ARO_SUBJECT) || (j->ulOverrideFlags & ARO_LOCATION)) {
  443. WRITELONG(static_cast<ULONG>(i.strReservedBlock2.size()));
  444. WRITESTRING(i.strReservedBlock2.c_str(), static_cast<ULONG>(i.strReservedBlock2.size()));
  445. }
  446. ++j;
  447. }
  448. WRITELONG((ULONG)strReservedBlock2.size());
  449. WRITESTRING(strReservedBlock2.c_str(), (ULONG)strReservedBlock2.size());
  450. data.GetData(lppData, lpulLen, base);
  451. return hrSuccess;
  452. }
  453. } /* namespace */