diff --git a/packages/teleport/src/lib/tdp/client.ts b/packages/teleport/src/lib/tdp/client.ts index 754c2cc19..b9b022254 100644 --- a/packages/teleport/src/lib/tdp/client.ts +++ b/packages/teleport/src/lib/tdp/client.ts @@ -29,6 +29,7 @@ import Codec, { SharedDirectoryErrCode, SharedDirectoryInfoResponse, SharedDirectoryListResponse, + SharedDirectoryReadResponse, FileSystemObject, } from './codec'; import { @@ -253,12 +254,23 @@ export default class Client extends EventEmitterWebAuthnSender { } } - handleSharedDirectoryReadRequest(buffer: ArrayBuffer) { + async handleSharedDirectoryReadRequest(buffer: ArrayBuffer) { const req = this.codec.decodeSharedDirectoryReadRequest(buffer); - // TODO(isaiah): delete debug logs - this.logger.debug('Received SharedDirectoryReadRequest:'); - this.logger.debug(req); - // TODO(isaiah): here's where we'll handle the SharedDirectoryReadResponse. + try { + const readData = await this.sdManager.readFile( + req.path, + req.offset, + req.length + ); + this.sendSharedDirectoryReadResponse({ + completionId: req.completionId, + errCode: SharedDirectoryErrCode.Nil, + readDataLength: readData.length, + readData, + }); + } catch (e) { + this.handleError(e); + } } async handleSharedDirectoryListRequest(buffer: ArrayBuffer) { @@ -367,6 +379,10 @@ export default class Client extends EventEmitterWebAuthnSender { this.send(this.codec.encodeSharedDirectoryListResponse(res)); } + sendSharedDirectoryReadResponse(response: SharedDirectoryReadResponse) { + this.send(this.codec.encodeSharedDirectoryReadResponse(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 3f1258043..31b8fbf4e 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_READ_REQUEST = 19, + SHARED_DIRECTORY_READ_RESPONSE = 20, SHARED_DIRECTORY_LIST_REQUEST = 25, SHARED_DIRECTORY_LIST_RESPONSE = 26, __LAST, // utility value @@ -129,6 +130,14 @@ export type SharedDirectoryReadRequest = { length: number; }; +// | message type (20) | completion_id uint32 | err_code uint32 | read_data_length uint32 | read_data []byte | +export type SharedDirectoryReadResponse = { + completionId: number; + errCode: SharedDirectoryErrCode; + readDataLength: number; + readData: Uint8Array; +}; + // | message type (25) | completion_id uint32 | directory_id uint32 | path_length uint32 | path []byte | export type SharedDirectoryListRequest = { completionId: number; @@ -527,6 +536,29 @@ export default class Codec { ]).buffer; } + // | message type (20) | completion_id uint32 | err_code uint32 | read_data_length uint32 | read_data []byte | + encodeSharedDirectoryReadResponse(res: SharedDirectoryReadResponse): Message { + const bufLen = + byteLength + 3 * uint32Length + byteLength * res.readDataLength; + const buffer = new ArrayBuffer(bufLen); + const view = new DataView(buffer); + let offset = 0; + + view.setUint8(offset, MessageType.SHARED_DIRECTORY_READ_RESPONSE); + offset += byteLength; + view.setUint32(offset, res.completionId); + offset += uint32Length; + view.setUint32(offset, res.errCode); + offset += uint32Length; + view.setUint32(offset, res.readDataLength); + offset += uint32Length; + res.readData.forEach(byte => { + view.setUint8(offset++, byte); + }); + + return buffer; + } + // | message type (26) | completion_id uint32 | err_code uint32 | fso_list_length uint32 | fso_list fso[] | encodeSharedDirectoryListResponse(res: SharedDirectoryListResponse): Message { const bufLenSansFsoList = byteLength + 3 * uint32Length; diff --git a/packages/teleport/src/lib/tdp/sharedDirectoryManager.ts b/packages/teleport/src/lib/tdp/sharedDirectoryManager.ts index a04ed318d..6c1148f72 100644 --- a/packages/teleport/src/lib/tdp/sharedDirectoryManager.ts +++ b/packages/teleport/src/lib/tdp/sharedDirectoryManager.ts @@ -81,6 +81,26 @@ export class SharedDirectoryManager { return infos; } + // Reads length bytes starting at offset from a file at path. + async readFile( + path: string, + offset: bigint, + length: number + ): Promise { + this.checkReady(); + + const fileHandle = await this.walkPath(path); + if (fileHandle.kind !== 'file') { + throw new Error('cannot read the bytes of a directory'); + } + + const file = await fileHandle.getFile(); + + return new Uint8Array( + await file.slice(Number(offset), Number(offset) + length).arrayBuffer() + ); + } + // walkPath walks a pathstr (assumed to be in the qualified Unix format specified // in the TDP spec), returning the FileSystemDirectoryHandle | FileSystemFileHandle // it finds at its end. If the pathstr isn't a valid path in the shared directory, diff --git a/yarn.lock b/yarn.lock index 441f43f63..9f1c73977 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10444,6 +10444,22 @@ node-gyp@8.4.1: tar "^6.1.2" which "^2.0.2" +node-gyp@8.4.1: + version "8.4.1" + resolved "https://registry.yarnpkg.com/node-gyp/-/node-gyp-8.4.1.tgz#3d49308fc31f768180957d6b5746845fbd429937" + integrity sha512-olTJRgUtAb/hOXG0E93wZDs5YiJlgbXxTwQAFHyNlRsXQnYzUaF2aGgujZbw+hR8aF4ZG/rST57bWMWD16jr9w== + dependencies: + env-paths "^2.2.0" + glob "^7.1.4" + graceful-fs "^4.2.6" + make-fetch-happen "^9.1.0" + nopt "^5.0.0" + npmlog "^6.0.0" + rimraf "^3.0.2" + semver "^7.3.5" + tar "^6.1.2" + which "^2.0.2" + node-int64@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b"