@rapidmx/mapi-plugin 1.0.0-beta.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (180) hide show
  1. package/LICENSE +382 -0
  2. package/README.md +60 -0
  3. package/dist/lib/BaseMapiEmsmdbRoute.js +297 -0
  4. package/dist/lib/BaseMapiEmsmdbRoute.js.map +1 -0
  5. package/dist/lib/BaseMapiNspiRoute.js +114 -0
  6. package/dist/lib/BaseMapiNspiRoute.js.map +1 -0
  7. package/dist/lib/MapiSessionManager.js +119 -0
  8. package/dist/lib/MapiSessionManager.js.map +1 -0
  9. package/dist/lib/RopDispatcher.js +34 -0
  10. package/dist/lib/RopDispatcher.js.map +1 -0
  11. package/dist/lib/codec/AppointmentRecurrence.js +314 -0
  12. package/dist/lib/codec/AppointmentRecurrence.js.map +1 -0
  13. package/dist/lib/codec/BufferCursor.js +213 -0
  14. package/dist/lib/codec/BufferCursor.js.map +1 -0
  15. package/dist/lib/codec/GlobalObjectId.js +92 -0
  16. package/dist/lib/codec/GlobalObjectId.js.map +1 -0
  17. package/dist/lib/codec/MapiGuid.js +41 -0
  18. package/dist/lib/codec/MapiGuid.js.map +1 -0
  19. package/dist/lib/codec/MapiTimeZone.js +93 -0
  20. package/dist/lib/codec/MapiTimeZone.js.map +1 -0
  21. package/dist/lib/codec/PropertyValue.js +214 -0
  22. package/dist/lib/codec/PropertyValue.js.map +1 -0
  23. package/dist/lib/codec/RopBuffer.js +26 -0
  24. package/dist/lib/codec/RopBuffer.js.map +1 -0
  25. package/dist/lib/codec/TypedString.js +54 -0
  26. package/dist/lib/codec/TypedString.js.map +1 -0
  27. package/dist/lib/index.js +99 -0
  28. package/dist/lib/index.js.map +1 -0
  29. package/dist/lib/mongo/MapiEmsmdbRouteMongo.js +84 -0
  30. package/dist/lib/mongo/MapiEmsmdbRouteMongo.js.map +1 -0
  31. package/dist/lib/mongo/MapiNspiRouteMongo.js +33 -0
  32. package/dist/lib/mongo/MapiNspiRouteMongo.js.map +1 -0
  33. package/dist/lib/mongo/index.js +7 -0
  34. package/dist/lib/mongo/index.js.map +1 -0
  35. package/dist/lib/mongo.js +6 -0
  36. package/dist/lib/mongo.js.map +1 -0
  37. package/dist/lib/nspi/NspiBindHandler.js +54 -0
  38. package/dist/lib/nspi/NspiBindHandler.js.map +1 -0
  39. package/dist/lib/nspi/NspiCodec.js +106 -0
  40. package/dist/lib/nspi/NspiCodec.js.map +1 -0
  41. package/dist/lib/nspi/NspiGetMatchesHandler.js +142 -0
  42. package/dist/lib/nspi/NspiGetMatchesHandler.js.map +1 -0
  43. package/dist/lib/rop/CalendarEventTarget.js +27 -0
  44. package/dist/lib/rop/CalendarEventTarget.js.map +1 -0
  45. package/dist/lib/rop/CalendarNamedProperties.js +57 -0
  46. package/dist/lib/rop/CalendarNamedProperties.js.map +1 -0
  47. package/dist/lib/rop/ContactTarget.js +23 -0
  48. package/dist/lib/rop/ContactTarget.js.map +1 -0
  49. package/dist/lib/rop/FastTransferStream.js +98 -0
  50. package/dist/lib/rop/FastTransferStream.js.map +1 -0
  51. package/dist/lib/rop/FolderTarget.js +111 -0
  52. package/dist/lib/rop/FolderTarget.js.map +1 -0
  53. package/dist/lib/rop/MeetingMessageClassHandler.js +95 -0
  54. package/dist/lib/rop/MeetingMessageClassHandler.js.map +1 -0
  55. package/dist/lib/rop/MessageBodyStream.js +42 -0
  56. package/dist/lib/rop/MessageBodyStream.js.map +1 -0
  57. package/dist/lib/rop/MessageTarget.js +44 -0
  58. package/dist/lib/rop/MessageTarget.js.map +1 -0
  59. package/dist/lib/rop/NamedPropertyRegistry.js +66 -0
  60. package/dist/lib/rop/NamedPropertyRegistry.js.map +1 -0
  61. package/dist/lib/rop/PropertyResolvers.js +313 -0
  62. package/dist/lib/rop/PropertyResolvers.js.map +1 -0
  63. package/dist/lib/rop/RopCreateMessageHandler.js +53 -0
  64. package/dist/lib/rop/RopCreateMessageHandler.js.map +1 -0
  65. package/dist/lib/rop/RopDeleteFolderHandler.js +133 -0
  66. package/dist/lib/rop/RopDeleteFolderHandler.js.map +1 -0
  67. package/dist/lib/rop/RopDeleteMessagesHandler.js +70 -0
  68. package/dist/lib/rop/RopDeleteMessagesHandler.js.map +1 -0
  69. package/dist/lib/rop/RopFastTransferSourceCopyPropertiesHandler.js +61 -0
  70. package/dist/lib/rop/RopFastTransferSourceCopyPropertiesHandler.js.map +1 -0
  71. package/dist/lib/rop/RopFastTransferSourceCopyToHandler.js +61 -0
  72. package/dist/lib/rop/RopFastTransferSourceCopyToHandler.js.map +1 -0
  73. package/dist/lib/rop/RopFastTransferSourceGetBufferHandler.js +75 -0
  74. package/dist/lib/rop/RopFastTransferSourceGetBufferHandler.js.map +1 -0
  75. package/dist/lib/rop/RopGetContentsTableHandler.js +76 -0
  76. package/dist/lib/rop/RopGetContentsTableHandler.js.map +1 -0
  77. package/dist/lib/rop/RopGetHierarchyTableHandler.js +40 -0
  78. package/dist/lib/rop/RopGetHierarchyTableHandler.js.map +1 -0
  79. package/dist/lib/rop/RopGetPropertiesSpecificHandler.js +55 -0
  80. package/dist/lib/rop/RopGetPropertiesSpecificHandler.js.map +1 -0
  81. package/dist/lib/rop/RopGetPropertyIdsFromNamesHandler.js +92 -0
  82. package/dist/lib/rop/RopGetPropertyIdsFromNamesHandler.js.map +1 -0
  83. package/dist/lib/rop/RopHandler.js +2 -0
  84. package/dist/lib/rop/RopHandler.js.map +1 -0
  85. package/dist/lib/rop/RopLogonHandler.js +132 -0
  86. package/dist/lib/rop/RopLogonHandler.js.map +1 -0
  87. package/dist/lib/rop/RopOpenFolderHandler.js +46 -0
  88. package/dist/lib/rop/RopOpenFolderHandler.js.map +1 -0
  89. package/dist/lib/rop/RopOpenMessageHandler.js +81 -0
  90. package/dist/lib/rop/RopOpenMessageHandler.js.map +1 -0
  91. package/dist/lib/rop/RopOpenStreamHandler.js +81 -0
  92. package/dist/lib/rop/RopOpenStreamHandler.js.map +1 -0
  93. package/dist/lib/rop/RopQueryRowsHandler.js +91 -0
  94. package/dist/lib/rop/RopQueryRowsHandler.js.map +1 -0
  95. package/dist/lib/rop/RopReadStreamHandler.js +55 -0
  96. package/dist/lib/rop/RopReadStreamHandler.js.map +1 -0
  97. package/dist/lib/rop/RopReleaseHandler.js +20 -0
  98. package/dist/lib/rop/RopReleaseHandler.js.map +1 -0
  99. package/dist/lib/rop/RopSaveChangesMessageHandler.js +210 -0
  100. package/dist/lib/rop/RopSaveChangesMessageHandler.js.map +1 -0
  101. package/dist/lib/rop/RopSetColumnsHandler.js +52 -0
  102. package/dist/lib/rop/RopSetColumnsHandler.js.map +1 -0
  103. package/dist/lib/rop/RopSetPropertiesHandler.js +162 -0
  104. package/dist/lib/rop/RopSetPropertiesHandler.js.map +1 -0
  105. package/dist/lib/rop/RopSubmitMessageHandler.js +314 -0
  106. package/dist/lib/rop/RopSubmitMessageHandler.js.map +1 -0
  107. package/dist/lib/rop/RopWriteStreamHandler.js +45 -0
  108. package/dist/lib/rop/RopWriteStreamHandler.js.map +1 -0
  109. package/dist/lib/rop/TaskNamedProperties.js +29 -0
  110. package/dist/lib/rop/TaskNamedProperties.js.map +1 -0
  111. package/dist/lib/rop/TaskTarget.js +19 -0
  112. package/dist/lib/rop/TaskTarget.js.map +1 -0
  113. package/dist/lib/sql/MapiEmsmdbRouteSQL.js +84 -0
  114. package/dist/lib/sql/MapiEmsmdbRouteSQL.js.map +1 -0
  115. package/dist/lib/sql/MapiNspiRouteSQL.js +33 -0
  116. package/dist/lib/sql/MapiNspiRouteSQL.js.map +1 -0
  117. package/dist/lib/sql/index.js +7 -0
  118. package/dist/lib/sql/index.js.map +1 -0
  119. package/dist/lib/sql.js +6 -0
  120. package/dist/lib/sql.js.map +1 -0
  121. package/dist/types/BaseMapiEmsmdbRoute.d.ts +82 -0
  122. package/dist/types/BaseMapiNspiRoute.d.ts +38 -0
  123. package/dist/types/MapiSessionManager.d.ts +130 -0
  124. package/dist/types/RopDispatcher.d.ts +17 -0
  125. package/dist/types/codec/AppointmentRecurrence.d.ts +14 -0
  126. package/dist/types/codec/BufferCursor.d.ts +78 -0
  127. package/dist/types/codec/GlobalObjectId.d.ts +11 -0
  128. package/dist/types/codec/MapiGuid.d.ts +6 -0
  129. package/dist/types/codec/MapiTimeZone.d.ts +7 -0
  130. package/dist/types/codec/PropertyValue.d.ts +77 -0
  131. package/dist/types/codec/RopBuffer.d.ts +25 -0
  132. package/dist/types/codec/TypedString.d.ts +17 -0
  133. package/dist/types/index.d.ts +94 -0
  134. package/dist/types/mongo/MapiEmsmdbRouteMongo.d.ts +19 -0
  135. package/dist/types/mongo/MapiNspiRouteMongo.d.ts +13 -0
  136. package/dist/types/mongo/index.d.ts +2 -0
  137. package/dist/types/mongo.d.ts +1 -0
  138. package/dist/types/nspi/NspiBindHandler.d.ts +22 -0
  139. package/dist/types/nspi/NspiCodec.d.ts +59 -0
  140. package/dist/types/nspi/NspiGetMatchesHandler.d.ts +24 -0
  141. package/dist/types/rop/CalendarEventTarget.d.ts +29 -0
  142. package/dist/types/rop/CalendarNamedProperties.d.ts +37 -0
  143. package/dist/types/rop/ContactTarget.d.ts +29 -0
  144. package/dist/types/rop/FastTransferStream.d.ts +18 -0
  145. package/dist/types/rop/FolderTarget.d.ts +63 -0
  146. package/dist/types/rop/MeetingMessageClassHandler.d.ts +36 -0
  147. package/dist/types/rop/MessageBodyStream.d.ts +26 -0
  148. package/dist/types/rop/MessageTarget.d.ts +38 -0
  149. package/dist/types/rop/NamedPropertyRegistry.d.ts +46 -0
  150. package/dist/types/rop/PropertyResolvers.d.ts +60 -0
  151. package/dist/types/rop/RopCreateMessageHandler.d.ts +23 -0
  152. package/dist/types/rop/RopDeleteFolderHandler.d.ts +38 -0
  153. package/dist/types/rop/RopDeleteMessagesHandler.d.ts +31 -0
  154. package/dist/types/rop/RopFastTransferSourceCopyPropertiesHandler.d.ts +24 -0
  155. package/dist/types/rop/RopFastTransferSourceCopyToHandler.d.ts +24 -0
  156. package/dist/types/rop/RopFastTransferSourceGetBufferHandler.d.ts +23 -0
  157. package/dist/types/rop/RopGetContentsTableHandler.d.ts +31 -0
  158. package/dist/types/rop/RopGetHierarchyTableHandler.d.ts +17 -0
  159. package/dist/types/rop/RopGetPropertiesSpecificHandler.d.ts +23 -0
  160. package/dist/types/rop/RopGetPropertyIdsFromNamesHandler.d.ts +23 -0
  161. package/dist/types/rop/RopHandler.d.ts +67 -0
  162. package/dist/types/rop/RopLogonHandler.d.ts +31 -0
  163. package/dist/types/rop/RopOpenFolderHandler.d.ts +19 -0
  164. package/dist/types/rop/RopOpenMessageHandler.d.ts +27 -0
  165. package/dist/types/rop/RopOpenStreamHandler.d.ts +27 -0
  166. package/dist/types/rop/RopQueryRowsHandler.d.ts +36 -0
  167. package/dist/types/rop/RopReadStreamHandler.d.ts +18 -0
  168. package/dist/types/rop/RopReleaseHandler.d.ts +14 -0
  169. package/dist/types/rop/RopSaveChangesMessageHandler.d.ts +44 -0
  170. package/dist/types/rop/RopSetColumnsHandler.d.ts +21 -0
  171. package/dist/types/rop/RopSetPropertiesHandler.d.ts +38 -0
  172. package/dist/types/rop/RopSubmitMessageHandler.d.ts +70 -0
  173. package/dist/types/rop/RopWriteStreamHandler.d.ts +21 -0
  174. package/dist/types/rop/TaskNamedProperties.d.ts +24 -0
  175. package/dist/types/rop/TaskTarget.d.ts +24 -0
  176. package/dist/types/sql/MapiEmsmdbRouteSQL.d.ts +19 -0
  177. package/dist/types/sql/MapiNspiRouteSQL.d.ts +13 -0
  178. package/dist/types/sql/index.d.ts +2 -0
  179. package/dist/types/sql.d.ts +1 -0
  180. package/package.json +104 -0
