From cf36d90d9150498abfd3c5ea0c06831ffa6082d6 Mon Sep 17 00:00:00 2001 From: Isaiah Becker-Mayer Date: Thu, 4 Aug 2022 14:28:46 -0700 Subject: [PATCH 1/7] SharedDirectoryCreateRequest --- packages/teleport/src/lib/tdp/client.ts | 11 +++++++++ packages/teleport/src/lib/tdp/codec.ts | 33 +++++++++++++++++++++++++ 2 files changed, 44 insertions(+) diff --git a/packages/teleport/src/lib/tdp/client.ts b/packages/teleport/src/lib/tdp/client.ts index f8cf89c1e..0013d2074 100644 --- a/packages/teleport/src/lib/tdp/client.ts +++ b/packages/teleport/src/lib/tdp/client.ts @@ -129,6 +129,9 @@ export default class Client extends EventEmitterWebAuthnSender { case MessageType.SHARED_DIRECTORY_INFO_REQUEST: this.handleSharedDirectoryInfoRequest(buffer); break; + case MessageType.SHARED_DIRECTORY_CREATE_REQUEST: + this.handleSharedDirectoryCreateRequest(buffer); + break; case MessageType.SHARED_DIRECTORY_READ_REQUEST: this.handleSharedDirectoryReadRequest(buffer); break; @@ -266,6 +269,14 @@ export default class Client extends EventEmitterWebAuthnSender { } } + handleSharedDirectoryCreateRequest(buffer: ArrayBuffer) { + const req = this.codec.decodeSharedDirectoryCreateRequest(buffer); + // TODO(isaiah) delete these log statements + this.logger.debug('received SharedDirectoryCreateRequest:'); + this.logger.debug(req); + // TODO(isaiah): here's where we'll create a file/dir and respond. + } + async handleSharedDirectoryReadRequest(buffer: ArrayBuffer) { const req = this.codec.decodeSharedDirectoryReadRequest(buffer); try { diff --git a/packages/teleport/src/lib/tdp/codec.ts b/packages/teleport/src/lib/tdp/codec.ts index 293608dd5..454f20bc2 100644 --- a/packages/teleport/src/lib/tdp/codec.ts +++ b/packages/teleport/src/lib/tdp/codec.ts @@ -41,6 +41,7 @@ export enum MessageType { SHARED_DIRECTORY_ACKNOWLEDGE = 12, SHARED_DIRECTORY_INFO_REQUEST = 13, SHARED_DIRECTORY_INFO_RESPONSE = 14, + SHARED_DIRECTORY_CREATE_REQUEST = 15, SHARED_DIRECTORY_READ_REQUEST = 19, SHARED_DIRECTORY_READ_RESPONSE = 20, SHARED_DIRECTORY_WRITE_REQUEST = 21, @@ -123,6 +124,14 @@ export type SharedDirectoryInfoResponse = { fso: FileSystemObject; }; +// | message type (15) | completion_id uint32 | directory_id uint32 | file_type uint32 | path_length uint32 | path []byte | +export type SharedDirectoryCreateRequest = { + completionId: number; + directoryId: number; + fileType: FileType; + path: string; +}; + // | message type (19) | completion_id uint32 | directory_id uint32 | path_length uint32 | path []byte | offset uint64 | length uint32 | export type SharedDirectoryReadRequest = { completionId: number; @@ -775,6 +784,30 @@ export default class Codec { }; } + // | message type (15) | completion_id uint32 | directory_id uint32 | file_type uint32 | path_length uint32 | path []byte | + decodeSharedDirectoryCreateRequest( + buffer: ArrayBuffer + ): SharedDirectoryCreateRequest { + const dv = new DataView(buffer); + let offset = 0; + offset += byteLength; // eat message type + const completionId = dv.getUint32(offset); + offset += uint32Length; // eat completion_id + const directoryId = dv.getUint32(offset); + offset += uint32Length; // eat directory_id + const fileType = dv.getUint32(offset); + offset += uint32Length; // eat directory_id + offset += uint32Length; // eat path_length + const path = this.decoder.decode(new Uint8Array(buffer.slice(offset))); + + return { + completionId, + directoryId, + fileType, + path, + }; + } + // | message type (19) | completion_id uint32 | directory_id uint32 | path_length uint32 | path []byte | offset uint64 | length uint32 | decodeSharedDirectoryReadRequest( buffer: ArrayBuffer From fbff9f2f14d77b4f395677a269a4824f475a4413 Mon Sep 17 00:00:00 2001 From: Isaiah Becker-Mayer Date: Thu, 4 Aug 2022 14:55:53 -0700 Subject: [PATCH 2/7] first untested iteration of SharedDirectoryManager.create --- .../src/lib/tdp/sharedDirectoryManager.ts | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/packages/teleport/src/lib/tdp/sharedDirectoryManager.ts b/packages/teleport/src/lib/tdp/sharedDirectoryManager.ts index 45620b1bb..60060a32b 100644 --- a/packages/teleport/src/lib/tdp/sharedDirectoryManager.ts +++ b/packages/teleport/src/lib/tdp/sharedDirectoryManager.ts @@ -11,6 +11,7 @@ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. +import { FileType } from './codec'; // SharedDirectoryManager manages a FileSystemDirectoryHandle for use // by the TDP client. Most of its methods can potentially throw errors @@ -143,6 +144,31 @@ export class SharedDirectoryManager { return writeData.length; } + /** + * Creates a new file or directory (determined by fileType) at path. + * @throws TODO(isaiah): if a file or directory already exists at the given path? + * @throws Anything potentially thrown by getFileHandle/getDirectoryHandle + * @throws {PathDoesNotExistError} if the path isn't a valid path to a directory. + */ + async create(path: string, fileType: FileType): Promise { + let splitPath = path.split('/'); + const fileOrDirName = splitPath.pop(); + const dirPath = splitPath.join('/'); + + const dirHandle = await this.walkPath(dirPath); + if (dirHandle.kind !== 'directory') { + throw new PathDoesNotExistError( + 'destination was a file, not a directory' + ); + } + + if (fileType === FileType.File) { + await dirHandle.getFileHandle(fileOrDirName, { create: true }); + } else { + await dirHandle.getDirectoryHandle(fileOrDirName, { create: true }); + } + } + /** * walkPath walks a pathstr (assumed to be in the qualified Unix format specified * in the TDP spec), returning the FileSystemDirectoryHandle | FileSystemFileHandle From 789cb829b97306f311b42dbb9b430c4a94e6c079 Mon Sep 17 00:00:00 2001 From: Isaiah Becker-Mayer Date: Thu, 4 Aug 2022 17:14:58 -0700 Subject: [PATCH 3/7] Creating files by copying or moving them from the windows machine is working. Directories get created but their contents isn't copied over. Next, check to confirm that native RDP clients copy the contents of directories as well. (Has console.log statements). --- packages/teleport/src/lib/tdp/client.ts | 45 ++++++++++++++++--- packages/teleport/src/lib/tdp/codec.ts | 25 +++++++++++ packages/teleport/src/lib/tdp/playerClient.ts | 2 +- .../src/lib/tdp/sharedDirectoryManager.ts | 2 +- 4 files changed, 65 insertions(+), 9 deletions(-) diff --git a/packages/teleport/src/lib/tdp/client.ts b/packages/teleport/src/lib/tdp/client.ts index 0013d2074..c4b3551cc 100644 --- a/packages/teleport/src/lib/tdp/client.ts +++ b/packages/teleport/src/lib/tdp/client.ts @@ -31,6 +31,7 @@ import Codec, { SharedDirectoryListResponse, SharedDirectoryReadResponse, SharedDirectoryWriteResponse, + SharedDirectoryCreateResponse, FileSystemObject, } from './codec'; import { @@ -98,7 +99,7 @@ export default class Client extends EventEmitterWebAuthnSender { }; } - processMessage(buffer: ArrayBuffer) { + async processMessage(buffer: ArrayBuffer) { try { const messageType = this.codec.decodeMessageType(buffer); switch (messageType) { @@ -130,7 +131,12 @@ export default class Client extends EventEmitterWebAuthnSender { this.handleSharedDirectoryInfoRequest(buffer); break; case MessageType.SHARED_DIRECTORY_CREATE_REQUEST: - this.handleSharedDirectoryCreateRequest(buffer); + // A typical sequence is that we receive a SharedDirectoryCreateRequest + // immediately followed by a SharedDirectoryWriteRequest. It's important + // that we await here so that this client doesn't field the SharedDirectoryWriteRequest + // until the create has successfully completed, or else we might get an error + // trying to write to a file that hasn't been created yet. + await this.handleSharedDirectoryCreateRequest(buffer); break; case MessageType.SHARED_DIRECTORY_READ_REQUEST: this.handleSharedDirectoryReadRequest(buffer); @@ -269,12 +275,28 @@ export default class Client extends EventEmitterWebAuthnSender { } } - handleSharedDirectoryCreateRequest(buffer: ArrayBuffer) { + async handleSharedDirectoryCreateRequest(buffer: ArrayBuffer) { const req = this.codec.decodeSharedDirectoryCreateRequest(buffer); - // TODO(isaiah) delete these log statements - this.logger.debug('received SharedDirectoryCreateRequest:'); - this.logger.debug(req); - // TODO(isaiah): here's where we'll create a file/dir and respond. + console.log( + 'received a SharedDirectoryCreateRequest: ' + JSON.stringify(req) + ); + try { + console.log('trying a sdManager.create for path = ' + req.path); + await this.sdManager.create(req.path, req.fileType); + console.log('create supposedly succeeded'); + this.sendSharedDirectoryCreateResponse({ + completionId: req.completionId, + errCode: SharedDirectoryErrCode.Nil, + }); + } catch (e) { + console.log('create failed'); + this.sendSharedDirectoryCreateResponse({ + completionId: req.completionId, + errCode: SharedDirectoryErrCode.Failed, + }); + // TODO(isaiah): this can probably become a non-fatal error + this.handleError(e); + } } async handleSharedDirectoryReadRequest(buffer: ArrayBuffer) { @@ -298,12 +320,16 @@ export default class Client extends EventEmitterWebAuthnSender { async handleSharedDirectoryWriteRequest(buffer: ArrayBuffer) { const req = this.codec.decodeSharedDirectoryWriteRequest(buffer); + console.log('received a SharedDirectoryWriteRequest'); + console.log(req); try { + console.log('attempting sdManager.writeFile for path = ' + req.path); const bytesWritten = await this.sdManager.writeFile( req.path, req.offset, req.writeData ); + console.log('writeFile supposedly succeeded'); this.sendSharedDirectoryWriteResponse({ completionId: req.completionId, @@ -311,6 +337,7 @@ export default class Client extends EventEmitterWebAuthnSender { bytesWritten, }); } catch (e) { + console.log('writeFile failed'); this.handleError(e); } } @@ -443,6 +470,10 @@ export default class Client extends EventEmitterWebAuthnSender { this.send(this.codec.encodeSharedDirectoryWriteResponse(response)); } + sendSharedDirectoryCreateResponse(response: SharedDirectoryCreateResponse) { + this.send(this.codec.encodeSharedDirectoryCreateResponse(response)); + } + resize(spec: ClientScreenSpec) { this.send(this.codec.encodeClientScreenSpec(spec)); } diff --git a/packages/teleport/src/lib/tdp/codec.ts b/packages/teleport/src/lib/tdp/codec.ts index 454f20bc2..edea9d4a1 100644 --- a/packages/teleport/src/lib/tdp/codec.ts +++ b/packages/teleport/src/lib/tdp/codec.ts @@ -42,6 +42,7 @@ export enum MessageType { SHARED_DIRECTORY_INFO_REQUEST = 13, SHARED_DIRECTORY_INFO_RESPONSE = 14, SHARED_DIRECTORY_CREATE_REQUEST = 15, + SHARED_DIRECTORY_CREATE_RESPONSE = 16, SHARED_DIRECTORY_READ_REQUEST = 19, SHARED_DIRECTORY_READ_RESPONSE = 20, SHARED_DIRECTORY_WRITE_REQUEST = 21, @@ -132,6 +133,12 @@ export type SharedDirectoryCreateRequest = { path: string; }; +// | message type (16) | completion_id uint32 | err_code uint32 | +export type SharedDirectoryCreateResponse = { + completionId: number; + errCode: SharedDirectoryErrCode; +}; + // | message type (19) | completion_id uint32 | directory_id uint32 | path_length uint32 | path []byte | offset uint64 | length uint32 | export type SharedDirectoryReadRequest = { completionId: number; @@ -575,6 +582,24 @@ export default class Codec { ]).buffer; } + // | message type (16) | completion_id uint32 | err_code uint32 | + encodeSharedDirectoryCreateResponse( + res: SharedDirectoryCreateResponse + ): Message { + const bufLen = byteLength + 2 * uint32Length; + const buffer = new ArrayBuffer(bufLen); + const view = new DataView(buffer); + let offset = 0; + + view.setUint8(offset, MessageType.SHARED_DIRECTORY_CREATE_RESPONSE); + offset += byteLength; + view.setUint32(offset, res.completionId); + offset += uint32Length; + view.setUint32(offset, res.errCode); + + return buffer; + } + // | message type (20) | completion_id uint32 | err_code uint32 | read_data_length uint32 | read_data []byte | encodeSharedDirectoryReadResponse(res: SharedDirectoryReadResponse): Message { const bufLen = diff --git a/packages/teleport/src/lib/tdp/playerClient.ts b/packages/teleport/src/lib/tdp/playerClient.ts index 53eac287f..ca4cde4ad 100644 --- a/packages/teleport/src/lib/tdp/playerClient.ts +++ b/packages/teleport/src/lib/tdp/playerClient.ts @@ -42,7 +42,7 @@ export class PlayerClient extends Client { } // Overrides Client implementation. - processMessage(buffer: ArrayBuffer) { + async processMessage(buffer: ArrayBuffer) { const json = JSON.parse(this.textDecoder.decode(buffer)); if (json.message === 'end') { diff --git a/packages/teleport/src/lib/tdp/sharedDirectoryManager.ts b/packages/teleport/src/lib/tdp/sharedDirectoryManager.ts index 60060a32b..945914930 100644 --- a/packages/teleport/src/lib/tdp/sharedDirectoryManager.ts +++ b/packages/teleport/src/lib/tdp/sharedDirectoryManager.ts @@ -146,7 +146,7 @@ export class SharedDirectoryManager { /** * Creates a new file or directory (determined by fileType) at path. - * @throws TODO(isaiah): if a file or directory already exists at the given path? + * If the path already exists, this operation is effectively ignored. * @throws Anything potentially thrown by getFileHandle/getDirectoryHandle * @throws {PathDoesNotExistError} if the path isn't a valid path to a directory. */ From fd793b9d28814aa2073c609d17ffda6065edfbed Mon Sep 17 00:00:00 2001 From: Isaiah Becker-Mayer Date: Fri, 5 Aug 2022 17:26:50 -0700 Subject: [PATCH 4/7] Adds a FileSystemObject to SharedDirectoryCreateResponse and removes all console.log statements --- packages/teleport/src/lib/tdp/client.ts | 20 +++++++++----------- packages/teleport/src/lib/tdp/codec.ts | 20 ++++++++++++++------ 2 files changed, 23 insertions(+), 17 deletions(-) diff --git a/packages/teleport/src/lib/tdp/client.ts b/packages/teleport/src/lib/tdp/client.ts index c4b3551cc..b9c04046c 100644 --- a/packages/teleport/src/lib/tdp/client.ts +++ b/packages/teleport/src/lib/tdp/client.ts @@ -277,22 +277,25 @@ export default class Client extends EventEmitterWebAuthnSender { async handleSharedDirectoryCreateRequest(buffer: ArrayBuffer) { const req = this.codec.decodeSharedDirectoryCreateRequest(buffer); - console.log( - 'received a SharedDirectoryCreateRequest: ' + JSON.stringify(req) - ); + try { - console.log('trying a sdManager.create for path = ' + req.path); await this.sdManager.create(req.path, req.fileType); - console.log('create supposedly succeeded'); + const info = await this.sdManager.getInfo(req.path); this.sendSharedDirectoryCreateResponse({ completionId: req.completionId, errCode: SharedDirectoryErrCode.Nil, + fso: this.toFso(info), }); } catch (e) { - console.log('create failed'); this.sendSharedDirectoryCreateResponse({ completionId: req.completionId, errCode: SharedDirectoryErrCode.Failed, + fso: { + lastModified: BigInt(0), + fileType: FileType.File, + size: BigInt(0), + path: req.path, + }, }); // TODO(isaiah): this can probably become a non-fatal error this.handleError(e); @@ -320,16 +323,12 @@ export default class Client extends EventEmitterWebAuthnSender { async handleSharedDirectoryWriteRequest(buffer: ArrayBuffer) { const req = this.codec.decodeSharedDirectoryWriteRequest(buffer); - console.log('received a SharedDirectoryWriteRequest'); - console.log(req); try { - console.log('attempting sdManager.writeFile for path = ' + req.path); const bytesWritten = await this.sdManager.writeFile( req.path, req.offset, req.writeData ); - console.log('writeFile supposedly succeeded'); this.sendSharedDirectoryWriteResponse({ completionId: req.completionId, @@ -337,7 +336,6 @@ export default class Client extends EventEmitterWebAuthnSender { bytesWritten, }); } catch (e) { - console.log('writeFile failed'); this.handleError(e); } } diff --git a/packages/teleport/src/lib/tdp/codec.ts b/packages/teleport/src/lib/tdp/codec.ts index edea9d4a1..ad05ddb52 100644 --- a/packages/teleport/src/lib/tdp/codec.ts +++ b/packages/teleport/src/lib/tdp/codec.ts @@ -133,10 +133,11 @@ export type SharedDirectoryCreateRequest = { path: string; }; -// | message type (16) | completion_id uint32 | err_code uint32 | +// | message type (16) | completion_id uint32 | err_code uint32 | file_system_object fso | export type SharedDirectoryCreateResponse = { completionId: number; errCode: SharedDirectoryErrCode; + fso: FileSystemObject; }; // | message type (19) | completion_id uint32 | directory_id uint32 | path_length uint32 | path []byte | offset uint64 | length uint32 | @@ -582,13 +583,13 @@ export default class Codec { ]).buffer; } - // | message type (16) | completion_id uint32 | err_code uint32 | + // | message type (16) | completion_id uint32 | err_code uint32 | file_system_object fso | encodeSharedDirectoryCreateResponse( res: SharedDirectoryCreateResponse ): Message { - const bufLen = byteLength + 2 * uint32Length; - const buffer = new ArrayBuffer(bufLen); - const view = new DataView(buffer); + const bufLenSansFso = byteLength + 2 * uint32Length; + const bufferSansFso = new ArrayBuffer(bufLenSansFso); + const view = new DataView(bufferSansFso); let offset = 0; view.setUint8(offset, MessageType.SHARED_DIRECTORY_CREATE_RESPONSE); @@ -596,8 +597,15 @@ export default class Codec { view.setUint32(offset, res.completionId); offset += uint32Length; view.setUint32(offset, res.errCode); + offset += uint32Length; - return buffer; + const fsoBuffer = this.encodeFileSystemObject(res.fso); + + // https://gist.github.com/72lions/4528834?permalink_comment_id=2395442#gistcomment-2395442 + return new Uint8Array([ + ...new Uint8Array(bufferSansFso), + ...new Uint8Array(fsoBuffer), + ]).buffer; } // | message type (20) | completion_id uint32 | err_code uint32 | read_data_length uint32 | read_data []byte | From ebb80328d25c89b7f83719fec6cde6509e9d9656 Mon Sep 17 00:00:00 2001 From: Isaiah Becker-Mayer Date: Fri, 5 Aug 2022 19:58:53 -0700 Subject: [PATCH 5/7] Updates create docstring --- packages/teleport/src/lib/tdp/sharedDirectoryManager.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/teleport/src/lib/tdp/sharedDirectoryManager.ts b/packages/teleport/src/lib/tdp/sharedDirectoryManager.ts index 945914930..2931c89bb 100644 --- a/packages/teleport/src/lib/tdp/sharedDirectoryManager.ts +++ b/packages/teleport/src/lib/tdp/sharedDirectoryManager.ts @@ -146,8 +146,9 @@ export class SharedDirectoryManager { /** * Creates a new file or directory (determined by fileType) at path. - * If the path already exists, this operation is effectively ignored. - * @throws Anything potentially thrown by getFileHandle/getDirectoryHandle + * If the path already exists for the given fileType, this operation is effectively ignored. + * @throws {DomException} If the path already exists but not for the given fileType. + * @throws Anything potentially thrown by getFileHandle/getDirectoryHandle. * @throws {PathDoesNotExistError} if the path isn't a valid path to a directory. */ async create(path: string, fileType: FileType): Promise { From 06e1db6b4ef9e8da859941347aea7aef4c6d6d9c Mon Sep 17 00:00:00 2001 From: Isaiah Becker-Mayer Date: Sun, 7 Aug 2022 13:07:57 -0700 Subject: [PATCH 6/7] Fixes invalid handleError call --- packages/teleport/src/lib/tdp/client.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/teleport/src/lib/tdp/client.ts b/packages/teleport/src/lib/tdp/client.ts index 820b7c8dd..025c72c7b 100644 --- a/packages/teleport/src/lib/tdp/client.ts +++ b/packages/teleport/src/lib/tdp/client.ts @@ -303,8 +303,7 @@ export default class Client extends EventEmitterWebAuthnSender { path: req.path, }, }); - // TODO(isaiah): this can probably become a non-fatal error - this.handleError(e); + this.handleError(e, TdpClientEvent.CLIENT_ERROR, false); } } From 25b131da889a660ebcc340a7ed4ffd9c6da5e628 Mon Sep 17 00:00:00 2001 From: Isaiah Becker-Mayer Date: Mon, 8 Aug 2022 19:45:16 -0700 Subject: [PATCH 7/7] awaits processMessage, updates signatures --- packages/teleport/src/lib/tdp/client.ts | 8 +++++--- packages/teleport/src/lib/tdp/playerClient.ts | 4 ++-- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/packages/teleport/src/lib/tdp/client.ts b/packages/teleport/src/lib/tdp/client.ts index 025c72c7b..88de278d5 100644 --- a/packages/teleport/src/lib/tdp/client.ts +++ b/packages/teleport/src/lib/tdp/client.ts @@ -82,8 +82,8 @@ export default class Client extends EventEmitterWebAuthnSender { this.emit(TdpClientEvent.WS_OPEN); }; - this.socket.onmessage = (ev: MessageEvent) => { - this.processMessage(ev.data as ArrayBuffer); + this.socket.onmessage = async (ev: MessageEvent) => { + await this.processMessage(ev.data as ArrayBuffer); }; // The socket 'error' event will only ever be emitted by the socket @@ -103,7 +103,9 @@ export default class Client extends EventEmitterWebAuthnSender { }; } - async processMessage(buffer: ArrayBuffer) { + // processMessage should be await-ed when called, + // so that its internal await-or-not logic is obeyed. + async processMessage(buffer: ArrayBuffer): Promise { try { const messageType = this.codec.decodeMessageType(buffer); switch (messageType) { diff --git a/packages/teleport/src/lib/tdp/playerClient.ts b/packages/teleport/src/lib/tdp/playerClient.ts index ca4cde4ad..25c95067b 100644 --- a/packages/teleport/src/lib/tdp/playerClient.ts +++ b/packages/teleport/src/lib/tdp/playerClient.ts @@ -42,7 +42,7 @@ export class PlayerClient extends Client { } // Overrides Client implementation. - async processMessage(buffer: ArrayBuffer) { + async processMessage(buffer: ArrayBuffer): Promise { const json = JSON.parse(this.textDecoder.decode(buffer)); if (json.message === 'end') { @@ -52,7 +52,7 @@ export class PlayerClient extends Client { } else { const ms = json.ms; this.emit(PlayerClientEvent.UPDATE_CURRENT_TIME, ms); - super.processMessage(base64ToArrayBuffer(json.message)); + await super.processMessage(base64ToArrayBuffer(json.message)); } }