autodev-cli 1.4.0 → 1.4.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 (237) hide show
  1. package/bin/autodev.js +0 -0
  2. package/out/agentBackup/archive.d.ts +44 -0
  3. package/out/agentBackup/archive.js +131 -0
  4. package/out/agentBackup/archive.js.map +1 -0
  5. package/out/agentBackup/export.d.ts +18 -0
  6. package/out/agentBackup/export.js +92 -0
  7. package/out/agentBackup/export.js.map +1 -0
  8. package/out/agentBackup/import.d.ts +21 -0
  9. package/out/agentBackup/import.js +40 -0
  10. package/out/agentBackup/import.js.map +1 -0
  11. package/out/agentBackup/index.d.ts +6 -0
  12. package/out/agentBackup/index.js +11 -0
  13. package/out/agentBackup/index.js.map +1 -0
  14. package/out/agentBackup/layout.d.ts +30 -0
  15. package/out/agentBackup/layout.js +126 -0
  16. package/out/agentBackup/layout.js.map +1 -0
  17. package/out/agentBackup/manifest.d.ts +24 -0
  18. package/out/agentBackup/manifest.js +70 -0
  19. package/out/agentBackup/manifest.js.map +1 -0
  20. package/out/agentBackup/opencodeDb.d.ts +20 -0
  21. package/out/agentBackup/opencodeDb.js +213 -0
  22. package/out/agentBackup/opencodeDb.js.map +1 -0
  23. package/out/agentBackup/sessionProviders.d.ts +35 -0
  24. package/out/agentBackup/sessionProviders.js +263 -0
  25. package/out/agentBackup/sessionProviders.js.map +1 -0
  26. package/out/agentBackup/upload.d.ts +9 -0
  27. package/out/agentBackup/upload.js +121 -0
  28. package/out/agentBackup/upload.js.map +1 -0
  29. package/out/cli.d.ts +1 -0
  30. package/out/cli.js +8 -0
  31. package/out/cli.js.map +1 -1
  32. package/out/cliExit.d.ts +34 -0
  33. package/out/cliExit.js +159 -0
  34. package/out/cliExit.js.map +1 -0
  35. package/out/commands/config.d.ts +2 -0
  36. package/out/commands/config.js +7 -7
  37. package/out/commands/config.js.map +1 -1
  38. package/out/commands/connect.d.ts +2 -0
  39. package/out/commands/connect.js +11 -0
  40. package/out/commands/connect.js.map +1 -1
  41. package/out/commands/export.d.ts +2 -0
  42. package/out/commands/export.js +79 -0
  43. package/out/commands/export.js.map +1 -0
  44. package/out/commands/import.d.ts +2 -0
  45. package/out/commands/import.js +92 -0
  46. package/out/commands/import.js.map +1 -0
  47. package/out/commands/init.d.ts +16 -0
  48. package/out/commands/init.js +9 -5
  49. package/out/commands/init.js.map +1 -1
  50. package/out/commands/resume.d.ts +2 -0
  51. package/out/commands/resume.js +65 -0
  52. package/out/commands/resume.js.map +1 -0
  53. package/out/commands/sessions.d.ts +2 -0
  54. package/out/commands/sessions.js +64 -0
  55. package/out/commands/sessions.js.map +1 -0
  56. package/out/commands/start.d.ts +2 -0
  57. package/out/commands/start.js +40 -7
  58. package/out/commands/start.js.map +1 -1
  59. package/out/commands/status.d.ts +2 -0
  60. package/out/commands/status.js +3 -3
  61. package/out/commands/status.js.map +1 -1
  62. package/out/commands/tailOutput.d.ts +12 -0
  63. package/out/commands/up.d.ts +3 -0
  64. package/out/configManager.d.ts +42 -0
  65. package/out/configManager.js +430 -0
  66. package/out/configManager.js.map +1 -0
  67. package/out/connect.d.ts +4 -0
  68. package/out/connect.js +7 -7
  69. package/out/connect.js.map +1 -1
  70. package/out/core/adapters.d.ts +34 -0
  71. package/out/core/adapters.js +84 -0
  72. package/out/core/adapters.js.map +1 -0
  73. package/out/core/commandHelpers.d.ts +12 -0
  74. package/out/core/commandHelpers.js +96 -0
  75. package/out/core/commandHelpers.js.map +1 -0
  76. package/out/core/projectMcp.d.ts +25 -0
  77. package/out/core/projectMcp.js +144 -0
  78. package/out/core/projectMcp.js.map +1 -0
  79. package/out/core/provider/BaseProvider.d.ts +14 -0
  80. package/out/core/provider/BaseProvider.js +25 -0
  81. package/out/core/provider/BaseProvider.js.map +1 -0
  82. package/out/core/provider/ProviderRegistry.d.ts +12 -0
  83. package/out/core/provider/ProviderRegistry.js +40 -0
  84. package/out/core/provider/ProviderRegistry.js.map +1 -0
  85. package/out/core/provider/contract.d.ts +62 -0
  86. package/out/core/provider/contract.js +9 -0
  87. package/out/core/provider/contract.js.map +1 -0
  88. package/out/core/provider/implementations.d.ts +54 -0
  89. package/out/core/provider/implementations.js +147 -0
  90. package/out/core/provider/implementations.js.map +1 -0
  91. package/out/core/settingsLoader.d.ts +221 -0
  92. package/out/core/settingsLoader.js +176 -0
  93. package/out/core/settingsLoader.js.map +1 -0
  94. package/out/discordGateway.d.ts +26 -0
  95. package/out/discordGateway.js +230 -0
  96. package/out/discordGateway.js.map +1 -0
  97. package/out/discordPoller.d.ts +28 -0
  98. package/out/discordPoller.js +247 -0
  99. package/out/discordPoller.js.map +1 -0
  100. package/out/dispatcher.d.ts +12 -0
  101. package/out/dispatcher.js +214 -0
  102. package/out/dispatcher.js.map +1 -0
  103. package/out/emailPoller.d.ts +42 -0
  104. package/out/emailPoller.js +221 -0
  105. package/out/emailPoller.js.map +1 -0
  106. package/out/git/gitService.d.ts +36 -0
  107. package/out/git/gitService.js +165 -0
  108. package/out/git/gitService.js.map +1 -0
  109. package/out/hookEventNormalizer.d.ts +39 -0
  110. package/out/hookEventNormalizer.js +397 -0
  111. package/out/hookEventNormalizer.js.map +1 -0
  112. package/out/hooksManager.d.ts +25 -0
  113. package/out/hooksManager.js +471 -0
  114. package/out/hooksManager.js.map +1 -0
  115. package/out/launchIde.d.ts +14 -0
  116. package/out/logger.d.ts +12 -0
  117. package/out/mcpEmailTest.d.ts +29 -0
  118. package/out/mcpEmailTest.js +245 -0
  119. package/out/mcpEmailTest.js.map +1 -0
  120. package/out/mcpInstallCheck.d.ts +23 -0
  121. package/out/mcpInstallCheck.js +219 -0
  122. package/out/mcpInstallCheck.js.map +1 -0
  123. package/out/mcpManager.d.ts +35 -0
  124. package/out/mcpManager.js +371 -0
  125. package/out/mcpManager.js.map +1 -0
  126. package/out/messageBuilder.d.ts +54 -0
  127. package/out/messageBuilder.js +373 -0
  128. package/out/messageBuilder.js.map +1 -0
  129. package/out/openCodeHooksManager.d.ts +23 -0
  130. package/out/openCodeHooksManager.js +511 -0
  131. package/out/openCodeHooksManager.js.map +1 -0
  132. package/out/periodicActions.d.ts +63 -0
  133. package/out/periodicActions.js +237 -0
  134. package/out/periodicActions.js.map +1 -0
  135. package/out/profileBuilder.d.ts +29 -0
  136. package/out/profileBuilder.js +366 -0
  137. package/out/profileBuilder.js.map +1 -0
  138. package/out/prompt.d.ts +12 -0
  139. package/out/prompt.js +18 -0
  140. package/out/prompt.js.map +1 -0
  141. package/out/protocolSections.d.ts +26 -0
  142. package/out/protocolSections.js +209 -0
  143. package/out/protocolSections.js.map +1 -0
  144. package/out/providers/claudeCliProvider.d.ts +71 -0
  145. package/out/providers/claudeCliProvider.js +425 -0
  146. package/out/providers/claudeCliProvider.js.map +1 -0
  147. package/out/providers/claudeTuiProvider.d.ts +23 -0
  148. package/out/providers/claudeTuiProvider.js +296 -0
  149. package/out/providers/claudeTuiProvider.js.map +1 -0
  150. package/out/providers/copilotCliProvider.d.ts +16 -0
  151. package/out/providers/copilotCliProvider.js +44 -0
  152. package/out/providers/copilotCliProvider.js.map +1 -0
  153. package/out/providers/copilotSdkProvider.d.ts +12 -0
  154. package/out/providers/copilotSdkProvider.js +445 -0
  155. package/out/providers/copilotSdkProvider.js.map +1 -0
  156. package/out/providers/grokTuiProvider.d.ts +14 -0
  157. package/out/providers/grokTuiProvider.js +271 -0
  158. package/out/providers/grokTuiProvider.js.map +1 -0
  159. package/out/providers/opencodeCliProvider.d.ts +29 -0
  160. package/out/providers/opencodeCliProvider.js +199 -0
  161. package/out/providers/opencodeCliProvider.js.map +1 -0
  162. package/out/providers/opencodeSdkProvider.d.ts +22 -0
  163. package/out/providers/opencodeSdkProvider.js +557 -0
  164. package/out/providers/opencodeSdkProvider.js.map +1 -0
  165. package/out/providers.d.ts +9 -0
  166. package/out/providers.js +44 -0
  167. package/out/providers.js.map +1 -0
  168. package/out/rateLimit.d.ts +18 -0
  169. package/out/rateLimit.js +90 -0
  170. package/out/rateLimit.js.map +1 -0
  171. package/out/rdp/auth.d.ts +55 -0
  172. package/out/rdp/auth.js +197 -0
  173. package/out/rdp/auth.js.map +1 -0
  174. package/out/rdp/bridge.d.ts +86 -0
  175. package/out/rdp/bridge.js +1398 -0
  176. package/out/rdp/bridge.js.map +1 -0
  177. package/out/rdp/constants.d.ts +86 -0
  178. package/out/rdp/constants.js +182 -0
  179. package/out/rdp/constants.js.map +1 -0
  180. package/out/rdp/index.d.ts +7 -0
  181. package/out/rdp/index.js +14 -0
  182. package/out/rdp/index.js.map +1 -0
  183. package/out/rdp/session.d.ts +30 -0
  184. package/out/rdp/session.js +196 -0
  185. package/out/rdp/session.js.map +1 -0
  186. package/out/rdp/types.d.ts +27 -0
  187. package/out/rdp/types.js +6 -0
  188. package/out/rdp/types.js.map +1 -0
  189. package/out/sdk/index.d.ts +22 -0
  190. package/out/sdk/index.js +81 -0
  191. package/out/sdk/index.js.map +1 -0
  192. package/out/sessionState.d.ts +54 -0
  193. package/out/sessionState.js +284 -0
  194. package/out/sessionState.js.map +1 -0
  195. package/out/sessions.d.ts +11 -0
  196. package/out/sessions.js +32 -0
  197. package/out/sessions.js.map +1 -0
  198. package/out/taskLoop.d.ts +152 -0
  199. package/out/taskLoop.js +2505 -0
  200. package/out/taskLoop.js.map +1 -0
  201. package/out/todo.d.ts +42 -0
  202. package/out/todo.js +311 -0
  203. package/out/todo.js.map +1 -0
  204. package/out/todoWriteManager.d.ts +26 -0
  205. package/out/todoWriteManager.js +44 -0
  206. package/out/todoWriteManager.js.map +1 -0
  207. package/out/vnc/auth.d.ts +52 -0
  208. package/out/vnc/auth.js +181 -0
  209. package/out/vnc/auth.js.map +1 -0
  210. package/out/vnc/bridge.d.ts +40 -0
  211. package/out/vnc/bridge.js +540 -0
  212. package/out/vnc/bridge.js.map +1 -0
  213. package/out/vnc/constants.d.ts +8 -0
  214. package/out/vnc/constants.js +34 -0
  215. package/out/vnc/constants.js.map +1 -0
  216. package/out/vnc/des.d.ts +6 -0
  217. package/out/vnc/des.js +93 -0
  218. package/out/vnc/des.js.map +1 -0
  219. package/out/vnc/index.d.ts +7 -0
  220. package/out/vnc/index.js +13 -0
  221. package/out/vnc/index.js.map +1 -0
  222. package/out/vnc/session.d.ts +18 -0
  223. package/out/vnc/session.js +193 -0
  224. package/out/vnc/session.js.map +1 -0
  225. package/out/vnc/types.d.ts +16 -0
  226. package/out/vnc/types.js +6 -0
  227. package/out/vnc/types.js.map +1 -0
  228. package/out/webSocketPoller.d.ts +95 -0
  229. package/out/webSocketPoller.js +986 -0
  230. package/out/webSocketPoller.js.map +1 -0
  231. package/out/webhook.d.ts +37 -0
  232. package/out/webhook.js +265 -0
  233. package/out/webhook.js.map +1 -0
  234. package/out/webhookPoller.d.ts +40 -0
  235. package/out/webhookPoller.js +378 -0
  236. package/out/webhookPoller.js.map +1 -0
  237. package/package.json +54 -41
