iobroker.bee 0.0.0-stage → 0.2.2

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.
@@ -0,0 +1,293 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Mux = exports.TunnelStream = exports.ConsoleReader = exports.CONSOLE = exports.CLOSE = exports.REFUSE_HEADER = exports.MAX_PAYLOAD = exports.WINDOW = exports.FRAME = void 0;
4
+ exports.consoleMessage = consoleMessage;
5
+ exports.resizeMessage = resizeMessage;
6
+ /**
7
+ * Stream multiplexer of the remote access tunnel.
8
+ *
9
+ * This file exists in both repositories: beekeeper-server/src/tunnel.ts and ioBroker.bee/src/lib/tunnel.ts.
10
+ * Both copies must stay identical; it imports nothing but Node.js.
11
+ *
12
+ * One WebSocket between agent and server carries all connections of the browser (admin and vis-2 open
13
+ * several at once, plus their own WebSocket). Every binary WebSocket message is one frame:
14
+ *
15
+ * type (1 byte) | stream id (4 bytes, big endian) | payload
16
+ *
17
+ * OPEN server -> agent new stream; the agent connects it to the local web interface, or starts a shell
18
+ * (console, see CONSOLE)
19
+ * DATA both bytes of the stream, at most MAX_PAYLOAD
20
+ * END both no more bytes in this direction (half close)
21
+ * RESET both abort; payload: reason (UTF-8)
22
+ * ACK both payload: 4 bytes, number of received bytes passed on
23
+ *
24
+ * Flow control per stream: a side sends at most WINDOW bytes the other side has not acknowledged.
25
+ * A slow browser or a slow local interface stalls only its own stream, not the tunnel, and neither
26
+ * side buffers more than WINDOW bytes per stream.
27
+ */
28
+ const node_stream_1 = require("node:stream");
29
+ exports.FRAME = { OPEN: 1, DATA: 2, END: 3, RESET: 4, ACK: 5 };
30
+ exports.WINDOW = 256 * 1024;
31
+ exports.MAX_PAYLOAD = 32 * 1024;
32
+ const ACK_THRESHOLD = 64 * 1024;
33
+ /**
34
+ * Header of the tunnel request with which the agent refuses a session (URI-encoded reason), e.g. because
35
+ * the owner does not allow this kind of access. The server answers 403 and ends the session.
36
+ */
37
+ exports.REFUSE_HEADER = 'x-bee-refused';
38
+ /** WebSocket close codes of the tunnel connection */
39
+ exports.CLOSE = {
40
+ /** The session ended (operator, expiry, server stop): do not reconnect */
41
+ ENDED: 4000,
42
+ /** The agent refuses: the owner does not allow this kind of access */
43
+ NOT_ALLOWED: 4003,
44
+ /** The agent finds no local web interface of this kind */
45
+ NO_TARGET: 4004,
46
+ /** Another connection of the same session took over */
47
+ REPLACED: 4009,
48
+ };
49
+ /**
50
+ * A stream of a console session carries messages instead of raw bytes, because besides the bytes of the
51
+ * terminal the size of the window travels too:
52
+ *
53
+ * type (1 byte) | length (4 bytes, big endian) | payload
54
+ *
55
+ * DATA both bytes of the terminal: input of the browser, output of the shell
56
+ * RESIZE server -> agent columns (2 bytes) | rows (2 bytes)
57
+ * EXIT agent -> server the shell ended; payload: exit code or signal (UTF-8)
58
+ */
59
+ exports.CONSOLE = { DATA: 1, RESIZE: 2, EXIT: 3 };
60
+ const MAX_CONSOLE_MESSAGE = 1024 * 1024;
61
+ function consoleMessage(type, payload = Buffer.alloc(0)) {
62
+ const buf = Buffer.allocUnsafe(5 + payload.length);
63
+ buf[0] = type;
64
+ buf.writeUInt32BE(payload.length, 1);
65
+ payload.copy(buf, 5);
66
+ return buf;
67
+ }
68
+ function resizeMessage(cols, rows) {
69
+ const payload = Buffer.allocUnsafe(4);
70
+ payload.writeUInt16BE(cols, 0);
71
+ payload.writeUInt16BE(rows, 2);
72
+ return consoleMessage(exports.CONSOLE.RESIZE, payload);
73
+ }
74
+ /** Splits the bytes of a console stream into messages */
75
+ class ConsoleReader {
76
+ onMessage;
77
+ buf = Buffer.alloc(0);
78
+ constructor(onMessage) {
79
+ this.onMessage = onMessage;
80
+ }
81
+ /** @returns false if the stream is broken (message too large); then nothing more comes out */
82
+ push(chunk) {
83
+ this.buf = this.buf.length ? Buffer.concat([this.buf, chunk]) : chunk;
84
+ while (this.buf.length >= 5) {
85
+ const length = this.buf.readUInt32BE(1);
86
+ if (length > MAX_CONSOLE_MESSAGE) {
87
+ this.buf = Buffer.alloc(0);
88
+ return false;
89
+ }
90
+ if (this.buf.length < 5 + length) {
91
+ break;
92
+ }
93
+ const type = this.buf[0];
94
+ const payload = this.buf.subarray(5, 5 + length);
95
+ this.buf = this.buf.subarray(5 + length);
96
+ this.onMessage(type, payload);
97
+ }
98
+ return true;
99
+ }
100
+ }
101
+ exports.ConsoleReader = ConsoleReader;
102
+ function frame(type, id, payload) {
103
+ const buf = Buffer.allocUnsafe(5 + (payload?.length ?? 0));
104
+ buf[0] = type;
105
+ buf.writeUInt32BE(id, 1);
106
+ payload?.copy(buf, 5);
107
+ return buf;
108
+ }
109
+ /** One connection of the browser inside the tunnel */
110
+ class TunnelStream extends node_stream_1.Duplex {
111
+ mux;
112
+ id;
113
+ /** Bytes this side may still send */
114
+ credit = exports.WINDOW;
115
+ /** Write waiting for credit */
116
+ pending = null;
117
+ /** Received bytes passed on but not yet acknowledged */
118
+ unacked = 0;
119
+ /** The reader did not take everything: acknowledge once it asks for more */
120
+ paused = false;
121
+ sentEnd = false;
122
+ gotEnd = false;
123
+ gotReset = false;
124
+ constructor(mux, id) {
125
+ super({ allowHalfOpen: true });
126
+ this.mux = mux;
127
+ this.id = id;
128
+ }
129
+ _write(chunk, _encoding, callback) {
130
+ this.send(chunk, callback);
131
+ }
132
+ send(chunk, callback) {
133
+ while (chunk.length) {
134
+ const n = Math.min(chunk.length, this.credit, exports.MAX_PAYLOAD);
135
+ if (n <= 0) {
136
+ this.pending = { chunk, callback };
137
+ return;
138
+ }
139
+ this.mux.sendFrame(exports.FRAME.DATA, this.id, chunk.subarray(0, n));
140
+ this.mux.bytesOut += n;
141
+ this.credit -= n;
142
+ chunk = chunk.subarray(n);
143
+ }
144
+ callback();
145
+ }
146
+ _final(callback) {
147
+ this.sentEnd = true;
148
+ this.mux.sendFrame(exports.FRAME.END, this.id);
149
+ callback();
150
+ }
151
+ _read() {
152
+ if (this.paused) {
153
+ this.paused = false;
154
+ this.ack(true);
155
+ }
156
+ }
157
+ _destroy(err, callback) {
158
+ if (!this.gotReset && !(this.sentEnd && this.gotEnd)) {
159
+ this.mux.sendFrame(exports.FRAME.RESET, this.id, Buffer.from(err?.message || 'closed'));
160
+ }
161
+ this.mux.forget(this.id);
162
+ const pending = this.pending;
163
+ this.pending = null;
164
+ pending?.callback(err || new Error('stream closed'));
165
+ callback(err);
166
+ }
167
+ ack(force) {
168
+ if (this.paused || !this.unacked || this.destroyed || (!force && this.unacked < ACK_THRESHOLD)) {
169
+ return;
170
+ }
171
+ const payload = Buffer.allocUnsafe(4);
172
+ payload.writeUInt32BE(this.unacked, 0);
173
+ this.unacked = 0;
174
+ this.mux.sendFrame(exports.FRAME.ACK, this.id, payload);
175
+ }
176
+ /** Frame from the other side, passed on by the Mux */
177
+ handle(type, payload) {
178
+ if (type === exports.FRAME.DATA) {
179
+ if (this.gotEnd) {
180
+ return;
181
+ }
182
+ this.mux.bytesIn += payload.length;
183
+ this.unacked += payload.length;
184
+ if (this.push(payload)) {
185
+ this.ack(false);
186
+ }
187
+ else {
188
+ this.paused = true;
189
+ }
190
+ }
191
+ else if (type === exports.FRAME.END) {
192
+ this.gotEnd = true;
193
+ this.push(null);
194
+ }
195
+ else if (type === exports.FRAME.ACK && payload.length >= 4) {
196
+ this.credit += payload.readUInt32BE(0);
197
+ const pending = this.pending;
198
+ if (pending) {
199
+ this.pending = null;
200
+ this.send(pending.chunk, pending.callback);
201
+ }
202
+ }
203
+ else if (type === exports.FRAME.RESET) {
204
+ this.gotReset = true;
205
+ this.destroy(new Error(payload.toString('utf8') || 'reset by the other side'));
206
+ }
207
+ }
208
+ }
209
+ exports.TunnelStream = TunnelStream;
210
+ /**
211
+ * Both ends of the tunnel. `send` puts a frame on the WebSocket; frames from the WebSocket go to
212
+ * `receive`. Only the server opens streams; the agent gets them through `onStream`.
213
+ */
214
+ class Mux {
215
+ send;
216
+ onStream;
217
+ streams = new Map();
218
+ nextId = 1;
219
+ closed = false;
220
+ /** Payload bytes received and sent, for the audit log */
221
+ bytesIn = 0;
222
+ bytesOut = 0;
223
+ constructor(send, onStream) {
224
+ this.send = send;
225
+ this.onStream = onStream;
226
+ }
227
+ get size() {
228
+ return this.streams.size;
229
+ }
230
+ get isClosed() {
231
+ return this.closed;
232
+ }
233
+ open() {
234
+ if (this.closed) {
235
+ throw new Error('tunnel closed');
236
+ }
237
+ const id = this.nextId;
238
+ this.nextId = this.nextId >= 0xffffffff ? 1 : this.nextId + 1;
239
+ const stream = new TunnelStream(this, id);
240
+ this.streams.set(id, stream);
241
+ this.sendFrame(exports.FRAME.OPEN, id);
242
+ return stream;
243
+ }
244
+ receive(buf) {
245
+ if (this.closed || buf.length < 5) {
246
+ return;
247
+ }
248
+ const type = buf[0];
249
+ const id = buf.readUInt32BE(1);
250
+ const payload = buf.subarray(5);
251
+ if (type === exports.FRAME.OPEN) {
252
+ if (!this.onStream || this.streams.has(id)) {
253
+ this.sendFrame(exports.FRAME.RESET, id, Buffer.from('refused'));
254
+ return;
255
+ }
256
+ const stream = new TunnelStream(this, id);
257
+ this.streams.set(id, stream);
258
+ this.onStream(stream);
259
+ return;
260
+ }
261
+ const stream = this.streams.get(id);
262
+ if (stream) {
263
+ stream.handle(type, payload);
264
+ }
265
+ else if (type === exports.FRAME.DATA) {
266
+ // late data for a stream this side already closed
267
+ this.sendFrame(exports.FRAME.RESET, id, Buffer.from('unknown stream'));
268
+ }
269
+ }
270
+ /** @internal */
271
+ sendFrame(type, id, payload) {
272
+ if (!this.closed) {
273
+ this.send(frame(type, id, payload));
274
+ }
275
+ }
276
+ /** @internal */
277
+ forget(id) {
278
+ this.streams.delete(id);
279
+ }
280
+ /** The WebSocket is gone: every stream ends with an error */
281
+ close(reason = 'tunnel closed') {
282
+ if (this.closed) {
283
+ return;
284
+ }
285
+ this.closed = true;
286
+ for (const stream of [...this.streams.values()]) {
287
+ stream.destroy(new Error(reason));
288
+ }
289
+ this.streams.clear();
290
+ }
291
+ }
292
+ exports.Mux = Mux;
293
+ //# sourceMappingURL=tunnel.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tunnel.js","sourceRoot":"","sources":["../../src/lib/tunnel.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,102 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.encodePayload = encodePayload;
7
+ exports.decodePayload = decodePayload;
8
+ exports.compareVersions = compareVersions;
9
+ exports.flatten = flatten;
10
+ exports.diffFlat = diffFlat;
11
+ exports.writeJsonAtomic = writeJsonAtomic;
12
+ exports.readJson = readJson;
13
+ exports.inWindow = inWindow;
14
+ exports.sleep = sleep;
15
+ const node_zlib_1 = require("node:zlib");
16
+ const promises_1 = require("node:fs/promises");
17
+ const semver_1 = __importDefault(require("semver"));
18
+ function encodePayload(obj) {
19
+ return (0, node_zlib_1.gzipSync)(Buffer.from(JSON.stringify(obj)));
20
+ }
21
+ function decodePayload(buf) {
22
+ if (buf.length > 2 && buf[0] === 0x1f && buf[1] === 0x8b) {
23
+ buf = (0, node_zlib_1.gunzipSync)(buf);
24
+ }
25
+ return JSON.parse(buf.toString('utf8'));
26
+ }
27
+ /**
28
+ * Compares two versions. Non-semver strings are compared lexically.
29
+ *
30
+ * @returns negative if a < b, 0 if equal, positive if a > b
31
+ */
32
+ function compareVersions(a, b) {
33
+ const va = semver_1.default.valid(a) ?? semver_1.default.coerce(a)?.version;
34
+ const vb = semver_1.default.valid(b) ?? semver_1.default.coerce(b)?.version;
35
+ if (va && vb) {
36
+ return semver_1.default.compare(va, vb);
37
+ }
38
+ return a.localeCompare(b);
39
+ }
40
+ function escapeKey(key) {
41
+ return key.replace(/~/g, '~0').replace(/\//g, '~1');
42
+ }
43
+ /** Flattens an object to JSON pointer paths (same format as the server) */
44
+ function flatten(obj, prefix = '', out = {}) {
45
+ if (obj !== null && typeof obj === 'object' && !Array.isArray(obj) && Object.keys(obj).length) {
46
+ for (const [k, v] of Object.entries(obj)) {
47
+ flatten(v, `${prefix}/${escapeKey(k)}`, out);
48
+ }
49
+ }
50
+ else {
51
+ out[prefix] = obj;
52
+ }
53
+ return out;
54
+ }
55
+ function diffFlat(prev, next) {
56
+ const set = {};
57
+ const unset = [];
58
+ for (const [k, v] of Object.entries(next)) {
59
+ if (!(k in prev) || JSON.stringify(prev[k]) !== JSON.stringify(v)) {
60
+ set[k] = v;
61
+ }
62
+ }
63
+ for (const k of Object.keys(prev)) {
64
+ if (!(k in next)) {
65
+ unset.push(k);
66
+ }
67
+ }
68
+ return { set, unset };
69
+ }
70
+ /** Writes JSON atomically (temp file + rename), so a power loss never leaves half a file */
71
+ async function writeJsonAtomic(file, data) {
72
+ const tmp = `${file}.tmp`;
73
+ await (0, promises_1.writeFile)(tmp, JSON.stringify(data, null, 2), { mode: 0o600 });
74
+ await (0, promises_1.rename)(tmp, file);
75
+ }
76
+ async function readJson(file) {
77
+ try {
78
+ return JSON.parse(await (0, promises_1.readFile)(file, 'utf8'));
79
+ }
80
+ catch {
81
+ return null;
82
+ }
83
+ }
84
+ /** "HH:MM" -> minutes since midnight */
85
+ function minutes(hhmm) {
86
+ const m = /^(\d{1,2}):(\d{2})$/.exec((hhmm || '').trim());
87
+ return m ? parseInt(m[1], 10) * 60 + parseInt(m[2], 10) : null;
88
+ }
89
+ /** True if `date` lies in the daily window [start, end). Windows may span midnight (22:00-05:00). */
90
+ function inWindow(date, start, end) {
91
+ const s = minutes(start);
92
+ const e = minutes(end);
93
+ if (s === null || e === null || s === e) {
94
+ return true;
95
+ }
96
+ const now = date.getHours() * 60 + date.getMinutes();
97
+ return s < e ? now >= s && now < e : now >= s || now < e;
98
+ }
99
+ function sleep(ms) {
100
+ return new Promise(resolve => setTimeout(resolve, ms));
101
+ }
102
+ //# sourceMappingURL=util.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"util.js","sourceRoot":"","sources":["../../src/lib/util.ts"],"names":[],"mappings":";;;;;AAIA,sCAEC;AAED,sCAKC;AAOD,0CAOC;AAOD,0BASC;AAED,4BAiBC;AAGD,0CAIC;AAED,4BAMC;AASD,4BAQC;AAED,sBAEC;AAlGD,yCAAiD;AACjD,+CAA+D;AAC/D,oDAA4B;AAE5B,SAAgB,aAAa,CAAC,GAAY;IACtC,OAAO,IAAA,oBAAQ,EAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;AACtD,CAAC;AAED,SAAgB,aAAa,CAAI,GAAW;IACxC,IAAI,GAAG,CAAC,MAAM,GAAG,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC,KAAK,IAAI,IAAI,GAAG,CAAC,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC;QACvD,GAAG,GAAG,IAAA,sBAAU,EAAC,GAAG,CAAC,CAAC;IAC1B,CAAC;IACD,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAM,CAAC;AACjD,CAAC;AAED;;;;GAIG;AACH,SAAgB,eAAe,CAAC,CAAS,EAAE,CAAS;IAChD,MAAM,EAAE,GAAG,gBAAM,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,gBAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,OAAO,CAAC;IACxD,MAAM,EAAE,GAAG,gBAAM,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,gBAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,OAAO,CAAC;IACxD,IAAI,EAAE,IAAI,EAAE,EAAE,CAAC;QACX,OAAO,gBAAM,CAAC,OAAO,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC;IAClC,CAAC;IACD,OAAO,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC;AAC9B,CAAC;AAED,SAAS,SAAS,CAAC,GAAW;IAC1B,OAAO,GAAG,CAAC,OAAO,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;AACxD,CAAC;AAED,2EAA2E;AAC3E,SAAgB,OAAO,CAAC,GAAY,EAAE,MAAM,GAAG,EAAE,EAAE,MAA+B,EAAE;IAChF,IAAI,GAAG,KAAK,IAAI,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,CAAC;QAC5F,KAAK,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,GAA8B,CAAC,EAAE,CAAC;YAClE,OAAO,CAAC,CAAC,EAAE,GAAG,MAAM,IAAI,SAAS,CAAC,CAAC,CAAC,EAAE,EAAE,GAAG,CAAC,CAAC;QACjD,CAAC;IACL,CAAC;SAAM,CAAC;QACJ,GAAG,CAAC,MAAM,CAAC,GAAG,GAAG,CAAC;IACtB,CAAC;IACD,OAAO,GAAG,CAAC;AACf,CAAC;AAED,SAAgB,QAAQ,CACpB,IAA6B,EAC7B,IAA6B;IAE7B,MAAM,GAAG,GAA4B,EAAE,CAAC;IACxC,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,KAAK,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;QACxC,IAAI,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,IAAI,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC;YAChE,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;QACf,CAAC;IACL,CAAC;IACD,KAAK,MAAM,CAAC,IAAI,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QAChC,IAAI,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,EAAE,CAAC;YACf,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;IACL,CAAC;IACD,OAAO,EAAE,GAAG,EAAE,KAAK,EAAE,CAAC;AAC1B,CAAC;AAED,4FAA4F;AACrF,KAAK,UAAU,eAAe,CAAC,IAAY,EAAE,IAAa;IAC7D,MAAM,GAAG,GAAG,GAAG,IAAI,MAAM,CAAC;IAC1B,MAAM,IAAA,oBAAS,EAAC,GAAG,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IACrE,MAAM,IAAA,iBAAM,EAAC,GAAG,EAAE,IAAI,CAAC,CAAC;AAC5B,CAAC;AAEM,KAAK,UAAU,QAAQ,CAAI,IAAY;IAC1C,IAAI,CAAC;QACD,OAAO,IAAI,CAAC,KAAK,CAAC,MAAM,IAAA,mBAAQ,EAAC,IAAI,EAAE,MAAM,CAAC,CAAM,CAAC;IACzD,CAAC;IAAC,MAAM,CAAC;QACL,OAAO,IAAI,CAAC;IAChB,CAAC;AACL,CAAC;AAED,wCAAwC;AACxC,SAAS,OAAO,CAAC,IAAY;IACzB,MAAM,CAAC,GAAG,qBAAqB,CAAC,IAAI,CAAC,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;IAC1D,OAAO,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,EAAE,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;AACnE,CAAC;AAED,qGAAqG;AACrG,SAAgB,QAAQ,CAAC,IAAU,EAAE,KAAa,EAAE,GAAW;IAC3D,MAAM,CAAC,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC;IACzB,MAAM,CAAC,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC;IACvB,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;QACtC,OAAO,IAAI,CAAC;IAChB,CAAC;IACD,MAAM,GAAG,GAAG,IAAI,CAAC,QAAQ,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,UAAU,EAAE,CAAC;IACrD,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,IAAI,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,IAAI,GAAG,GAAG,CAAC,CAAC;AAC7D,CAAC;AAED,SAAgB,KAAK,CAAC,EAAU;IAC5B,OAAO,IAAI,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,CAAC;AAC3D,CAAC"}