@@ -0,0 +1,314 @@
1
+ ///////////////////////////////////////////////////////////////////////////////
2
+ // Copyright (C) 2026 Jean-Philippe Steinmetz. All rights reserved.
3
+ // SPDX-License-Identifier: MPL-2.0
4
+ ///////////////////////////////////////////////////////////////////////////////
5
+ import { BufferWriter } from "./BufferCursor.js";
6
+ import { RecurrenceFrequency } from "@rapidmx/restapi";
7
+ /**
8
+ * Encodes/decodes `CalendarEvent.recurrenceRule` to/from the binary blob carried by `PidLidAppointmentRecur`
9
+ * (`[MS-OXOCAL]` §2.2.1.44: the outer `AppointmentRecurrencePattern` structure, §2.2.1.44.5, wrapping an inner
10
+ * `RecurrencePattern`, §2.2.1.44.1). Every field layout/size below was confirmed against those spec pages
11
+ * directly, not assumed.
12
+ *
13
+ * **Pragmatic scope, matching the same limits `CalendarSyncAdapter` (EAS) already carries**:
14
+ * - Only `RecurFrequency` Daily/Weekly/Monthly/Yearly and `PatternType` Day/Week/Month are produced or
15
+ * accepted - `MonthNth`/`MonthEnd`/Hijri patterns have no representation in `RecurrenceRule` (no ordinal-
16
+ * weekday field) and decoding one throws a clear, documented error rather than silently guessing.
17
+ * - `DeletedInstanceCount`/`ModifiedInstanceCount` (recurrence exceptions) and the outer structure's
18
+ * `ExceptionCount` are always written as `0`; decoding a nonzero value throws, since `RecurrenceRule` has no
19
+ * field to hold per-instance exceptions.
20
+ * - A `YEARLY` rule with `interval > 1` ("every N years", N>1) is encoded as `RecurFrequency=Monthly` with
21
+ * `Period=12*N`, per the spec's own stated rule that "a yearly recurrence pattern is just a monthly pattern
22
+ * that occurs every 12 months" (a plain `RecurFrequency=Yearly` value's `Period` MUST be exactly 12).
23
+ * **This is a one-way, documented round-trip asymmetry**: decoding that same blob back yields
24
+ * `{freq: MONTHLY, interval: 12*N}`, not the original `{freq: YEARLY, interval: N}` - the two are
25
+ * indistinguishable on the wire once encoded, the same ambiguity a real Exchange server itself has no way
26
+ * around either.
27
+ * - `StartTimeOffset`/`EndTimeOffset` (the outer structure) are derived from `startDate`/`endDate`'s own
28
+ * time-of-day only (`hours*60+minutes`), not carried through decode - the real start/end instant of any
29
+ * given occurrence comes from `PidLidAppointmentStartWhole`/`EndWhole`, not from this blob.
30
+ *
31
+ * @author Jean-Philippe Steinmetz
32
+ */
33
+ /** `RecurFrequency` (`[MS-OXOCAL]` §2.2.1.44.1). */
34
+ var RecurFrequencyWire;
35
+ (function (RecurFrequencyWire) {
36
+ RecurFrequencyWire[RecurFrequencyWire["Daily"] = 8202] = "Daily";
37
+ RecurFrequencyWire[RecurFrequencyWire["Weekly"] = 8203] = "Weekly";
38
+ RecurFrequencyWire[RecurFrequencyWire["Monthly"] = 8204] = "Monthly";
39
+ RecurFrequencyWire[RecurFrequencyWire["Yearly"] = 8205] = "Yearly";
40
+ })(RecurFrequencyWire || (RecurFrequencyWire = {}));
41
+ /** `PatternType` (`[MS-OXOCAL]` §2.2.1.44.1) - only the 3 values this codec produces/accepts. */
42
+ var PatternTypeWire;
43
+ (function (PatternTypeWire) {
44
+ PatternTypeWire[PatternTypeWire["Day"] = 0] = "Day";
45
+ PatternTypeWire[PatternTypeWire["Week"] = 1] = "Week";
46
+ PatternTypeWire[PatternTypeWire["Month"] = 2] = "Month";
47
+ })(PatternTypeWire || (PatternTypeWire = {}));
48
+ /** `EndType` (`[MS-OXOCAL]` §2.2.1.44.1). */
49
+ var EndTypeWire;
50
+ (function (EndTypeWire) {
51
+ EndTypeWire[EndTypeWire["AfterDate"] = 8225] = "AfterDate";
52
+ EndTypeWire[EndTypeWire["AfterN"] = 8226] = "AfterN";
53
+ EndTypeWire[EndTypeWire["Never"] = 8227] = "Never";
54
+ })(EndTypeWire || (EndTypeWire = {}));
55
+ /** Sentinel `EndDate` value (`[MS-OXOCAL]` §2.2.1.44.1) meaning "never-ending". */
56
+ const NEVER_END_DATE = 0x5ae980df;
57
+ const MINUTES_PER_DAY = 1440;
58
+ const MINUTES_PER_WEEK = 10080;
59
+ /** `RecurrencePattern.CalendarType` `Default` value (Gregorian) - the only calendar type this codec supports. */
60
+ const CALENDAR_TYPE_DEFAULT = 0x0000;
61
+ /** `RecurrencePattern.FirstDOW` `Sunday` value - this codec always encodes with a Sunday week start, since
62
+ * `RecurrenceRule` has no field of its own to source a different one from. */
63
+ const FIRST_DOW_SUNDAY = 0x00000000;
64
+ /** `RecurrencePattern`'s `DayOfWeekMask` bits (`[MS-OXOCAL]` §2.2.1.44.1) - confirmed identical to
65
+ * `CalendarSyncAdapter`'s own `DAY_OF_WEEK_BITS` table for MS-ASCAL's `DayOfWeek` field, reused here under the
66
+ * same RFC 5545 two-letter day codes `RecurrenceRule.byDay` uses. */
67
+ const DAY_OF_WEEK_BITS = { SU: 1, MO: 2, TU: 4, WE: 8, TH: 16, FR: 32, SA: 64 };
68
+ const DAY_OF_WEEK_CODE_BY_BIT = [
69
+ ["SU", 1],
70
+ ["MO", 2],
71
+ ["TU", 4],
72
+ ["WE", 8],
73
+ ["TH", 16],
74
+ ["FR", 32],
75
+ ["SA", 64],
76
+ ];
77
+ /** Minutes-since-1601-01-01T00:00:00Z epoch, as a JS `Date`-compatible millisecond timestamp - every date field
78
+ * in `RecurrencePattern` uses this epoch/unit, distinct from `PropertyValue.ts`'s `FILETIME` epoch (which is
79
+ * also 1601-based, but in 100-ns units, not minutes). */
80
+ const EPOCH_1601_MS = Date.UTC(1601, 0, 1);
81
+ function dateToMinutesSince1601(date) {
82
+ return Math.round((date.getTime() - EPOCH_1601_MS) / 60000);
83
+ }
84
+ function minutesSince1601ToDate(minutes) {
85
+ return new Date(EPOCH_1601_MS + minutes * 60000);
86
+ }
87
+ function utcMidnight(date) {
88
+ return new Date(Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate()));
89
+ }
90
+ /** Calendar months elapsed between 1601-01-01 and the first day of the month containing `date`. */
91
+ function monthsElapsedSince1601(date) {
92
+ return (date.getUTCFullYear() - 1601) * 12 + date.getUTCMonth();
93
+ }
94
+ function dayOfWeekMaskFromByDay(byDay, fallbackDate) {
95
+ if (byDay && byDay.length > 0) {
96
+ return byDay.reduce((mask, day) => mask | (DAY_OF_WEEK_BITS[day] ?? 0), 0);
97
+ }
98
+ return DAY_OF_WEEK_CODE_BY_BIT[fallbackDate.getUTCDay()][1];
99
+ }
100
+ function byDayFromMask(mask) {
101
+ return DAY_OF_WEEK_CODE_BY_BIT.filter(([, bit]) => (mask & bit) !== 0).map(([code]) => code);
102
+ }
103
+ /**
104
+ * Encodes `rule` (plus the occurrence's own `startDate`/`endDate`, needed for phase/time-of-day derivation)
105
+ * into an `AppointmentRecurrencePattern` blob, ready to be written as the raw content of a `PtypBinary`
106
+ * `PidLidAppointmentRecur` property value.
107
+ */
108
+ export function encodeAppointmentRecurrence(rule, startDate, endDate) {
109
+ let recurFrequency;
110
+ let patternType;
111
+ let period;
112
+ let patternSpecific;
113
+ let firstDateTime;
114
+ switch (rule.freq) {
115
+ case RecurrenceFrequency.DAILY: {
116
+ recurFrequency = RecurFrequencyWire.Daily;
117
+ patternType = PatternTypeWire.Day;
118
+ period = rule.interval * MINUTES_PER_DAY;
119
+ patternSpecific = Buffer.alloc(0);
120
+ firstDateTime = dateToMinutesSince1601(utcMidnight(startDate)) % period;
121
+ break;
122
+ }
123
+ case RecurrenceFrequency.WEEKLY: {
124
+ recurFrequency = RecurFrequencyWire.Weekly;
125
+ patternType = PatternTypeWire.Week;
126
+ period = rule.interval;
127
+ const mask = dayOfWeekMaskFromByDay(rule.byDay, startDate);
128
+ patternSpecific = new BufferWriter().writeUInt32LE(mask).toBuffer();
129
+ const midnight = utcMidnight(startDate);
130
+ const weekStart = new Date(midnight.getTime() - midnight.getUTCDay() * MINUTES_PER_DAY * 60000);
131
+ firstDateTime = dateToMinutesSince1601(weekStart) % (period * MINUTES_PER_WEEK);
132
+ break;
133
+ }
134
+ case RecurrenceFrequency.MONTHLY: {
135
+ recurFrequency = RecurFrequencyWire.Monthly;
136
+ patternType = PatternTypeWire.Month;
137
+ period = rule.interval;
138
+ const day = rule.byMonthDay?.[0] ?? startDate.getUTCDate();
139
+ patternSpecific = new BufferWriter().writeUInt32LE(day).toBuffer();
140
+ const monthsElapsed = monthsElapsedSince1601(startDate);
141
+ const refMonthIndex = monthsElapsed % period;
142
+ firstDateTime = dateToMinutesSince1601(new Date(Date.UTC(1601, refMonthIndex, 1)));
143
+ break;
144
+ }
145
+ case RecurrenceFrequency.YEARLY: {
146
+ patternType = PatternTypeWire.Month;
147
+ if (rule.interval <= 1) {
148
+ recurFrequency = RecurFrequencyWire.Yearly;
149
+ period = 12;
150
+ }
151
+ else {
152
+ recurFrequency = RecurFrequencyWire.Monthly;
153
+ period = 12 * rule.interval;
154
+ }
155
+ const day = rule.byMonthDay?.[0] ?? startDate.getUTCDate();
156
+ patternSpecific = new BufferWriter().writeUInt32LE(day).toBuffer();
157
+ const monthsElapsed = monthsElapsedSince1601(startDate);
158
+ const refMonthIndex = monthsElapsed % period;
159
+ firstDateTime = dateToMinutesSince1601(new Date(Date.UTC(1601, refMonthIndex, 1)));
160
+ break;
161
+ }
162
+ default:
163
+ throw new Error(`AppointmentRecurrence: unsupported RecurrenceFrequency ${rule.freq}`);
164
+ }
165
+ let endType;
166
+ let occurrenceCount = 0;
167
+ let endDateMinutes;
168
+ if (rule.until) {
169
+ endType = EndTypeWire.AfterDate;
170
+ endDateMinutes = dateToMinutesSince1601(utcMidnight(rule.until));
171
+ }
172
+ else if (rule.count != null) {
173
+ endType = EndTypeWire.AfterN;
174
+ occurrenceCount = rule.count;
175
+ endDateMinutes = NEVER_END_DATE;
176
+ }
177
+ else {
178
+ endType = EndTypeWire.Never;
179
+ endDateMinutes = NEVER_END_DATE;
180
+ }
181
+ const writer = new BufferWriter();
182
+ // RecurrencePattern (MS-OXOCAL §2.2.1.44.1)
183
+ writer.writeUInt16LE(0x3004); // ReaderVersion
184
+ writer.writeUInt16LE(0x3004); // WriterVersion
185
+ writer.writeUInt16LE(recurFrequency);
186
+ writer.writeUInt16LE(patternType);
187
+ writer.writeUInt16LE(CALENDAR_TYPE_DEFAULT);
188
+ writer.writeUInt32LE(firstDateTime);
189
+ writer.writeUInt32LE(period);
190
+ writer.writeUInt32LE(0); // SlidingFlag
191
+ writer.writeBytes(patternSpecific);
192
+ writer.writeUInt32LE(endType);
193
+ writer.writeUInt32LE(occurrenceCount);
194
+ writer.writeUInt32LE(FIRST_DOW_SUNDAY);
195
+ writer.writeUInt32LE(0); // DeletedInstanceCount
196
+ writer.writeUInt32LE(0); // ModifiedInstanceCount
197
+ writer.writeUInt32LE(dateToMinutesSince1601(utcMidnight(startDate))); // StartDate
198
+ writer.writeUInt32LE(endDateMinutes); // EndDate
199
+ // AppointmentRecurrencePattern's own wrapper fields (MS-OXOCAL §2.2.1.44.5)
200
+ writer.writeUInt32LE(0x00003006); // ReaderVersion2
201
+ writer.writeUInt32LE(0x00003009); // WriterVersion2
202
+ writer.writeUInt32LE(startDate.getUTCHours() * 60 + startDate.getUTCMinutes()); // StartTimeOffset
203
+ writer.writeUInt32LE(endDate.getUTCHours() * 60 + endDate.getUTCMinutes()); // EndTimeOffset
204
+ writer.writeUInt16LE(0); // ExceptionCount
205
+ writer.writeUInt32LE(0); // ReservedBlock1Size
206
+ writer.writeUInt32LE(0); // ReservedBlock2Size
207
+ return writer.toBuffer();
208
+ }
209
+ /**
210
+ * Decodes an `AppointmentRecurrencePattern` blob (read from `reader`'s current position) back into a
211
+ * `RecurrenceRule`. Throws a clear error for any pattern this pragmatic subset can't represent (see this
212
+ * file's own doc comment) rather than silently dropping data.
213
+ */
214
+ export function decodeAppointmentRecurrence(reader) {
215
+ reader.readUInt16LE(); // ReaderVersion
216
+ reader.readUInt16LE(); // WriterVersion
217
+ const recurFrequency = reader.readUInt16LE();
218
+ const patternType = reader.readUInt16LE();
219
+ reader.readUInt16LE(); // CalendarType
220
+ const firstDateTime = reader.readUInt32LE();
221
+ const period = reader.readUInt32LE();
222
+ reader.readUInt32LE(); // SlidingFlag
223
+ let freq;
224
+ let interval;
225
+ let byDay;
226
+ let byMonthDay;
227
+ let byMonth;
228
+ switch (recurFrequency) {
229
+ case RecurFrequencyWire.Daily:
230
+ if (patternType !== PatternTypeWire.Day) {
231
+ throw new Error(`AppointmentRecurrence: unsupported PatternType 0x${patternType.toString(16)} for Daily`);
232
+ }
233
+ freq = RecurrenceFrequency.DAILY;
234
+ interval = period / MINUTES_PER_DAY;
235
+ break;
236
+ case RecurFrequencyWire.Weekly: {
237
+ if (patternType !== PatternTypeWire.Week) {
238
+ throw new Error(`AppointmentRecurrence: unsupported PatternType 0x${patternType.toString(16)} for Weekly`);
239
+ }
240
+ const mask = reader.readUInt32LE();
241
+ freq = RecurrenceFrequency.WEEKLY;
242
+ interval = period;
243
+ byDay = byDayFromMask(mask);
244
+ break;
245
+ }
246
+ case RecurFrequencyWire.Monthly: {
247
+ if (patternType !== PatternTypeWire.Month) {
248
+ throw new Error(`AppointmentRecurrence: unsupported PatternType 0x${patternType.toString(16)} for Monthly`);
249
+ }
250
+ const day = reader.readUInt32LE();
251
+ freq = RecurrenceFrequency.MONTHLY;
252
+ interval = period;
253
+ byMonthDay = [day];
254
+ break;
255
+ }
256
+ case RecurFrequencyWire.Yearly: {
257
+ if (patternType !== PatternTypeWire.Month) {
258
+ throw new Error(`AppointmentRecurrence: unsupported PatternType 0x${patternType.toString(16)} for Yearly`);
259
+ }
260
+ const day = reader.readUInt32LE();
261
+ freq = RecurrenceFrequency.YEARLY;
262
+ interval = 1;
263
+ byMonthDay = [day];
264
+ byMonth = [minutesSince1601ToDate(firstDateTime).getUTCMonth() + 1];
265
+ break;
266
+ }
267
+ default:
268
+ throw new Error(`AppointmentRecurrence: unsupported RecurFrequency 0x${recurFrequency.toString(16)}`);
269
+ }
270
+ const endType = reader.readUInt32LE();
271
+ const occurrenceCount = reader.readUInt32LE();
272
+ reader.readUInt32LE(); // FirstDOW
273
+ const deletedInstanceCount = reader.readUInt32LE();
274
+ if (deletedInstanceCount > 0) {
275
+ throw new Error("AppointmentRecurrence: recurrence exceptions (DeletedInstanceCount > 0) are not supported");
276
+ }
277
+ const modifiedInstanceCount = reader.readUInt32LE();
278
+ if (modifiedInstanceCount > 0) {
279
+ throw new Error("AppointmentRecurrence: recurrence exceptions (ModifiedInstanceCount > 0) are not supported");
280
+ }
281
+ reader.readUInt32LE(); // StartDate
282
+ const endDateMinutes = reader.readUInt32LE();
283
+ let until;
284
+ let count;
285
+ if (endType === EndTypeWire.AfterDate) {
286
+ until = minutesSince1601ToDate(endDateMinutes);
287
+ }
288
+ else if (endType === EndTypeWire.AfterN) {
289
+ count = occurrenceCount;
290
+ }
291
+ reader.readUInt32LE(); // ReaderVersion2
292
+ reader.readUInt32LE(); // WriterVersion2
293
+ reader.readUInt32LE(); // StartTimeOffset
294
+ reader.readUInt32LE(); // EndTimeOffset
295
+ const exceptionCount = reader.readUInt16LE();
296
+ if (exceptionCount > 0) {
297
+ throw new Error("AppointmentRecurrence: exceptions (ExceptionCount > 0) are not supported");
298
+ }
299
+ const reservedBlock1Size = reader.readUInt32LE();
300
+ reader.readBytes(reservedBlock1Size);
301
+ const reservedBlock2Size = reader.readUInt32LE();
302
+ reader.readBytes(reservedBlock2Size);
303
+ return {
304
+ freq,
305
+ interval,
306
+ ...(byDay ? { byDay } : {}),
307
+ ...(byMonthDay ? { byMonthDay } : {}),
308
+ ...(byMonth ? { byMonth } : {}),
309
+ ...(until ? { until } : {}),
310
+ ...(count != null ? { count } : {}),
311
+ exceptions: [],
312
+ };
313
+ }
314
+ //# sourceMappingURL=AppointmentRecurrence.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AppointmentRecurrence.js","sourceRoot":"","sources":["../../../src/codec/AppointmentRecurrence.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,213 @@
1
+ ///////////////////////////////////////////////////////////////////////////////
2
+ // Copyright (C) 2026 Jean-Philippe Steinmetz. All rights reserved.
3
+ // SPDX-License-Identifier: MPL-2.0
4
+ ///////////////////////////////////////////////////////////////////////////////
5
+ /**
6
+ * A small positional cursor over a `Buffer`, shared by every MAPI codec in this package (`MapiGuid`,
7
+ * `PropertyValue`, `RopBuffer`, and every individual ROP handler's own request/response encoding). Unlike
8
+ * WBXML's self-describing tag stream, MAPI's wire structures are fixed/variable-length binary records with no
9
+ * generic "read until end tag" mechanism, so every codec needs the same little-endian primitive reads/writes
10
+ * with automatic offset advancement - reimplementing that per file would be pure repetition across the dozens
11
+ * of ROP handlers this phase's plan calls for, unlike EAS's WBXML codec, which only needed one such reader.
12
+ *
13
+ * Underlying `Buffer` methods already bounds-check and throw `RangeError` on overflow/truncation for every
14
+ * fixed-width primitive read - a malformed or truncated ROP buffer legitimately failing loudly is the correct
15
+ * behavior, not something to swallow here. `readBytes`/`readNullTerminatedUtf16LE`/`readNullTerminatedString8`
16
+ * are the three exceptions: `Buffer.subarray` silently clamps instead of throwing, and a null-terminated scan
17
+ * has no fixed width to bounds-check against `Buffer` methods at all, so those three enforce the same
18
+ * "malformed/truncated input throws" contract explicitly below instead of relying on it happening for free.
19
+ *
20
+ * @author Jean-Philippe Steinmetz
21
+ */
22
+ export class BufferReader {
23
+ constructor(buffer, offset = 0) {
24
+ this.buffer = buffer;
25
+ this.offset = offset;
26
+ }
27
+ get position() {
28
+ return this.offset;
29
+ }
30
+ get remaining() {
31
+ return this.buffer.length - this.offset;
32
+ }
33
+ hasMore() {
34
+ return this.offset < this.buffer.length;
35
+ }
36
+ readUInt8() {
37
+ const value = this.buffer.readUInt8(this.offset);
38
+ this.offset += 1;
39
+ return value;
40
+ }
41
+ readInt16LE() {
42
+ const value = this.buffer.readInt16LE(this.offset);
43
+ this.offset += 2;
44
+ return value;
45
+ }
46
+ readUInt16LE() {
47
+ const value = this.buffer.readUInt16LE(this.offset);
48
+ this.offset += 2;
49
+ return value;
50
+ }
51
+ readInt32LE() {
52
+ const value = this.buffer.readInt32LE(this.offset);
53
+ this.offset += 4;
54
+ return value;
55
+ }
56
+ readUInt32LE() {
57
+ const value = this.buffer.readUInt32LE(this.offset);
58
+ this.offset += 4;
59
+ return value;
60
+ }
61
+ readFloatLE() {
62
+ const value = this.buffer.readFloatLE(this.offset);
63
+ this.offset += 4;
64
+ return value;
65
+ }
66
+ readDoubleLE() {
67
+ const value = this.buffer.readDoubleLE(this.offset);
68
+ this.offset += 8;
69
+ return value;
70
+ }
71
+ readBigInt64LE() {
72
+ const value = this.buffer.readBigInt64LE(this.offset);
73
+ this.offset += 8;
74
+ return value;
75
+ }
76
+ readBigUInt64LE() {
77
+ const value = this.buffer.readBigUInt64LE(this.offset);
78
+ this.offset += 8;
79
+ return value;
80
+ }
81
+ /** Unlike every fixed-width `readXxxLE` above, `Buffer.prototype.subarray` never throws - an out-of-range
82
+ * `length` (negative, or reaching past the buffer's end) silently clamps instead, which would otherwise let
83
+ * a malformed/truncated ROP buffer produce garbage-but-not-erroring reads (a negative `length` even rewinds
84
+ * `offset` backwards) instead of failing loudly the way this class's own doc comment promises. Explicitly
85
+ * bounds-checked here for that reason. */
86
+ readBytes(length) {
87
+ if (length < 0 || this.offset + length > this.buffer.length) {
88
+ throw new RangeError(`BufferReader.readBytes(${length}): out of range at offset ${this.offset} (buffer length ${this.buffer.length}).`);
89
+ }
90
+ const value = this.buffer.subarray(this.offset, this.offset + length);
91
+ this.offset += length;
92
+ return value;
93
+ }
94
+ /** Reads a UTF-16LE string up to (and consuming) its terminating `0x0000` code unit - `PtypString`'s wire
95
+ * encoding. Scans on 2-byte boundaries, since a null code unit's low/high byte pair can't be mistaken for
96
+ * one half of a non-null UTF-16 code unit at an odd offset the way a naive single-byte scan could.
97
+ *
98
+ * Throws if no terminator is found before running out of buffer, rather than silently returning whatever
99
+ * content preceded the truncation and leaving `offset` past the buffer's actual end - a truncated/malformed
100
+ * string is exactly the kind of input this class's own doc comment says should fail loudly. This also
101
+ * closes a real DoS: `PropertyValue.ts`'s `readCountedArray` loops a client-controlled element count with
102
+ * no independent cap, trusting each element's own reader to terminate the loop early against a too-small
103
+ * buffer the way every fixed-width reader already does - without this, `PtypMultipleString`'s
104
+ * `readNullTerminatedUtf16LE` calls kept silently returning `""` past the buffer's end forever instead. */
105
+ readNullTerminatedUtf16LE() {
106
+ let end = this.offset;
107
+ while (end + 1 < this.buffer.length && !(this.buffer[end] === 0 && this.buffer[end + 1] === 0)) {
108
+ end += 2;
109
+ }
110
+ if (end + 1 >= this.buffer.length) {
111
+ throw new RangeError("BufferReader.readNullTerminatedUtf16LE(): no null terminator found before the end of the buffer.");
112
+ }
113
+ const value = this.buffer.toString("utf16le", this.offset, end);
114
+ this.offset = end + 2;
115
+ return value;
116
+ }
117
+ /** Reads a single-byte-terminated 8-bit string (`PtypString8`'s wire encoding). Multibyte string content
118
+ * itself is treated as UTF-8, a pragmatic choice documented in `PropertyValue.ts` rather than the
119
+ * "externally specified encoding" the spec leaves open-ended.
120
+ *
121
+ * Throws if no terminator is found before running out of buffer - see `readNullTerminatedUtf16LE`'s own
122
+ * doc comment for why (identical reasoning, including the `readCountedArray` DoS this closes). */
123
+ readNullTerminatedString8() {
124
+ let end = this.offset;
125
+ while (end < this.buffer.length && this.buffer[end] !== 0) {
126
+ end += 1;
127
+ }
128
+ if (end >= this.buffer.length) {
129
+ throw new RangeError("BufferReader.readNullTerminatedString8(): no null terminator found before the end of the buffer.");
130
+ }
131
+ const value = this.buffer.toString("utf-8", this.offset, end);
132
+ this.offset = end + 1;
133
+ return value;
134
+ }
135
+ }
136
+ /** The write-side counterpart to `BufferReader`, accumulating chunks and concatenating once via `toBuffer()` -
137
+ * the same "array of pieces, `Buffer.concat` once at the end" idiom `WbxmlEncoder` already uses for its own
138
+ * byte-array accumulator, adapted for multi-byte little-endian fields instead of single bytes. */
139
+ export class BufferWriter {
140
+ constructor() {
141
+ this.chunks = [];
142
+ }
143
+ writeUInt8(value) {
144
+ const buf = Buffer.alloc(1);
145
+ buf.writeUInt8(value, 0);
146
+ this.chunks.push(buf);
147
+ return this;
148
+ }
149
+ writeInt16LE(value) {
150
+ const buf = Buffer.alloc(2);
151
+ buf.writeInt16LE(value, 0);
152
+ this.chunks.push(buf);
153
+ return this;
154
+ }
155
+ writeUInt16LE(value) {
156
+ const buf = Buffer.alloc(2);
157
+ buf.writeUInt16LE(value, 0);
158
+ this.chunks.push(buf);
159
+ return this;
160
+ }
161
+ writeInt32LE(value) {
162
+ const buf = Buffer.alloc(4);
163
+ buf.writeInt32LE(value, 0);
164
+ this.chunks.push(buf);
165
+ return this;
166
+ }
167
+ writeUInt32LE(value) {
168
+ const buf = Buffer.alloc(4);
169
+ buf.writeUInt32LE(value, 0);
170
+ this.chunks.push(buf);
171
+ return this;
172
+ }
173
+ writeFloatLE(value) {
174
+ const buf = Buffer.alloc(4);
175
+ buf.writeFloatLE(value, 0);
176
+ this.chunks.push(buf);
177
+ return this;
178
+ }
179
+ writeDoubleLE(value) {
180
+ const buf = Buffer.alloc(8);
181
+ buf.writeDoubleLE(value, 0);
182
+ this.chunks.push(buf);
183
+ return this;
184
+ }
185
+ writeBigInt64LE(value) {
186
+ const buf = Buffer.alloc(8);
187
+ buf.writeBigInt64LE(value, 0);
188
+ this.chunks.push(buf);
189
+ return this;
190
+ }
191
+ writeBigUInt64LE(value) {
192
+ const buf = Buffer.alloc(8);
193
+ buf.writeBigUInt64LE(value, 0);
194
+ this.chunks.push(buf);
195
+ return this;
196
+ }
197
+ writeBytes(value) {
198
+ this.chunks.push(value);
199
+ return this;
200
+ }
201
+ writeNullTerminatedUtf16LE(value) {
202
+ this.chunks.push(Buffer.from(value, "utf16le"), Buffer.from([0x00, 0x00]));
203
+ return this;
204
+ }
205
+ writeNullTerminatedString8(value) {
206
+ this.chunks.push(Buffer.from(value, "utf-8"), Buffer.from([0x00]));
207
+ return this;
208
+ }
209
+ toBuffer() {
210
+ return Buffer.concat(this.chunks);
211
+ }
212
+ }
213
+ //# sourceMappingURL=BufferCursor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BufferCursor.js","sourceRoot":"","sources":["../../../src/codec/BufferCursor.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,92 @@
1
+ ///////////////////////////////////////////////////////////////////////////////
2
+ // Copyright (C) 2026 Jean-Philippe Steinmetz. All rights reserved.
3
+ // SPDX-License-Identifier: MPL-2.0
4
+ ///////////////////////////////////////////////////////////////////////////////
5
+ import { BufferWriter } from "./BufferCursor.js";
6
+ import { dateToFiletime } from "./PropertyValue.js";
7
+ /**
8
+ * Encodes/decodes `CalendarEvent.icalUid` to/from the `GlobalObjectId` BLOB carried by `PidLidGlobalObjectId`
9
+ * (`[MS-OXOCAL]` §2.2.1.27, confirmed field-by-field this session): `ByteArrayID(16, a fixed spec constant)
10
+ * +YH(1)+YL(1)+M(1)+D(1)+CreationTime(8,FILETIME)+X(8,reserved,zero)+Size(4)+Data(variable, Size bytes)` - 40
11
+ * fixed header bytes followed by `Data`.
12
+ *
13
+ * **This server never actually embeds a `GlobalObjectId` in the invites it sends** - `RopSubmitMessageHandler
14
+ * .submitAppointment()`'s invite is a plain RFC 5546 `METHOD:REQUEST` iCalendar attachment relayed over SMTP,
15
+ * not a native MAPI Store object a real client reads `PidLidGlobalObjectId` off of directly; that iCalendar
16
+ * only ever carries a plain `UID:` line. A real Outlook client receiving it therefore always *synthesizes* its
17
+ * own `PidLidGlobalObjectId` from that bare iCalendar UID, using the `VCALID` form both `[MS-ASEMAIL]` §2.2.2.37
18
+ * and the equivalent `[MS-OXCICAL]` UID-conversion algorithm document: `Data` = the 8-byte ASCII marker
19
+ * `"vCal-Uid"`, then a 4-byte version (`01 00 00 00`), then the UID's own bytes, then one trailing `NUL` -
20
+ * **not** the bare UID bytes this codec originally assumed. `decodeGlobalObjectId` recognizes and unwraps this
21
+ * form (falling back to treating `Data` as bare bytes only when the `vCal-Uid` marker isn't present, i.e. an
22
+ * `OutlookID` - a native-Exchange-generated `GlobalObjectId` this server's own SMTP-based invite flow never
23
+ * produces, but decoded for completeness). `encodeGlobalObjectId` now builds the same `VCALID` shape, both so
24
+ * a round trip through this codec models the real wire form and because `MeetingMessageClassHandler.test.ts`'s
25
+ * own test doubles need a realistic fixture to encode.
26
+ *
27
+ * **Pragmatic scope**: `YH`/`YL`/`M`/`D` (the `PidLidExceptionReplaceTime` fields, used only when a
28
+ * `GlobalObjectId` identifies a single modified occurrence of a recurring series) are always encoded as `0`
29
+ * ("not an exception") and not decoded - recurrence exceptions are a documented gap elsewhere in this pragmatic
30
+ * subset (see `AppointmentRecurrence.ts`'s own doc comment), so there is nothing to source a real value from.
31
+ * `PidLidCleanGlobalObjectId` (the sibling property real Outlook also sets, identical structure but with
32
+ * `YH`/`YL`/`M`/`D` always zeroed) is therefore byte-identical to this codec's own output and needs no separate
33
+ * implementation - the same value serves both properties.
34
+ *
35
+ * @author Jean-Philippe Steinmetz
36
+ */
37
+ /** `ByteArrayID` (`[MS-OXOCAL]` §2.2.1.27): a fixed 16-byte constant identifying this BLOB as a `GlobalObjectId`
38
+ * - the spec's own exact required byte sequence, not invented. */
39
+ const BYTE_ARRAY_ID = Buffer.from([0x04, 0x00, 0x00, 0x00, 0x82, 0x00, 0xe0, 0x00, 0x74, 0xc5, 0xb7, 0x10, 0x1a, 0x82, 0xe0, 0x08]);
40
+ /** `VCALID`'s own fixed 8-byte ASCII marker (`[MS-ASEMAIL]` §2.2.2.37's `VCALSTRING`) identifying `Data` as a
41
+ * `"vCal-Uid"`-wrapped UID rather than a native `OutlookID`'s raw bytes. */
42
+ const VCAL_MARKER = Buffer.from("vCal-Uid", "ascii");
43
+ /** `VCALID`'s own fixed 4-byte `VERSION` field - always this exact literal byte sequence, not a real version
44
+ * negotiated at runtime. */
45
+ const VCAL_VERSION = Buffer.from([0x01, 0x00, 0x00, 0x00]);
46
+ /** A `VCALID`'s minimum possible `Data` length: `VCAL_MARKER` (8) + `VCAL_VERSION` (4) + a zero-length UID (0)
47
+ * + the trailing `NUL` (1). */
48
+ const VCAL_MIN_DATA_LENGTH = VCAL_MARKER.length + VCAL_VERSION.length + 1;
49
+ /** Encodes `icalUid` into a `GlobalObjectId` BLOB in the `VCALID` form a real client synthesizes from a plain
50
+ * iCalendar UID (see this file's own doc comment for why that's the form that matters here), using `at` as the
51
+ * `CreationTime`. */
52
+ export function encodeGlobalObjectId(icalUid, at) {
53
+ const data = Buffer.concat([VCAL_MARKER, VCAL_VERSION, Buffer.from(icalUid, "utf-8"), Buffer.from([0x00])]);
54
+ const writer = new BufferWriter();
55
+ writer.writeBytes(BYTE_ARRAY_ID);
56
+ writer.writeUInt8(0); // YH - not an exception, see class doc comment
57
+ writer.writeUInt8(0); // YL
58
+ writer.writeUInt8(0); // M
59
+ writer.writeUInt8(0); // D
60
+ writer.writeBigUInt64LE(dateToFiletime(at)); // CreationTime
61
+ writer.writeBytes(Buffer.alloc(8)); // X - reserved, MUST be zero
62
+ writer.writeUInt32LE(data.length); // Size
63
+ writer.writeBytes(data);
64
+ return writer.toBuffer();
65
+ }
66
+ /** Decodes a `GlobalObjectId` BLOB (read from `reader`'s current position) back into the `icalUid` it was
67
+ * built from - unwrapping the `VCALID` form (see this file's own doc comment) when `Data` carries the
68
+ * `"vCal-Uid"` marker, falling back to treating `Data` as bare `OutlookID` bytes otherwise. Throws if
69
+ * `ByteArrayID` doesn't match the spec's own fixed constant - a real, spec-mandated identity check, not an
70
+ * invented restriction. */
71
+ export function decodeGlobalObjectId(reader) {
72
+ const byteArrayId = reader.readBytes(16);
73
+ if (!byteArrayId.equals(BYTE_ARRAY_ID)) {
74
+ throw new Error("GlobalObjectId: ByteArrayID did not match the required [MS-OXOCAL] constant.");
75
+ }
76
+ reader.readUInt8(); // YH
77
+ reader.readUInt8(); // YL
78
+ reader.readUInt8(); // M
79
+ reader.readUInt8(); // D
80
+ reader.readBigUInt64LE(); // CreationTime - not needed to recover icalUid
81
+ reader.readBytes(8); // X
82
+ const size = reader.readUInt32LE();
83
+ const data = reader.readBytes(size);
84
+ const isVCalId = size >= VCAL_MIN_DATA_LENGTH && data.subarray(0, 8).equals(VCAL_MARKER) && data.subarray(8, 12).equals(VCAL_VERSION);
85
+ if (isVCalId) {
86
+ // Strip the marker+version prefix and the trailing NUL, per [MS-ASEMAIL]'s own UID-length formula
87
+ // (BYTECOUNT minus the marker minus the version minus 1 byte for the NUL).
88
+ return data.subarray(12, data.length - 1).toString("utf-8");
89
+ }
90
+ return data.toString("utf-8");
91
+ }
92
+ //# sourceMappingURL=GlobalObjectId.js.map