@@ -0,0 +1,1398 @@
1
+ "use strict";
2
+ /**
3
+ * RdpBridge — TCP/TLS connection to an RDP server.
4
+ *
5
+ * Implements the minimal RDP protocol phases needed for screen + input:
6
+ *
7
+ * Phase 1 X.224 Connection Request / Confirm (TPKT/X.224)
8
+ * Phase 2 TLS upgrade (PROTOCOL_SSL negotiated in Phase 1)
9
+ * Phase 3 MCS Connect (T.125 / GCC Conference Create)
10
+ * Phase 4 MCS Erect Domain + Attach User
11
+ * Phase 5 MCS Channel Joins (Global + optional cliprdr)
12
+ * Phase 6 RDP Security Exchange + Client Info (Classic RDP Security disabled; TLS carries everything)
13
+ * Phase 7 Demand-Active / Confirm-Active capability exchange
14
+ * Phase 8 Running — bitmap updates, input events, clipboard
15
+ *
16
+ * Emits:
17
+ * 'fbu' (rects: RdpRect[]) — one or more bitmap rectangles
18
+ * 'cursor' ({ hotX, hotY, width, height, rgba: string }) — cursor shape update
19
+ * 'clipboard' (text: string) — remote clipboard changed
20
+ * 'error' (err: Error) — unrecoverable error
21
+ * 'close' () — TCP/TLS connection closed
22
+ */
23
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
24
+ if (k2 === undefined) k2 = k;
25
+ var desc = Object.getOwnPropertyDescriptor(m, k);
26
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
27
+ desc = { enumerable: true, get: function() { return m[k]; } };
28
+ }
29
+ Object.defineProperty(o, k2, desc);
30
+ }) : (function(o, m, k, k2) {
31
+ if (k2 === undefined) k2 = k;
32
+ o[k2] = m[k];
33
+ }));
34
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
35
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
36
+ }) : function(o, v) {
37
+ o["default"] = v;
38
+ });
39
+ var __importStar = (this && this.__importStar) || (function () {
40
+ var ownKeys = function(o) {
41
+ ownKeys = Object.getOwnPropertyNames || function (o) {
42
+ var ar = [];
43
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
44
+ return ar;
45
+ };
46
+ return ownKeys(o);
47
+ };
48
+ return function (mod) {
49
+ if (mod && mod.__esModule) return mod;
50
+ var result = {};
51
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
52
+ __setModuleDefault(result, mod);
53
+ return result;
54
+ };
55
+ })();
56
+ Object.defineProperty(exports, "__esModule", { value: true });
57
+ exports.RdpBridge = void 0;
58
+ const net = __importStar(require("net"));
59
+ const crypto = __importStar(require("crypto"));
60
+ const events_1 = require("events");
61
+ const auth_1 = require("./auth");
62
+ const constants_1 = require("./constants");
63
+ // ── TPKT helpers ───────────────────────────────────────────────────────────
64
+ function wrapTpkt(payload) {
65
+ const out = Buffer.alloc(4 + payload.length);
66
+ out[0] = 0x03; // TPKT version
67
+ out[1] = 0x00;
68
+ out.writeUInt16BE(4 + payload.length, 2);
69
+ payload.copy(out, 4);
70
+ return out;
71
+ }
72
+ function wrapX224Data(payload) {
73
+ // X.224 Data TPDU: LI(1)=2, code(1)=0xF0, EOT(1)=0x80
74
+ const header = Buffer.from([0x02, constants_1.X224_TPDU_DATA, 0x80]);
75
+ return Buffer.concat([header, payload]);
76
+ }
77
+ function wrapMcsSend(userId, channelId, payload) {
78
+ // MCS SendDataRequest BER encoding (simplified):
79
+ // opcode(1) | initiator(2) | channelId(2) | dataPriority+segmentation(1) | length(variable) | data
80
+ const dataLen = payload.length;
81
+ let lenBuf;
82
+ if (dataLen < 128) {
83
+ lenBuf = Buffer.from([dataLen]);
84
+ }
85
+ else if (dataLen < 0x4000) {
86
+ lenBuf = Buffer.from([0x80 | (dataLen >> 8), dataLen & 0xff]);
87
+ }
88
+ else {
89
+ lenBuf = Buffer.from([0x82, (dataLen >> 16) & 0xff, (dataLen >> 8) & 0xff, dataLen & 0xff]);
90
+ }
91
+ const header = Buffer.alloc(6);
92
+ header[0] = constants_1.MCS_SEND_DATA_REQUEST;
93
+ header.writeUInt16BE(userId - 1001, 1); // initiator (offset from base)
94
+ header.writeUInt16BE(channelId, 3);
95
+ header[5] = 0x70; // high priority + end of segmentation
96
+ return Buffer.concat([header, lenBuf, payload]);
97
+ }
98
+ // ── GCC / MCS Connect helpers ─────────────────────────────────────────────
99
+ /** Encode a BER length field. */
100
+ function berLen(len) {
101
+ if (len < 128)
102
+ return Buffer.from([len]);
103
+ if (len < 256)
104
+ return Buffer.from([0x81, len]);
105
+ return Buffer.from([0x82, len >> 8, len & 0xff]);
106
+ }
107
+ /** Wrap `content` in a BER TLV. `tag` may be 1 or 2 bytes (e.g. 0x7f65). */
108
+ function berTlv(tag, content) {
109
+ const tagBuf = tag > 0xff
110
+ ? Buffer.from([tag >> 8, tag & 0xff])
111
+ : Buffer.from([tag]);
112
+ return Buffer.concat([tagBuf, berLen(content.length), content]);
113
+ }
114
+ /** BER-encode a small non-negative INTEGER. */
115
+ function berInt(v) {
116
+ if (v === 0)
117
+ return Buffer.from([0x02, 0x01, 0x00]);
118
+ if (v <= 0x7f)
119
+ return Buffer.from([0x02, 0x01, v]);
120
+ if (v <= 0x7fff)
121
+ return Buffer.from([0x02, 0x02, v >> 8, v & 0xff]);
122
+ return Buffer.from([0x02, 0x03, (v >> 16) & 0xff, (v >> 8) & 0xff, v & 0xff]);
123
+ }
124
+ /** Build one MCS DomainParameters SEQUENCE. */
125
+ function berDomainParams(maxChannelIds, maxUserIds, maxTokenIds, numPriorities, minThroughput, maxHeight, maxMCSPDU, protocolVersion) {
126
+ const body = Buffer.concat([
127
+ berInt(maxChannelIds), berInt(maxUserIds), berInt(maxTokenIds),
128
+ berInt(numPriorities), berInt(minThroughput), berInt(maxHeight),
129
+ berInt(maxMCSPDU), berInt(protocolVersion),
130
+ ]);
131
+ return berTlv(0x30, body); // SEQUENCE
132
+ }
133
+ function buildMcsConnectInitial(width, height, colorDepth) {
134
+ // ── GCC Conference Create Request (client core data only) ─────────────────
135
+ // Full spec: [MS-RDPBCGR] section 2.2.1.3
136
+ const rdpVersion = 0x00080004; // RDP 5.0+
137
+ // Client Core Data (CS_CORE): type=0xC001, length=216 bytes
138
+ const csCore = Buffer.alloc(216);
139
+ csCore.writeUInt16LE(0xC001, 0); // CS_CORE
140
+ csCore.writeUInt16LE(216, 2); // length
141
+ csCore.writeUInt32LE(rdpVersion, 4);
142
+ csCore.writeUInt16LE(width, 8);
143
+ csCore.writeUInt16LE(height, 10);
144
+ csCore.writeUInt16LE(0xCA01, 12); // colorDepth = 8bpp (negotiated later)
145
+ csCore.writeUInt16LE(0xAA03, 14); // SASSequence
146
+ csCore.writeUInt32LE(0x0409, 16); // keyboardLayout (English US)
147
+ csCore.writeUInt32LE(2600, 20); // clientBuild
148
+ Buffer.from('autodev', 'utf16le').copy(csCore, 24); // clientName (32 bytes)
149
+ csCore.writeUInt32LE(0x00000004, 56); // keyboardType = IBM enhanced
150
+ csCore.writeUInt32LE(0x00000000, 60); // keyboardSubType
151
+ csCore.writeUInt32LE(12, 64); // keyboardFunctionKey
152
+ csCore.writeUInt16LE(0xCA01, 130); // postBeta2ColorDepth
153
+ csCore.writeUInt16LE(1, 132); // clientProductId
154
+ csCore.writeUInt32LE(0, 134); // serialNumber
155
+ const hcd = colorDepth >= 24 ? 24 : colorDepth >= 16 ? 16 : colorDepth >= 15 ? 15 : 8;
156
+ csCore.writeUInt16LE(hcd, 138);
157
+ csCore.writeUInt16LE(0x0007, 140); // supportedColorDepths: 15|16|24bpp
158
+ csCore.writeUInt16LE(0x0001, 142); // earlyCapabilityFlags: ERRINFO_PDU
159
+ csCore.writeUInt8(0, 208); // connectionType
160
+ csCore.writeUInt8(0, 209); // pad1Octet
161
+ csCore.writeUInt32LE(0x00000001 /* PROTOCOL_SSL */, 210); // serverSelectedProtocol
162
+ // Client Security Data (CS_SECURITY): no encryption
163
+ const csSec = Buffer.alloc(12);
164
+ csSec.writeUInt16LE(0xC002, 0);
165
+ csSec.writeUInt16LE(12, 2);
166
+ csSec.writeUInt32LE(constants_1.ENCRYPTION_METHOD_NONE, 4);
167
+ csSec.writeUInt32LE(constants_1.ENCRYPTION_LEVEL_NONE, 8);
168
+ // Client Cluster Data (CS_CLUSTER)
169
+ const csCluster = Buffer.alloc(12);
170
+ csCluster.writeUInt16LE(0xC004, 0);
171
+ csCluster.writeUInt16LE(12, 2);
172
+ csCluster.writeUInt32LE(0x0000000D, 4); // REDIRECTION_SUPPORTED
173
+ csCluster.writeUInt32LE(0, 8);
174
+ const clientData = Buffer.concat([csCore, csSec, csCluster]);
175
+ // GCC ConferenceCreateRequest — T.124 PER-encoded wrapper
176
+ // [MS-RDPBCGR] 2.2.1.3 — the ConnectGCCPDU body must carry the proper
177
+ // T.124 ConferenceCreateRequest structure before the H.221/Duca client data.
178
+ // Without this structure xrdp cannot extract the screen dimensions and aborts
179
+ // with "xrdp_bitmap_create: size overflow 0x0x4" after the FontMap handshake.
180
+ function perLen(len) {
181
+ // PER unconstrained length: 1 byte for 0-127, 2 bytes for 128-16383
182
+ if (len <= 0x7F)
183
+ return Buffer.from([len]);
184
+ return Buffer.from([0x80 | (len >> 8), len & 0xFF]);
185
+ }
186
+ // ConnectGCCPDU body: choice(1) + conference-name(5) + optFlags(2) + "Duca"(4)
187
+ // + perLen(clientData) + clientData
188
+ const ccrBody = Buffer.concat([
189
+ Buffer.from([
190
+ 0x00, // choice: conferenceCreateRequest
191
+ 0x08, 0x00, 0x10, 0x00, 0x01, // conference name PER
192
+ 0xC0, 0x00, // optional fields: userData present
193
+ 0x44, 0x75, 0x63, 0x61, // H.221 non-standard key "Duca"
194
+ ]),
195
+ perLen(clientData.length), // PER length of client data blocks
196
+ clientData,
197
+ ]);
198
+ // T.124 ConnectData: H.221 OID key + PER length of CCR body + CCR body
199
+ const gccKey = Buffer.from([0x00, 0x05, 0x00, 0x14, 0x7c, 0x00, 0x01]);
200
+ const gccConnReq = Buffer.concat([gccKey, perLen(ccrBody.length), ccrBody]);
201
+ // MCS Connect-Initial BER encoding [MS-RDPBCGR] 2.2.1.3 / T.125
202
+ //
203
+ // ConnectInitial ::= [APPLICATION 101] IMPLICIT SEQUENCE {
204
+ // callingDomainSelector OCTET STRING,
205
+ // calledDomainSelector OCTET STRING,
206
+ // upwardFlag BOOLEAN,
207
+ // targetParameters DomainParameters,
208
+ // minimumParameters DomainParameters,
209
+ // maximumParameters DomainParameters,
210
+ // userData OCTET STRING ← contains GCC data
211
+ // }
212
+ const body = Buffer.concat([
213
+ berTlv(0x04, Buffer.from([0x01])), // callingDomainSelector
214
+ berTlv(0x04, Buffer.from([0x01])), // calledDomainSelector
215
+ Buffer.from([0x01, 0x01, 0xff]), // upwardFlag BOOLEAN TRUE
216
+ berDomainParams(34, 2, 0, 1, 0, 1, 65535, 2), // target
217
+ berDomainParams(1, 1, 1, 1, 0, 1, 1056, 2), // minimum
218
+ berDomainParams(65535, 64535, 65535, 1, 0, 1, 65535, 2), // maximum
219
+ berTlv(0x04, gccConnReq), // userData
220
+ ]);
221
+ // Wrap in APPLICATION 101 CONSTRUCTED = 0x7f 0x65
222
+ return berTlv(0x7f65, body);
223
+ }
224
+ // ── License crypto helpers ────────────────────────────────────────────────
225
+ /** Modular exponentiation using BigInt (for pure-JS RSA). */
226
+ function modpow(base, exp, mod) {
227
+ let result = 1n;
228
+ base = base % mod;
229
+ while (exp > 0n) {
230
+ if (exp & 1n)
231
+ result = (result * base) % mod;
232
+ exp >>= 1n;
233
+ base = (base * base) % mod;
234
+ }
235
+ return result;
236
+ }
237
+ /**
238
+ * RSA PKCS#1 v1.5 encrypt `message` using an RDP little-endian modulus.
239
+ * Returns the ciphertext in little-endian (as RDP expects).
240
+ */
241
+ function rsaEncryptLe(message, modulusLe, exponent) {
242
+ const modulusBe = Buffer.from(modulusLe).reverse();
243
+ const n = BigInt('0x' + modulusBe.toString('hex'));
244
+ const ks = modulusLe.length;
245
+ const padLen = ks - message.length - 3;
246
+ const padBytes = crypto.randomBytes(padLen).map(b => b === 0 ? 1 : b);
247
+ const padded = Buffer.concat([Buffer.from([0x00, 0x02]), padBytes, Buffer.from([0x00]), message]);
248
+ const m = BigInt('0x' + padded.toString('hex'));
249
+ const c = modpow(m, BigInt(exponent), n);
250
+ const cHex = c.toString(16).padStart(ks * 2, '0');
251
+ return Buffer.from(Buffer.from(cHex, 'hex')).reverse(); // LE
252
+ }
253
+ /** MS-RDPELE §5.1.3 salted hash: SHA1 + MD5 mix. */
254
+ function saltedHash(secret, label, r1, r2) {
255
+ const sha = crypto.createHash('sha1');
256
+ sha.update(label);
257
+ sha.update(secret);
258
+ sha.update(r1);
259
+ sha.update(r2);
260
+ const md5 = crypto.createHash('md5');
261
+ md5.update(secret);
262
+ md5.update(sha.digest());
263
+ return md5.digest();
264
+ }
265
+ /** Derive MAC key (16 B) and enc key (16 B) from pre-master secret. */
266
+ function deriveLicenseKeys(preMaster, clientRand, serverRand) {
267
+ let ms = Buffer.concat([
268
+ saltedHash(preMaster, Buffer.from('A'), clientRand, serverRand),
269
+ saltedHash(preMaster, Buffer.from('BB'), clientRand, serverRand),
270
+ saltedHash(preMaster, Buffer.from('CCC'), clientRand, serverRand),
271
+ ]);
272
+ const skb = Buffer.concat([
273
+ saltedHash(ms, Buffer.from('A'), serverRand, clientRand),
274
+ saltedHash(ms, Buffer.from('BB'), serverRand, clientRand),
275
+ saltedHash(ms, Buffer.from('CCC'), serverRand, clientRand),
276
+ ]);
277
+ return [skb.slice(0, 16), skb.slice(16, 32)]; // [mac_key, enc_key]
278
+ }
279
+ function macData(macKey, data) {
280
+ const p1 = Buffer.alloc(40).fill(0x36);
281
+ const p2 = Buffer.alloc(48).fill(0x5c);
282
+ const sha = crypto.createHash('sha1');
283
+ sha.update(macKey);
284
+ sha.update(p1);
285
+ const lenBuf = Buffer.alloc(4);
286
+ lenBuf.writeUInt32LE(data.length, 0);
287
+ sha.update(lenBuf);
288
+ sha.update(data);
289
+ const md5 = crypto.createHash('md5');
290
+ md5.update(macKey);
291
+ md5.update(p2);
292
+ md5.update(sha.digest());
293
+ return md5.digest().slice(0, 16);
294
+ }
295
+ function rc4(key, data) {
296
+ const cipher = crypto.createDecipheriv('rc4', key, null);
297
+ return Buffer.concat([cipher.update(data), cipher.final()]);
298
+ }
299
+ /**
300
+ * Parse SERVER_LICENSE_REQUEST to extract serverRandom, RSA modulus (LE) and exponent.
301
+ * `body` starts at the license preamble (4 bytes before ServerRandom).
302
+ */
303
+ function parseLicenseRequest(body) {
304
+ // body[0..3] = preamble; body[4..35] = ServerRandom[32]
305
+ const serverRand = body.slice(4, 36);
306
+ let pos = 36;
307
+ // ProductInfo: dwVersion(4) + cbCompanyName(4) + name + cbProductId(4) + id
308
+ const cbCompany = body.readUInt32LE(pos + 4);
309
+ pos += 4 + 4 + cbCompany;
310
+ const cbProduct = body.readUInt32LE(pos);
311
+ pos += 4 + cbProduct;
312
+ // KeyExchangeList blob: wBlobType(2)+wBlobLen(2)+data
313
+ const kbLen = body.readUInt16LE(pos + 2);
314
+ pos += 4 + kbLen;
315
+ // ServerCertificate blob
316
+ const certLen = body.readUInt16LE(pos + 2);
317
+ const cert = body.slice(pos + 4, pos + 4 + certLen);
318
+ // PROPRIETARYSERVERCERTIFICATE: dwVersion(4)+dwSigAlgId(4)+dwKeyAlgId(4)+wBlobType(2)+wBlobLen(2)+RSA_PUBLIC_KEY
319
+ const pkBlobLen = cert.readUInt16LE(14);
320
+ const pk = cert.slice(16, 16 + pkBlobLen); // RSA_PUBLIC_KEY
321
+ // RSA1(4)+keylen(4)+bitlen(4)+datalen(4)+pubExp(4)+modulus(keylen)
322
+ const keylen = pk.readUInt32LE(4);
323
+ const bitlen = pk.readUInt32LE(8);
324
+ const exponent = pk.readUInt32LE(16);
325
+ const modulusLe = pk.slice(20, 20 + bitlen / 8);
326
+ return { serverRand, modulusLe, exponent };
327
+ }
328
+ function buildNewLicenseRequest(clientRand, encPms, username, machine) {
329
+ const un = Buffer.from(username + '\0', 'ascii');
330
+ const mn = Buffer.from(machine + '\0', 'ascii');
331
+ const body = Buffer.alloc(4 + 4 + 32 + 4 + encPms.length + 4 + un.length + 4 + mn.length);
332
+ let off = 0;
333
+ body.writeUInt32LE(1, off);
334
+ off += 4; // KEY_EXCHANGE_ALG_RSA
335
+ body.writeUInt32LE(0x04000000, off);
336
+ off += 4; // PlatformId
337
+ clientRand.copy(body, off);
338
+ off += 32;
339
+ body.writeUInt16LE(0x0006, off);
340
+ off += 2; // wBlobType = BB_KEY_EXCHG_ALG_BLOB
341
+ body.writeUInt16LE(encPms.length, off);
342
+ off += 2;
343
+ encPms.copy(body, off);
344
+ off += encPms.length;
345
+ body.writeUInt16LE(0x000f, off);
346
+ off += 2; // wBlobType = BB_CLIENT_USER_NAME_BLOB
347
+ body.writeUInt16LE(un.length, off);
348
+ off += 2;
349
+ un.copy(body, off);
350
+ off += un.length;
351
+ body.writeUInt16LE(0x0010, off);
352
+ off += 2; // wBlobType = BB_CLIENT_MACHINE_NAME_BLOB
353
+ body.writeUInt16LE(mn.length, off);
354
+ off += 2;
355
+ mn.copy(body, off);
356
+ const preamble = Buffer.alloc(4);
357
+ preamble.writeUInt8(0x13, 0);
358
+ preamble.writeUInt8(0x00, 1);
359
+ preamble.writeUInt16LE(4 + body.length, 2);
360
+ return Buffer.concat([preamble, body]);
361
+ }
362
+ function buildPlatformChallengeResponse(encKey, macKey, challengePlain) {
363
+ const hwid = Buffer.alloc(8);
364
+ hwid.writeUInt32LE(2, 0); // hardwareIDVersion=2, hardwareID1=0
365
+ const encChallenge = rc4(encKey, challengePlain);
366
+ const encHwid = rc4(encKey, hwid);
367
+ const mac = macData(macKey, Buffer.concat([challengePlain, hwid]));
368
+ const encChalLen = Buffer.alloc(4);
369
+ encChalLen.writeUInt16LE(0x000e, 0);
370
+ encChalLen.writeUInt16LE(encChallenge.length, 2);
371
+ const encHwidLen = Buffer.alloc(4);
372
+ encHwidLen.writeUInt16LE(0x000f, 0);
373
+ encHwidLen.writeUInt16LE(encHwid.length, 2);
374
+ const body = Buffer.concat([encChalLen, encChallenge, encHwidLen, encHwid, mac]);
375
+ const preamble = Buffer.alloc(4);
376
+ preamble.writeUInt8(0x15, 0);
377
+ preamble.writeUInt8(0x80, 1);
378
+ preamble.writeUInt16LE(4 + body.length, 2);
379
+ return Buffer.concat([preamble, body]);
380
+ }
381
+ // ── Client Info PDU ───────────────────────────────────────────────────────
382
+ function buildClientInfoPdu(username, password, domain) {
383
+ // [MS-RDPBCGR] 2.2.1.11 INFO_PACKET
384
+ const INFO_MOUSE = 0x00000001;
385
+ const INFO_DISABLECTRLALTDEL = 0x00000002;
386
+ const INFO_AUTOLOGON = 0x00000008;
387
+ const INFO_UNICODE = 0x00000010;
388
+ const INFO_MAXIMIZESHELL = 0x00000020;
389
+ const INFO_COMPRESSION = 0x00000080;
390
+ const INFO_ENABLEWINDOWSKEY = 0x00000100;
391
+ const encU = (s) => Buffer.from(s + '\0', 'utf16le');
392
+ const domainBuf = encU(domain);
393
+ const usernameBuf = encU(username);
394
+ const passwordBuf = encU(password);
395
+ const shellBuf = encU('');
396
+ const workdirBuf = encU('');
397
+ const flags = INFO_MOUSE | INFO_DISABLECTRLALTDEL | INFO_AUTOLOGON |
398
+ INFO_UNICODE | INFO_MAXIMIZESHELL | INFO_COMPRESSION |
399
+ INFO_ENABLEWINDOWSKEY;
400
+ const hdr = Buffer.alloc(18);
401
+ hdr.writeUInt32LE(0, 0); // codePage
402
+ hdr.writeUInt32LE(flags, 4);
403
+ hdr.writeUInt16LE(domainBuf.length - 2, 8); // cbDomain (excluding null)
404
+ hdr.writeUInt16LE(usernameBuf.length - 2, 10); // cbUserName
405
+ hdr.writeUInt16LE(passwordBuf.length - 2, 12); // cbPassword
406
+ hdr.writeUInt16LE(shellBuf.length - 2, 14); // cbAlternateShell
407
+ hdr.writeUInt16LE(workdirBuf.length - 2, 16); // cbWorkingDir
408
+ // TS_EXTENDED_INFO_PACKET (required by RDP 5.0+)
409
+ // clientAddressFamily(2) + cbClientAddress(2) + clientAddress(2) +
410
+ // cbClientDir(2) + clientDir(2) + clientTimeZone(172) +
411
+ // clientSessionId(4) + performanceFlags(4) + cbAutoReconnectCookie(2)
412
+ const ext = Buffer.alloc(2 + 2 + 2 + 2 + 2 + 172 + 4 + 4 + 2);
413
+ ext.writeUInt16LE(0x0002, 0); // clientAddressFamily = AF_INET
414
+ ext.writeUInt16LE(2, 2); // cbClientAddress = 2 (null-term only)
415
+ // clientAddress[2] = 0x0000 (already zero)
416
+ ext.writeUInt16LE(2, 6); // cbClientDir = 2 (null-term only)
417
+ // clientDir[2] = 0x0000 (already zero)
418
+ // clientTimeZone[172] = all zeros (UTC)
419
+ // clientSessionId, performanceFlags, cbAutoReconnectCookie = 0
420
+ const info = Buffer.concat([hdr, domainBuf, usernameBuf, passwordBuf, shellBuf, workdirBuf, ext]);
421
+ // Prepend SEC_INFO_PKT Security Header (flags only — no encryption)
422
+ const secHdr = Buffer.alloc(4);
423
+ secHdr.writeUInt16LE(constants_1.SEC_LOGON_INFO, 0);
424
+ secHdr.writeUInt16LE(0, 2); // flagsHi
425
+ return Buffer.concat([secHdr, info]);
426
+ }
427
+ // ── RDP Capabilities ──────────────────────────────────────────────────────
428
+ function buildConfirmActivePdu(userId, shareId, width, height) {
429
+ // ShareControlHeader
430
+ const numCaps = 13;
431
+ // Build minimal capability set
432
+ const caps = [];
433
+ // CAPSTYPE_GENERAL (0x0001)
434
+ const genCap = Buffer.alloc(24);
435
+ genCap.writeUInt16LE(0x0001, 0);
436
+ genCap.writeUInt16LE(24, 2);
437
+ genCap.writeUInt16LE(1, 4); // osMajorType = Windows
438
+ genCap.writeUInt16LE(3, 6); // osMinorType = Windows NT
439
+ genCap.writeUInt16LE(0x0200, 8); // protocolVersion
440
+ genCap.writeUInt16LE(0x0000, 12); // extraFlags: no fast-path (force slow-path TPKT updates)
441
+ genCap.writeUInt16LE(2, 20); // refreshRectSupport
442
+ genCap.writeUInt16LE(2, 22); // suppressOutputSupport
443
+ caps.push(genCap);
444
+ // CAPSTYPE_BITMAP (0x0002)
445
+ const bitmapCap = Buffer.alloc(28);
446
+ bitmapCap.writeUInt16LE(0x0002, 0);
447
+ bitmapCap.writeUInt16LE(28, 2);
448
+ bitmapCap.writeUInt16LE(24, 4); // preferredBitsPerPixel
449
+ bitmapCap.writeUInt16LE(1, 6); // receive1BitPerPixel
450
+ bitmapCap.writeUInt16LE(1, 8); // receive4BitsPerPixel
451
+ bitmapCap.writeUInt16LE(1, 10); // receive8BitsPerPixel
452
+ bitmapCap.writeUInt16LE(width, 12);
453
+ bitmapCap.writeUInt16LE(height, 14);
454
+ bitmapCap.writeUInt16LE(0, 16); // pad
455
+ bitmapCap.writeUInt16LE(1, 18); // desktopResizeFlag
456
+ bitmapCap.writeUInt16LE(1, 20); // bitmapCompressionFlag
457
+ bitmapCap.writeUInt8(0, 22); // highColorFlags
458
+ bitmapCap.writeUInt8(0, 23); // drawingFlags
459
+ bitmapCap.writeUInt16LE(1, 24); // multipleRectangleSupport
460
+ bitmapCap.writeUInt16LE(0, 26); // pad
461
+ caps.push(bitmapCap);
462
+ // CAPSTYPE_ORDER (0x0003) — minimal, no complex orders
463
+ const orderCap = Buffer.alloc(88);
464
+ orderCap.writeUInt16LE(0x0003, 0);
465
+ orderCap.writeUInt16LE(88, 2);
466
+ // terminalDescriptor: 16 bytes (zero)
467
+ orderCap.writeUInt32LE(0, 20); // pad4OctetsA
468
+ orderCap.writeUInt16LE(1, 24); // desktopSaveXGranularity
469
+ orderCap.writeUInt16LE(20, 26); // desktopSaveYGranularity
470
+ orderCap.writeUInt16LE(0, 28); // pad2OctetsA
471
+ orderCap.writeUInt16LE(1, 30); // maximumOrderLevel
472
+ orderCap.writeUInt16LE(0, 32); // numberFonts
473
+ orderCap.writeUInt16LE(0x0022, 34); // orderFlags: NEGOTIATEORDERSUPPORT | ZEROBOUNDSDELTASSUPPORT
474
+ // orderSupport: 32 bytes (all zero — no drawing orders)
475
+ orderCap.writeUInt16LE(0, 68); // textFlags
476
+ orderCap.writeUInt16LE(0, 70); // orderSupportExFlags
477
+ orderCap.writeUInt32LE(0, 72); // pad4OctetsB
478
+ orderCap.writeUInt32LE(230400, 76); // desktopSaveSize
479
+ orderCap.writeUInt16LE(0, 80); // pad2OctetsC
480
+ orderCap.writeUInt16LE(0, 82); // pad2OctetsD
481
+ orderCap.writeUInt16LE(0x006e, 84); // textANSICodePage
482
+ orderCap.writeUInt16LE(0, 86); // pad2OctetsE
483
+ caps.push(orderCap);
484
+ // CAPSTYPE_INPUT (0x000d)
485
+ const inputCap = Buffer.alloc(88);
486
+ inputCap.writeUInt16LE(0x000d, 0);
487
+ inputCap.writeUInt16LE(88, 2);
488
+ inputCap.writeUInt16LE(0x0001 | 0x0004 | 0x0020, 4); // INPUT_FLAG_SCANCODES | MOUSEX | UNICODE
489
+ inputCap.writeUInt16LE(0, 6); // pad
490
+ inputCap.writeUInt32LE(0x0409, 8); // keyboardLayout
491
+ inputCap.writeUInt32LE(4, 12); // keyboardType
492
+ inputCap.writeUInt32LE(0, 16); // keyboardSubType
493
+ inputCap.writeUInt32LE(12, 20); // keyboardFunctionKey
494
+ caps.push(inputCap);
495
+ // CAPSTYPE_POINTER (0x0008)
496
+ const ptrCap = Buffer.alloc(10);
497
+ ptrCap.writeUInt16LE(0x0008, 0);
498
+ ptrCap.writeUInt16LE(10, 2);
499
+ ptrCap.writeUInt16LE(0, 4); // colorPointerFlag
500
+ ptrCap.writeUInt16LE(20, 6); // colorPointerCacheSize
501
+ ptrCap.writeUInt16LE(21, 8); // pointerCacheSize
502
+ caps.push(ptrCap);
503
+ // CAPSTYPE_SHARE (0x0009)
504
+ const shareCap = Buffer.alloc(8);
505
+ shareCap.writeUInt16LE(0x0009, 0);
506
+ shareCap.writeUInt16LE(8, 2);
507
+ shareCap.writeUInt16LE(0, 4); // nodeId (client = 0)
508
+ shareCap.writeUInt16LE(0, 6); // pad
509
+ caps.push(shareCap);
510
+ // CAPSTYPE_COLORCACHE (0x000a)
511
+ const ccCap = Buffer.alloc(8);
512
+ ccCap.writeUInt16LE(0x000a, 0);
513
+ ccCap.writeUInt16LE(8, 2);
514
+ ccCap.writeUInt16LE(6, 4);
515
+ ccCap.writeUInt16LE(0, 6);
516
+ caps.push(ccCap);
517
+ // CAPSTYPE_CONTROL (0x0005)
518
+ const ctrlCap = Buffer.alloc(12);
519
+ ctrlCap.writeUInt16LE(0x0005, 0);
520
+ ctrlCap.writeUInt16LE(12, 2);
521
+ caps.push(ctrlCap);
522
+ // CAPSTYPE_ACTIVATION (0x0007)
523
+ const actCap = Buffer.alloc(12);
524
+ actCap.writeUInt16LE(0x0007, 0);
525
+ actCap.writeUInt16LE(12, 2);
526
+ caps.push(actCap);
527
+ // CAPSTYPE_FONT (0x000e)
528
+ const fontCap = Buffer.alloc(8);
529
+ fontCap.writeUInt16LE(0x000e, 0);
530
+ fontCap.writeUInt16LE(8, 2);
531
+ fontCap.writeUInt16LE(1, 4); // fontSupportFlags: FONTSUPPORT_FONTLIST
532
+ caps.push(fontCap);
533
+ // CAPSTYPE_BITMAPCACHE (0x0004)
534
+ const bmpCache = Buffer.alloc(40);
535
+ bmpCache.writeUInt16LE(0x0004, 0);
536
+ bmpCache.writeUInt16LE(40, 2);
537
+ caps.push(bmpCache);
538
+ // CAPSTYPE_BRUSHSUPPORT (0x000f)
539
+ const brushCap = Buffer.alloc(8);
540
+ brushCap.writeUInt16LE(0x000f, 0);
541
+ brushCap.writeUInt16LE(8, 2);
542
+ caps.push(brushCap);
543
+ // CAPSTYPE_GLYPHCACHE (0x0010)
544
+ const glyphCap = Buffer.alloc(52);
545
+ glyphCap.writeUInt16LE(0x0010, 0);
546
+ glyphCap.writeUInt16LE(52, 2);
547
+ glyphCap.writeUInt8(0, 50); // glyphSupportLevel = GLYPH_SUPPORT_NONE
548
+ caps.push(glyphCap);
549
+ const capsData = Buffer.concat(caps);
550
+ // ConfirmActive PDU body: shareId(4)+originatorId(2)+lengthSrcDesc(2)+lengthCombCaps(2)
551
+ // + sourceDescriptor(9) + numberCapabilities(2) + pad(2) = 23 bytes
552
+ const body = Buffer.alloc(23);
553
+ body.writeUInt32LE(shareId, 0);
554
+ body.writeUInt16LE(0x03EA, 4); // originatorId (client)
555
+ body.writeUInt16LE(9, 6); // lengthSourceDescriptor
556
+ body.writeUInt16LE(capsData.length + 4, 8); // lengthCombinedCapabilities (+4 for numCaps+pad)
557
+ Buffer.from('autodev\0\0', 'ascii').copy(body, 10); // sourceDescriptor (9 bytes, ends at 18)
558
+ body.writeUInt16LE(caps.length, 19); // numberCapabilities — immediately after sourceDescriptor
559
+ body.writeUInt16LE(0, 21); // pad2Octets
560
+ const pduBody = Buffer.concat([body, capsData]);
561
+ // Share Control Header
562
+ const shareCtrl = Buffer.alloc(6);
563
+ const pduLen = 6 + pduBody.length;
564
+ shareCtrl.writeUInt16LE(pduLen, 0);
565
+ shareCtrl.writeUInt16LE(constants_1.PDUTYPE_CONFIRMACTIVEPDU | 0x10, 2); // type | version
566
+ shareCtrl.writeUInt16LE(userId, 4); // PDUSource
567
+ return Buffer.concat([shareCtrl, pduBody]);
568
+ }
569
+ // ── Synchronize / Control / Font PDUs ────────────────────────────────────
570
+ function buildSynchronizePdu(userId, shareId) {
571
+ const hdr = buildShareDataHeader(userId, shareId, constants_1.PDUTYPE2_SYNCHRONIZE, 4);
572
+ const body = Buffer.alloc(4);
573
+ body.writeUInt16LE(1, 0); // messageType = SYNCMSGTYPE_SYNC
574
+ body.writeUInt16LE(1002, 2); // targetUser (MCS user ID)
575
+ return Buffer.concat([hdr, body]);
576
+ }
577
+ function buildControlPdu(userId, shareId, action) {
578
+ const hdr = buildShareDataHeader(userId, shareId, constants_1.PDUTYPE2_CONTROL, 8);
579
+ const body = Buffer.alloc(8);
580
+ body.writeUInt16LE(action, 0);
581
+ body.writeUInt16LE(0, 2);
582
+ body.writeUInt32LE(0, 4);
583
+ return Buffer.concat([hdr, body]);
584
+ }
585
+ function buildFontListPdu(userId, shareId) {
586
+ const hdr = buildShareDataHeader(userId, shareId, constants_1.PDUTYPE2_FONTLIST, 8);
587
+ const body = Buffer.alloc(8);
588
+ body.writeUInt16LE(0, 0); // numberFonts
589
+ body.writeUInt16LE(0, 2); // totalNumFonts
590
+ body.writeUInt16LE(0x0003, 4); // listFlags
591
+ body.writeUInt16LE(50, 6); // entrySize
592
+ return Buffer.concat([hdr, body]);
593
+ }
594
+ function buildShareDataHeader(userId, shareId, pduType2, dataLen) {
595
+ // Share Control Header (6) + Share Data Header (12) = 18 bytes
596
+ const totalBodyLen = dataLen;
597
+ const pduLen = 18 + totalBodyLen;
598
+ const hdr = Buffer.alloc(18);
599
+ // Share Control Header
600
+ hdr.writeUInt16LE(pduLen, 0);
601
+ hdr.writeUInt16LE(constants_1.PDUTYPE_DATAPDU | 0x10, 2); // type | RDP5 version
602
+ hdr.writeUInt16LE(userId, 4); // PDUSource
603
+ // Share Data Header (TS_SHAREDATAHEADER, 12 bytes at hdr[6..17])
604
+ hdr.writeUInt32LE(shareId, 6); // shareId
605
+ hdr[10] = 0; // pad1Octet
606
+ hdr[11] = 1; // streamId (STREAM_LOW)
607
+ hdr.writeUInt16LE(totalBodyLen, 12); // uncompressedLength
608
+ hdr[14] = pduType2; // pduType2
609
+ hdr[15] = 0; // compressType
610
+ hdr.writeUInt16LE(totalBodyLen, 16); // compressedLength
611
+ return hdr.slice(0, 18);
612
+ }
613
+ function buildRefreshRectPdu(userId, shareId, width, height) {
614
+ // TS_REFRESH_RECT_PDU: numberOfAreas(1) + pad(3) + InclusiveRect (left/top/right/bottom each uint16)
615
+ const body = Buffer.alloc(12);
616
+ body[0] = 1; // numberOfAreas = 1
617
+ body.writeUInt16LE(0, 4); // left
618
+ body.writeUInt16LE(0, 6); // top
619
+ body.writeUInt16LE(width - 1, 8); // right
620
+ body.writeUInt16LE(height - 1, 10); // bottom
621
+ const hdr = buildShareDataHeader(userId, shareId, constants_1.PDUTYPE2_REFRESH_RECT, body.length);
622
+ return Buffer.concat([hdr, body]);
623
+ }
624
+ function buildSuppressOutputPdu(userId, shareId, allow, width = 0, height = 0) {
625
+ // TS_SUPPRESS_OUTPUT_PDU: allowDisplayUpdates(1) + pad(3) [+ desktopRect(8) when allow=true]
626
+ const body = allow ? Buffer.alloc(12) : Buffer.alloc(4);
627
+ body[0] = allow ? 1 : 0;
628
+ if (allow) {
629
+ // pad3Octets[1..3] already zero
630
+ body.writeUInt16LE(0, 4); // left
631
+ body.writeUInt16LE(0, 6); // top
632
+ body.writeUInt16LE(width - 1, 8); // right
633
+ body.writeUInt16LE(height - 1, 10); // bottom
634
+ }
635
+ const hdr = buildShareDataHeader(userId, shareId, constants_1.PDUTYPE2_SUPPRESS_OUTPUT, body.length);
636
+ return Buffer.concat([hdr, body]);
637
+ }
638
+ // ── Bitmap decompression (RLE — MS-RDPEGDI 3.1.9) ─────────────────────────
639
+ function decompressBitmap(src, width, height, bytesPerPixel) {
640
+ const rowSize = width * bytesPerPixel;
641
+ const dst = Buffer.alloc(rowSize * height);
642
+ let si = 0;
643
+ let di = 0;
644
+ const readPixel = () => {
645
+ if (si + bytesPerPixel > src.length)
646
+ return Buffer.alloc(bytesPerPixel);
647
+ const p = src.slice(si, si + bytesPerPixel);
648
+ si += bytesPerPixel;
649
+ return p;
650
+ };
651
+ while (si < src.length && di < dst.length) {
652
+ if (si >= src.length)
653
+ break;
654
+ const code = src[si++];
655
+ const type = code >> 4;
656
+ let len = code & 0x0f;
657
+ if (len === 0) {
658
+ if (si >= src.length)
659
+ break;
660
+ len = src[si++] + 16;
661
+ }
662
+ if (type === 0x0) {
663
+ // FILL — copy from scanline above
664
+ for (let i = 0; i < len && di < dst.length; i++, di++) {
665
+ dst[di] = di >= rowSize ? dst[di - rowSize] : 0;
666
+ }
667
+ }
668
+ else if (type === 0x8) {
669
+ // MIX — XOR with previous scanline pixel
670
+ const pixel = readPixel();
671
+ for (let i = 0; i < len && di < dst.length; i++, di += bytesPerPixel) {
672
+ for (let b = 0; b < bytesPerPixel; b++) {
673
+ dst[di + b] = di >= rowSize ? dst[di - rowSize + b] ^ pixel[b] : pixel[b];
674
+ }
675
+ di -= bytesPerPixel;
676
+ di += bytesPerPixel;
677
+ }
678
+ }
679
+ else if (type === 0xc) {
680
+ // COLOR — repeat pixel
681
+ const pixel = readPixel();
682
+ for (let i = 0; i < len && di + bytesPerPixel <= dst.length; i++, di += bytesPerPixel) {
683
+ pixel.copy(dst, di);
684
+ }
685
+ }
686
+ else if (type === 0xf) {
687
+ // COPY — raw pixels
688
+ for (let i = 0; i < len && di + bytesPerPixel <= dst.length; i++, di += bytesPerPixel) {
689
+ readPixel().copy(dst, di);
690
+ }
691
+ }
692
+ else {
693
+ // Unknown / unhandled run type — skip
694
+ si += len * bytesPerPixel;
695
+ }
696
+ }
697
+ return dst;
698
+ }
699
+ // ── Convert various bit depths to RGBA ────────────────────────────────────
700
+ function toRgba(data, bytesPerPixel) {
701
+ if (bytesPerPixel === 4) {
702
+ // Assume BGRX (Windows native) → RGBA
703
+ const out = Buffer.alloc(data.length);
704
+ for (let i = 0; i < data.length; i += 4) {
705
+ out[i] = data[i + 2]; // R
706
+ out[i + 1] = data[i + 1]; // G
707
+ out[i + 2] = data[i]; // B
708
+ out[i + 3] = 0xff; // A
709
+ }
710
+ return out;
711
+ }
712
+ if (bytesPerPixel === 3) {
713
+ // BGR → RGBA
714
+ const pixels = Math.floor(data.length / 3);
715
+ const out = Buffer.alloc(pixels * 4);
716
+ for (let i = 0; i < pixels; i++) {
717
+ out[i * 4] = data[i * 3 + 2]; // R
718
+ out[i * 4 + 1] = data[i * 3 + 1]; // G
719
+ out[i * 4 + 2] = data[i * 3]; // B
720
+ out[i * 4 + 3] = 0xff;
721
+ }
722
+ return out;
723
+ }
724
+ if (bytesPerPixel === 2) {
725
+ // RGB565 → RGBA
726
+ const pixels = Math.floor(data.length / 2);
727
+ const out = Buffer.alloc(pixels * 4);
728
+ for (let i = 0; i < pixels; i++) {
729
+ const v = data.readUInt16LE(i * 2);
730
+ out[i * 4] = ((v >> 11) & 0x1f) << 3;
731
+ out[i * 4 + 1] = ((v >> 5) & 0x3f) << 2;
732
+ out[i * 4 + 2] = (v & 0x1f) << 3;
733
+ out[i * 4 + 3] = 0xff;
734
+ }
735
+ return out;
736
+ }
737
+ // 1 bpp / other — return as-is with alpha=0xff
738
+ const out = Buffer.alloc(data.length * 2);
739
+ for (let i = 0; i < data.length; i++) {
740
+ out[i * 2] = data[i];
741
+ out[i * 2 + 1] = 0xff;
742
+ }
743
+ return out;
744
+ }
745
+ // ── RdpBridge class ───────────────────────────────────────────────────────
746
+ class RdpBridge extends events_1.EventEmitter {
747
+ _sock = null;
748
+ _closed = false;
749
+ _recvBuf = Buffer.alloc(0);
750
+ _handshakeBuf = Buffer.alloc(0); // raw-byte accumulator for handshake TPKT parsing
751
+ _width = constants_1.RDP_DEFAULT_WIDTH;
752
+ _height = constants_1.RDP_DEFAULT_HEIGHT;
753
+ _colorDepth = constants_1.RDP_DEFAULT_COLOR_DEPTH;
754
+ _userId = 0;
755
+ _shareId = 0;
756
+ _connected = false;
757
+ // Auto-reconnect support (xrdp closes TCP after initial handshake, expects client to reconnect)
758
+ _opts = null;
759
+ _reconnectCount = 0;
760
+ static _MAX_RECONNECTS = 10;
761
+ /** Optional external logger — set before calling connect(). */
762
+ log = (msg) => console.error(msg);
763
+ get width() { return this._width; }
764
+ get height() { return this._height; }
765
+ // ── Public API ─────────────────────────────────────────────────────────
766
+ /**
767
+ * Connect to an RDP server. Resolves once the desktop is ready.
768
+ */
769
+ async connect(opts) {
770
+ this._opts = opts; // save for auto-reconnect
771
+ const host = opts.host;
772
+ const port = opts.port ?? constants_1.RDP_DEFAULT_PORT;
773
+ const username = opts.username ?? '';
774
+ const password = opts.password ?? '';
775
+ const domain = opts.domain ?? '';
776
+ const width = opts.width ?? constants_1.RDP_DEFAULT_WIDTH;
777
+ const height = opts.height ?? constants_1.RDP_DEFAULT_HEIGHT;
778
+ const colorDepth = opts.colorDepth ?? constants_1.RDP_DEFAULT_COLOR_DEPTH;
779
+ this._width = width;
780
+ this._height = height;
781
+ this._colorDepth = colorDepth;
782
+ return new Promise((resolve, reject) => {
783
+ // connectResolved: true once the initial handshake has resolved the Promise.
784
+ // The rawSock 'close' listener uses this guard so it doesn't emit 'close'
785
+ // after the TLS socket takes over (the TLS 'close' handler manages reconnect).
786
+ let connectResolved = false;
787
+ const sock = net.createConnection({ host, port });
788
+ this._sock = sock;
789
+ sock.once('error', (err) => {
790
+ if (!this._closed) {
791
+ this._closed = true;
792
+ this.emit('error', err);
793
+ reject(err);
794
+ }
795
+ });
796
+ sock.once('close', () => {
797
+ // Only fire if the Promise never resolved (pre-Phase-8 failure on raw socket)
798
+ if (!connectResolved && !this._closed) {
799
+ this._closed = true;
800
+ this.emit('close');
801
+ }
802
+ });
803
+ sock.once('connect', async () => {
804
+ try {
805
+ await this._handshake(sock, host, port, username, password, domain, width, height, colorDepth, (info) => { connectResolved = true; resolve(info); }, reject);
806
+ }
807
+ catch (err) {
808
+ reject(err instanceof Error ? err : new Error(String(err)));
809
+ sock.destroy();
810
+ }
811
+ });
812
+ });
813
+ }
814
+ /** Send a mouse event. buttonMask: bit0=left, bit1=right, bit2=middle */
815
+ sendMouse(x, y, buttonMask) {
816
+ if (!this._userId || !this._connected)
817
+ return;
818
+ let flags = constants_1.PTRFLAGS_MOVE;
819
+ if (buttonMask & 1)
820
+ flags |= constants_1.PTRFLAGS_BUTTON1 | constants_1.PTRFLAGS_DOWN;
821
+ if (buttonMask & 2)
822
+ flags |= constants_1.PTRFLAGS_BUTTON2 | constants_1.PTRFLAGS_DOWN;
823
+ if (buttonMask & 4)
824
+ flags |= constants_1.PTRFLAGS_BUTTON3 | constants_1.PTRFLAGS_DOWN;
825
+ this._sendInput(constants_1.INPUT_EVENT_MOUSE, Buffer.from([
826
+ flags & 0xff, (flags >> 8) & 0xff,
827
+ x & 0xff, (x >> 8) & 0xff,
828
+ y & 0xff, (y >> 8) & 0xff,
829
+ ]));
830
+ }
831
+ /** Send a key event. jsKeyCode is the browser keyCode value. */
832
+ sendKey(jsKeyCode, down) {
833
+ if (!this._userId || !this._connected)
834
+ return;
835
+ const scanCode = constants_1.RDP_SCANCODE[jsKeyCode] ?? jsKeyCode;
836
+ const isExt = constants_1.RDP_EXTENDED_KEYS.has(jsKeyCode);
837
+ let flags = down ? 0 : constants_1.KBDFLAGS_RELEASE;
838
+ if (isExt)
839
+ flags |= constants_1.KBDFLAGS_EXTENDED;
840
+ this._sendInput(constants_1.INPUT_EVENT_SCANCODE, Buffer.from([
841
+ flags & 0xff, (flags >> 8) & 0xff,
842
+ scanCode & 0xff, (scanCode >> 8) & 0xff,
843
+ 0, 0,
844
+ ]));
845
+ }
846
+ /** Push local clipboard text to the remote. */
847
+ sendClipboardText(text) {
848
+ // Clipboard redirection requires the cliprdr channel (Phase 2).
849
+ // For now, log silently — no-op until channel join is implemented.
850
+ void text;
851
+ }
852
+ close() {
853
+ if (!this._closed) {
854
+ this._closed = true;
855
+ this._sock?.destroy();
856
+ this._sock = null;
857
+ }
858
+ }
859
+ // ── Auto-reconnect ─────────────────────────────────────────────────────
860
+ /**
861
+ * Reconnect using saved opts — called after xrdp's clean TCP close that
862
+ * follows the initial session-creation handshake.
863
+ */
864
+ _doReconnect() {
865
+ if (this._closed || !this._opts)
866
+ return;
867
+ const opts = this._opts;
868
+ const host = opts.host;
869
+ const port = opts.port ?? constants_1.RDP_DEFAULT_PORT;
870
+ const username = opts.username ?? '';
871
+ const password = opts.password ?? '';
872
+ const domain = opts.domain ?? '';
873
+ const width = opts.width ?? constants_1.RDP_DEFAULT_WIDTH;
874
+ const height = opts.height ?? constants_1.RDP_DEFAULT_HEIGHT;
875
+ const colorDepth = opts.colorDepth ?? constants_1.RDP_DEFAULT_COLOR_DEPTH;
876
+ this._width = width;
877
+ this._height = height;
878
+ this._colorDepth = colorDepth;
879
+ const sock = net.createConnection({ host, port });
880
+ this._sock = sock;
881
+ sock.once('error', (err) => {
882
+ this.log(`[RDP] reconnect socket error: ${err.message}`);
883
+ if (err.code === 'ECONNRESET' && this._connected)
884
+ return;
885
+ if (!this._closed) {
886
+ this._closed = true;
887
+ this.emit('error', err);
888
+ }
889
+ });
890
+ sock.once('connect', async () => {
891
+ try {
892
+ await this._handshake(sock, host, port, username, password, domain, width, height, colorDepth, (_info) => { this.log('[RDP] reconnect complete — desktop ready'); }, (err) => {
893
+ this.log(`[RDP] reconnect handshake failed: ${err.message}`);
894
+ if (!this._closed) {
895
+ this._closed = true;
896
+ this.emit('error', err);
897
+ }
898
+ });
899
+ }
900
+ catch (err) {
901
+ const e = err instanceof Error ? err : new Error(String(err));
902
+ this.log(`[RDP] reconnect exception: ${e.message}`);
903
+ if (!this._closed) {
904
+ this._closed = true;
905
+ this.emit('error', e);
906
+ }
907
+ sock.destroy();
908
+ }
909
+ });
910
+ }
911
+ // ── Handshake ──────────────────────────────────────────────────────────
912
+ async _handshake(rawSock, host, _port, username, password, domain, width, height, colorDepth, resolve, reject) {
913
+ // ── Phase 1: X.224 negotiation ─────────────────────────────────────
914
+ const reqProtocol = (0, auth_1.selectRequestProtocol)();
915
+ rawSock.write((0, auth_1.buildX224ConnectRequest)(reqProtocol, username));
916
+ const cc = await this._readTpkt(rawSock);
917
+ const selectedProtocol = (0, auth_1.parseX224ConnectConfirm)(cc);
918
+ this.log(`[RDP] Phase1 done — selectedProtocol=${selectedProtocol}`);
919
+ // ── Phase 2: TLS upgrade ───────────────────────────────────────────
920
+ let sock = rawSock;
921
+ if (selectedProtocol !== 0 /* PROTOCOL_RDP */) {
922
+ sock = await (0, auth_1.upgradeTls)(rawSock, host);
923
+ this._sock = sock;
924
+ }
925
+ this.log(`[RDP] Phase2 done — TLS=${selectedProtocol !== 0}`);
926
+ // Attach error/close monitors — these must be in place before we send
927
+ // anything so we can surface failures immediately. We do NOT install the
928
+ // _recvBuf 'data' listener here; that is done inside _runLoop so that
929
+ // handshake packets read via _readTpkt are not also buffered in _recvBuf.
930
+ sock.on('error', (err) => {
931
+ this.log(`[RDP] socket error: ${err.message}`);
932
+ // ECONNRESET while connected = xrdp closing for reconnect; let 'close' handler decide
933
+ if (err.code === 'ECONNRESET' && this._connected)
934
+ return;
935
+ if (!this._closed) {
936
+ this._closed = true;
937
+ this.emit('error', err);
938
+ }
939
+ });
940
+ sock.on('close', () => {
941
+ this.log(`[RDP] socket closed (connected=${this._connected})`);
942
+ if (this._closed)
943
+ return;
944
+ // xrdp closes TCP after initial session setup — auto-reconnect for actual desktop
945
+ if (this._connected && this._opts && this._reconnectCount < RdpBridge._MAX_RECONNECTS) {
946
+ this._reconnectCount++;
947
+ this.log(`[RDP] clean close — auto-reconnect ${this._reconnectCount}/${RdpBridge._MAX_RECONNECTS}`);
948
+ this._connected = false;
949
+ this._userId = 0;
950
+ this._shareId = 0;
951
+ this._recvBuf = Buffer.alloc(0);
952
+ this._handshakeBuf = Buffer.alloc(0);
953
+ this._sock = null;
954
+ // Progressive delay: give xrdp time to start the X session backend
955
+ const delayMs = Math.min(1000 * this._reconnectCount, 5000);
956
+ setTimeout(() => this._doReconnect(), delayMs);
957
+ }
958
+ else {
959
+ this._closed = true;
960
+ this.emit('close');
961
+ }
962
+ });
963
+ // ── Phase 3: MCS Connect ───────────────────────────────────────────
964
+ const mcsInit = buildMcsConnectInitial(width, height, colorDepth);
965
+ this._sendX224Data(sock, mcsInit);
966
+ const mcsResp = await this._readTpktPayload(sock);
967
+ // Parse MCS ConnectResponse to extract server data (GCC) — we only need userId seed
968
+ // Most fields are not critical for basic connectivity; skip deep parsing for now.
969
+ void mcsResp;
970
+ // ── Phase 4: MCS Erect Domain + Attach User ────────────────────────
971
+ this._sendX224Data(sock, Buffer.from([constants_1.MCS_ERECT_DOMAIN_REQUEST, 0x00, 0x01, 0x00, 0x01]));
972
+ this._sendX224Data(sock, Buffer.from([constants_1.MCS_ATTACH_USER_REQUEST]));
973
+ const auConf = await this._readTpktPayload(sock);
974
+ if (auConf[0] !== constants_1.MCS_ATTACH_USER_CONFIRM) {
975
+ throw new Error(`Expected MCS_ATTACH_USER_CONFIRM, got 0x${auConf[0].toString(16)}`);
976
+ }
977
+ this._userId = 1001 + auConf.readUInt16BE(2);
978
+ this.log(`[RDP] Phase4 done — userId=${this._userId}`);
979
+ // ── Phase 5: Channel joins ─────────────────────────────────────────
980
+ for (const channelId of [this._userId, constants_1.MCS_CHANNEL_GLOBAL]) {
981
+ const joinReq = Buffer.alloc(5);
982
+ joinReq[0] = constants_1.MCS_CHANNEL_JOIN_REQUEST;
983
+ joinReq.writeUInt16BE(this._userId - 1001, 1);
984
+ joinReq.writeUInt16BE(channelId, 3);
985
+ this._sendX224Data(sock, joinReq);
986
+ const joinConf = await this._readTpktPayload(sock);
987
+ if (joinConf[0] !== constants_1.MCS_CHANNEL_JOIN_CONFIRM) {
988
+ throw new Error(`MCS channel join failed for channel ${channelId}`);
989
+ }
990
+ }
991
+ // ── Phase 6: Client Info (login) ───────────────────────────────────
992
+ const clientInfo = buildClientInfoPdu(username, password, domain);
993
+ this._sendMcsData(sock, constants_1.MCS_CHANNEL_GLOBAL, clientInfo);
994
+ this.log(`[RDP] Phase6 sent ClientInfo`);
995
+ // ── Phase 6b: License exchange ─────────────────────────────────────
996
+ await this._doLicenseExchange(sock, username);
997
+ this.log(`[RDP] Phase6b license exchange done`);
998
+ // ── Phase 7: Capability exchange ──────────────────────────────────
999
+ // Wait for Demand Active PDU from server
1000
+ const demandPdu = await this._waitForPduType(sock, constants_1.PDUTYPE_DEMANDACTIVEPDU, 15_000);
1001
+ this._shareId = demandPdu.readUInt32LE(6); // shareId in Share Control Header
1002
+ this.log(`[RDP] Phase7 DemandActive — shareId=0x${this._shareId.toString(16)}`);
1003
+ const confirmActive = buildConfirmActivePdu(this._userId, this._shareId, width, height);
1004
+ this._sendMcsData(sock, constants_1.MCS_CHANNEL_GLOBAL, confirmActive);
1005
+ this.log(`[RDP] Phase7 ConfirmActive sent, sending sync/ctrl/fontlist`);
1006
+ // Send synchronize + control + font list (required sequence per MS-RDPBCGR §1.3.1.1)
1007
+ this._sendMcsData(sock, constants_1.MCS_CHANNEL_GLOBAL, buildSynchronizePdu(this._userId, this._shareId));
1008
+ this._sendMcsData(sock, constants_1.MCS_CHANNEL_GLOBAL, buildControlPdu(this._userId, this._shareId, 4 /*CTRLACTION_COOPERATE*/));
1009
+ this._sendMcsData(sock, constants_1.MCS_CHANNEL_GLOBAL, buildControlPdu(this._userId, this._shareId, 1 /*CTRLACTION_REQUEST_CONTROL*/));
1010
+ this._sendMcsData(sock, constants_1.MCS_CHANNEL_GLOBAL, buildFontListPdu(this._userId, this._shareId));
1011
+ // ── Phase 8: Running ───────────────────────────────────────────────
1012
+ this._connected = true;
1013
+ const info = {
1014
+ name: host,
1015
+ width,
1016
+ height,
1017
+ colorDepth,
1018
+ };
1019
+ resolve(info);
1020
+ // Start continuous parsing
1021
+ this._runLoop(sock);
1022
+ }
1023
+ // ── License exchange ──────────────────────────────────────────────────
1024
+ /**
1025
+ * Handle RDP license exchange after Client Info PDU.
1026
+ * Responds to SERVER_LICENSE_REQUEST and PLATFORM_CHALLENGE until the
1027
+ * server completes licensing (or sends a non-license PDU).
1028
+ */
1029
+ async _doLicenseExchange(sock, username) {
1030
+ const SEC_LICENSE_PKT = 0x0080;
1031
+ let clientRand;
1032
+ let preMaster;
1033
+ let serverRand;
1034
+ let macKey;
1035
+ let encKey;
1036
+ for (let i = 0; i < 10; i++) {
1037
+ const tpkt = await this._readTpkt(sock);
1038
+ // Parse MCS SDI header to find the RDP/license payload
1039
+ const payload = tpkt.slice(7); // skip TPKT(4)+X224(3)
1040
+ if (payload.length < 7 || payload[0] !== constants_1.MCS_SEND_DATA_INDICATION) {
1041
+ // Not an SDI — put it back and stop
1042
+ this._handshakeBuf = Buffer.concat([tpkt, this._handshakeBuf]);
1043
+ return;
1044
+ }
1045
+ let off = 6;
1046
+ const lb = payload[off++];
1047
+ if (lb & 0x80)
1048
+ off++; // T.125 PER 2-byte form: [0x80|hi, lo] — skip the low byte
1049
+ const rdp = payload.slice(off);
1050
+ if (rdp.length < 6)
1051
+ continue;
1052
+ const secFlags = rdp.readUInt16LE(0);
1053
+ if (!(secFlags & SEC_LICENSE_PKT)) {
1054
+ // Not a license packet (could be Demand Active) — put it back
1055
+ this._handshakeBuf = Buffer.concat([tpkt, this._handshakeBuf]);
1056
+ return;
1057
+ }
1058
+ const body = rdp.slice(4); // skip 4-byte security header
1059
+ const msgType = body[0];
1060
+ if (msgType === 0x01) { // SERVER_LICENSE_REQUEST
1061
+ const { serverRand: sr, modulusLe, exponent } = parseLicenseRequest(body);
1062
+ serverRand = sr;
1063
+ clientRand = crypto.randomBytes(32);
1064
+ preMaster = crypto.randomBytes(48);
1065
+ const encPms = rsaEncryptLe(preMaster, modulusLe, exponent);
1066
+ const req = buildNewLicenseRequest(clientRand, encPms, username, 'autodev');
1067
+ const secHdr = Buffer.from([0x80, 0x00, 0x00, 0x00]); // SEC_LICENSE_PKT
1068
+ this._sendMcsData(sock, constants_1.MCS_CHANNEL_GLOBAL, Buffer.concat([secHdr, req]));
1069
+ }
1070
+ else if (msgType === 0x02 && clientRand && preMaster && serverRand) { // PLATFORM_CHALLENGE
1071
+ [macKey, encKey] = deriveLicenseKeys(preMaster, clientRand, serverRand);
1072
+ const chalLen = body.readUInt16LE(6);
1073
+ const encChal = body.slice(8, 8 + chalLen);
1074
+ const challenge = rc4(encKey, encChal);
1075
+ const resp = buildPlatformChallengeResponse(encKey, macKey, challenge);
1076
+ const secHdr = Buffer.from([0x80, 0x00, 0x00, 0x00]);
1077
+ this._sendMcsData(sock, constants_1.MCS_CHANNEL_GLOBAL, Buffer.concat([secHdr, resp]));
1078
+ }
1079
+ else if (msgType === 0x03 || msgType === 0xff) {
1080
+ // NEW_LICENSE or ERROR_ALERT — licensing done regardless of error code
1081
+ return;
1082
+ }
1083
+ }
1084
+ }
1085
+ // ── Run loop ─────────────────────────────────────────────────────────
1086
+ _runLoop(sock) {
1087
+ this.log(`[RDP] runLoop started (handshakeBuf=${this._handshakeBuf.length} bytes)`);
1088
+ const pump = () => {
1089
+ while (true) {
1090
+ const pdu = this._tryParseTpkt();
1091
+ if (!pdu)
1092
+ break;
1093
+ try {
1094
+ this._dispatchPdu(pdu);
1095
+ }
1096
+ catch (e) {
1097
+ this.log(`[RDP] dispatchPdu error: ${e}`);
1098
+ }
1099
+ }
1100
+ };
1101
+ // Drain any bytes left over from handshake phase into _recvBuf
1102
+ if (this._handshakeBuf.length > 0) {
1103
+ this._recvBuf = Buffer.concat([this._recvBuf, this._handshakeBuf]);
1104
+ this._handshakeBuf = Buffer.alloc(0);
1105
+ }
1106
+ // Install _recvBuf listener here (not during handshake) so that
1107
+ // _readTpkt's own listeners are the sole consumers during negotiation.
1108
+ sock.on('data', (chunk) => {
1109
+ this._recvBuf = Buffer.concat([this._recvBuf, chunk]);
1110
+ pump();
1111
+ });
1112
+ pump();
1113
+ }
1114
+ _dispatchPdu(data) {
1115
+ if (data.length < 4)
1116
+ return;
1117
+ // X.224 Data TPDU header is 3 bytes (LI=2, code=F0, EOT=80)
1118
+ const x224Start = 4; // after TPKT
1119
+ if (data[x224Start + 1] !== constants_1.X224_TPDU_DATA)
1120
+ return;
1121
+ const payload = data.slice(x224Start + 3);
1122
+ if (payload.length < 1)
1123
+ return;
1124
+ const mcsOpcode = payload[0];
1125
+ // MCS SendDataIndication
1126
+ if (mcsOpcode !== constants_1.MCS_SEND_DATA_INDICATION)
1127
+ return;
1128
+ if (payload.length < 8)
1129
+ return;
1130
+ // Decode MCS SDI PER/BER length field.
1131
+ // MCS fixed header: opcode(1)+initiator(2)+channelId(2)+priority(1) = 6 bytes.
1132
+ let offset = 6;
1133
+ const lenByte = payload[offset];
1134
+ offset++;
1135
+ if (lenByte & 0x80) {
1136
+ offset++; // T.125 PER 2-byte form: [0x80|hi, lo] — skip the low byte
1137
+ }
1138
+ const rdpPayload = payload.slice(offset);
1139
+ if (rdpPayload.length < 6)
1140
+ return;
1141
+ // With TLS (no RDP-level encryption), run-loop PDUs have no security header.
1142
+ // rdpPayload starts directly with the Share Control Header.
1143
+ const shareCtrl = rdpPayload;
1144
+ if (shareCtrl.length < 6)
1145
+ return;
1146
+ const pduType = shareCtrl.readUInt16LE(2) & 0x0f;
1147
+ this.log(`[RDP] runLoop PDU: pduType=0x${pduType.toString(16)} len=${shareCtrl.length}`);
1148
+ if (pduType === (constants_1.PDUTYPE_DATAPDU & 0x0f)) {
1149
+ const body = shareCtrl.slice(6);
1150
+ const pt2 = body.length >= 9 ? body[8] : -1;
1151
+ this.log(`[RDP] DataPDU pduType2=0x${pt2.toString(16)}`);
1152
+ this._handleDataPdu(body);
1153
+ }
1154
+ else if (pduType === (constants_1.PDUTYPE_DEMANDACTIVEPDU & 0x0f)) {
1155
+ // Re-issued demand active — re-confirm and re-sync
1156
+ this._shareId = shareCtrl.readUInt32LE(6);
1157
+ if (this._sock) {
1158
+ const s = this._sock;
1159
+ const confirm = buildConfirmActivePdu(this._userId, this._shareId, this._width, this._height);
1160
+ this._sendMcsData(s, constants_1.MCS_CHANNEL_GLOBAL, confirm);
1161
+ this._sendMcsData(s, constants_1.MCS_CHANNEL_GLOBAL, buildSynchronizePdu(this._userId, this._shareId));
1162
+ this._sendMcsData(s, constants_1.MCS_CHANNEL_GLOBAL, buildControlPdu(this._userId, this._shareId, 4));
1163
+ this._sendMcsData(s, constants_1.MCS_CHANNEL_GLOBAL, buildControlPdu(this._userId, this._shareId, 1));
1164
+ this._sendMcsData(s, constants_1.MCS_CHANNEL_GLOBAL, buildFontListPdu(this._userId, this._shareId));
1165
+ }
1166
+ }
1167
+ }
1168
+ _handleDataPdu(body) {
1169
+ if (body.length < 12)
1170
+ return;
1171
+ // TS_SHAREDATAHEADER: shareId(4)+pad(1)+streamId(1)+uncompressedLength(2)+pduType2(1)
1172
+ const pduType2 = body[8];
1173
+ if (pduType2 === constants_1.PDUTYPE2_UPDATE) {
1174
+ if (body.length >= 14) {
1175
+ const updateType = body.readUInt16LE(12);
1176
+ this.log(`[RDP] UpdatePDU updateType=0x${updateType.toString(16)}`);
1177
+ }
1178
+ this._handleUpdatePdu(body.slice(12));
1179
+ }
1180
+ else if (pduType2 === constants_1.PDUTYPE2_FONTMAP) {
1181
+ // Server finished capability sequence.
1182
+ // Send TS_SYNCHRONIZE_EVENT (keyboard state sync) to signal client readiness.
1183
+ // Do NOT send SuppressOutput/RefreshRect here — xrdp's Xvnc backend crashes on them
1184
+ // before it has fully started; just send INPUT_SYNC and let xrdp push updates.
1185
+ this._connected = true;
1186
+ if (this._sock) {
1187
+ // toggleFlags=0: no CapsLock/NumLock/ScrollLock
1188
+ this._sendInput(constants_1.INPUT_EVENT_SYNC, Buffer.from([0, 0, 0, 0, 0, 0]));
1189
+ this.log('[RDP] FontMap received — sent INPUT_SYNC');
1190
+ }
1191
+ }
1192
+ }
1193
+ _handleUpdatePdu(body) {
1194
+ if (body.length < 2)
1195
+ return;
1196
+ const updateType = body.readUInt16LE(0);
1197
+ if (updateType === constants_1.UPDATE_BITMAP) {
1198
+ this._parseBitmapUpdate(body.slice(2));
1199
+ }
1200
+ else if (updateType === 0x0003 /* UPDATETYPE_SYNCHRONIZE */) {
1201
+ // xrdp backend is syncing — wait before requesting a full-screen refresh
1202
+ // to give the Xvnc backend time to start up
1203
+ if (this._sock && !this._closed) {
1204
+ const s = this._sock;
1205
+ setTimeout(() => {
1206
+ if (!this._closed && this._connected && s === this._sock) {
1207
+ this._sendMcsData(s, constants_1.MCS_CHANNEL_GLOBAL, buildRefreshRectPdu(this._userId, this._shareId, this._width, this._height));
1208
+ this.log('[RDP] UPDATETYPE_SYNCHRONIZE — sent RefreshRect (delayed)');
1209
+ }
1210
+ }, 2000);
1211
+ }
1212
+ }
1213
+ // UPDATE_ORDERS and UPDATE_POINTER can be added in Phase 2
1214
+ }
1215
+ _parseBitmapUpdate(data) {
1216
+ if (data.length < 2)
1217
+ return;
1218
+ const numRects = data.readUInt16LE(0); // TS_BITMAP_DATA_ARRAY: numberRectangles
1219
+ let offset = 2;
1220
+ const rects = [];
1221
+ for (let i = 0; i < numRects; i++) {
1222
+ if (offset + 18 > data.length)
1223
+ break;
1224
+ const x = data.readUInt16LE(offset);
1225
+ const y = data.readUInt16LE(offset + 2);
1226
+ const w = data.readUInt16LE(offset + 4) - x + 1; // destRight - destLeft + 1
1227
+ const h = data.readUInt16LE(offset + 6) - y + 1; // destBottom - destTop + 1
1228
+ // offset+8 = width (bitmap pixels), offset+10 = height (bitmap pixels) — skipped
1229
+ const bpp = data.readUInt16LE(offset + 12); // bitsPerPixel
1230
+ const flags = data.readUInt16LE(offset + 14); // flags
1231
+ const bitmapLen = data.readUInt16LE(offset + 16); // bitmapLength
1232
+ offset += 18;
1233
+ if (offset + bitmapLen > data.length)
1234
+ break;
1235
+ const bitmapData = data.slice(offset, offset + bitmapLen);
1236
+ offset += bitmapLen;
1237
+ const bpp8 = Math.ceil(bpp / 8);
1238
+ let raw;
1239
+ if (flags & constants_1.BITMAP_COMPRESSION && !(flags & constants_1.NO_BITMAP_COMPRESSION_HDR)) {
1240
+ // Compressed with standard header (12 bytes)
1241
+ raw = decompressBitmap(bitmapData.slice(12), w, h, bpp8);
1242
+ }
1243
+ else if (flags & constants_1.BITMAP_COMPRESSION) {
1244
+ raw = decompressBitmap(bitmapData, w, h, bpp8);
1245
+ }
1246
+ else {
1247
+ raw = bitmapData;
1248
+ }
1249
+ // RDP sends bottom-up bitmaps — flip vertically
1250
+ const rowSize = w * bpp8;
1251
+ const flipped = Buffer.alloc(raw.length);
1252
+ for (let row = 0; row < h; row++) {
1253
+ raw.copy(flipped, row * rowSize, (h - 1 - row) * rowSize, (h - row) * rowSize);
1254
+ }
1255
+ rects.push({ x, y, w, h, data: toRgba(flipped, bpp8) });
1256
+ }
1257
+ if (rects.length > 0) {
1258
+ this.emit('fbu', rects);
1259
+ }
1260
+ }
1261
+ // ── Input helper ──────────────────────────────────────────────────────
1262
+ _sendInput(eventType, eventData) {
1263
+ if (!this._sock || !this._userId)
1264
+ return;
1265
+ // TS_INPUT_PDU_DATA: numEvents(2) + pad(2) + [TS_INPUT_EVENT: eventTime(4) + messageType(2) + slowPathData(6)]
1266
+ const event = Buffer.alloc(16);
1267
+ event.writeUInt16LE(1, 0); // numEvents
1268
+ event.writeUInt16LE(0, 2); // pad
1269
+ event.writeUInt32LE(0, 4); // eventTime (0 = server picks)
1270
+ event.writeUInt16LE(eventType, 8); // messageType
1271
+ if (eventData.length >= 6) {
1272
+ eventData.copy(event, 10, 0, 6); // slowPathData at offset 10
1273
+ }
1274
+ const shareHdr = buildShareDataHeader(this._userId, this._shareId, constants_1.PDUTYPE2_INPUT, event.length);
1275
+ this._sendMcsData(this._sock, constants_1.MCS_CHANNEL_GLOBAL, Buffer.concat([shareHdr, event]));
1276
+ }
1277
+ // ── Low-level send helpers ─────────────────────────────────────────────
1278
+ _sendX224Data(sock, payload) {
1279
+ sock.write(wrapTpkt(wrapX224Data(payload)));
1280
+ }
1281
+ _sendMcsData(sock, channelId, payload) {
1282
+ const mcs = wrapMcsSend(this._userId, channelId, payload);
1283
+ this._sendX224Data(sock, mcs);
1284
+ }
1285
+ // ── Receive helpers ───────────────────────────────────────────────────
1286
+ /** Read a complete TPKT from the given socket (used during handshake only). */
1287
+ _readTpkt(sock) {
1288
+ return new Promise((resolve, reject) => {
1289
+ const tryExtract = () => {
1290
+ if (this._handshakeBuf.length < 4)
1291
+ return false;
1292
+ const len = this._handshakeBuf.readUInt16BE(2);
1293
+ if (this._handshakeBuf.length < len)
1294
+ return false;
1295
+ const pkt = this._handshakeBuf.slice(0, len);
1296
+ this._handshakeBuf = this._handshakeBuf.slice(len); // preserve remainder
1297
+ resolve(pkt);
1298
+ return true;
1299
+ };
1300
+ if (tryExtract())
1301
+ return; // already buffered enough
1302
+ const onData = (chunk) => {
1303
+ this._handshakeBuf = Buffer.concat([this._handshakeBuf, chunk]);
1304
+ if (tryExtract())
1305
+ sock.off('data', onData);
1306
+ };
1307
+ sock.on('data', onData);
1308
+ setTimeout(() => { sock.off('data', onData); reject(new Error('TPKT read timeout')); }, 15_000);
1309
+ });
1310
+ }
1311
+ /** Read a TPKT and return only the payload (after TPKT+X.224 headers). */
1312
+ async _readTpktPayload(sock) {
1313
+ const full = await this._readTpkt(sock);
1314
+ // TPKT(4) + X.224 Data header(3) = 7
1315
+ return full.slice(7);
1316
+ }
1317
+ /**
1318
+ * Wait for an RDP PDU with a specific pduType in the Share Control Header.
1319
+ * Times out after `timeoutMs` ms.
1320
+ */
1321
+ _waitForPduType(sock, pduType, timeoutMs) {
1322
+ return new Promise((resolve, reject) => {
1323
+ // Seed with any bytes buffered during license exchange
1324
+ let buf = this._handshakeBuf;
1325
+ this._handshakeBuf = Buffer.alloc(0);
1326
+ const timer = setTimeout(() => {
1327
+ sock.off('data', onData);
1328
+ reject(new Error(`Timeout waiting for PDU type 0x${pduType.toString(16)}`));
1329
+ }, timeoutMs);
1330
+ const onData = (chunk) => {
1331
+ buf = Buffer.concat([buf, chunk]);
1332
+ // Try to extract complete TPKT packets
1333
+ while (buf.length >= 4) {
1334
+ const len = buf.readUInt16BE(2);
1335
+ if (buf.length < len)
1336
+ break;
1337
+ const tpkt = buf.slice(0, len);
1338
+ buf = buf.slice(len);
1339
+ // Decode MCS → RDP
1340
+ if (tpkt.length < 11)
1341
+ continue;
1342
+ const mcsPayload = tpkt.slice(7); // skip TPKT(4) + X224(3)
1343
+ if (mcsPayload[0] !== constants_1.MCS_SEND_DATA_INDICATION)
1344
+ continue;
1345
+ // MCS fixed header: opcode(1)+initiator(2)+channelId(2)+priority(1) = 6 bytes
1346
+ let offset = 6;
1347
+ const lb = mcsPayload[offset++];
1348
+ if (lb & 0x80)
1349
+ offset++; // T.125 PER 2-byte form: skip low byte
1350
+ const rdp = mcsPayload.slice(offset);
1351
+ if (rdp.length < 6)
1352
+ continue;
1353
+ // With TLS, Demand Active has no security header — rdp IS the Share Control Header.
1354
+ const t = rdp.readUInt16LE(2) & 0x0f;
1355
+ if (t === (pduType & 0x0f)) {
1356
+ clearTimeout(timer);
1357
+ sock.off('data', onData);
1358
+ // Save any remaining bytes so _runLoop can consume them
1359
+ this._handshakeBuf = Buffer.concat([buf, this._handshakeBuf]);
1360
+ buf = Buffer.alloc(0);
1361
+ resolve(rdp);
1362
+ return;
1363
+ }
1364
+ }
1365
+ };
1366
+ sock.on('data', onData);
1367
+ // Process any pre-seeded data immediately
1368
+ if (buf.length > 0)
1369
+ onData(Buffer.alloc(0));
1370
+ });
1371
+ }
1372
+ /** Try to parse one complete TPKT from _recvBuf without blocking.
1373
+ * Fast-path PDUs (first byte != 0x03) are skipped with a warning since we
1374
+ * advertise no fast-path support; they should not appear, but guard anyway. */
1375
+ _tryParseTpkt() {
1376
+ if (this._recvBuf.length < 4)
1377
+ return null;
1378
+ if (this._recvBuf[0] !== 0x03) {
1379
+ // Not a TPKT — could be a stray fast-path byte. Scan forward to next 0x03.
1380
+ this.log(`[RDP] warn: non-TPKT byte 0x${this._recvBuf[0].toString(16)} — skipping`);
1381
+ const next = this._recvBuf.indexOf(0x03, 1);
1382
+ this._recvBuf = next >= 0 ? this._recvBuf.slice(next) : Buffer.alloc(0);
1383
+ return null;
1384
+ }
1385
+ const len = this._recvBuf.readUInt16BE(2);
1386
+ if (len < 4) {
1387
+ this._recvBuf = this._recvBuf.slice(4);
1388
+ return null;
1389
+ } // guard
1390
+ if (this._recvBuf.length < len)
1391
+ return null;
1392
+ const pkt = this._recvBuf.slice(0, len);
1393
+ this._recvBuf = this._recvBuf.slice(len);
1394
+ return pkt;
1395
+ }
1396
+ }
1397
+ exports.RdpBridge = RdpBridge;
1398
+ //# sourceMappingURL=bridge.js.map