diff --git a/packages/shared/libs/logger.js b/packages/shared/libs/logger.js index 972c97d54..8a10659c2 100644 --- a/packages/shared/libs/logger.js +++ b/packages/shared/libs/logger.js @@ -35,6 +35,10 @@ export class Logger { this.log('info', ...args); } + debug(...args) { + this.log('debug', ...args); + } + error(...args) { this.log('error', ...args); } diff --git a/packages/teleport/src/lib/tdp/client.ts b/packages/teleport/src/lib/tdp/client.ts index 55e04b555..077ee6234 100644 --- a/packages/teleport/src/lib/tdp/client.ts +++ b/packages/teleport/src/lib/tdp/client.ts @@ -112,6 +112,9 @@ export default class Client extends EventEmitterWebAuthnSender { case MessageType.SHARED_DIRECTORY_ACKNOWLEDGE: this.handleSharedDirectoryAcknowledge(buffer); break; + case MessageType.SHARED_DIRECTORY_INFO_REQUEST: + this.handleSharedDirectoryInfoRequest(buffer); + break; default: this.logger.warn(`received unsupported message type ${messageType}`); } @@ -204,6 +207,15 @@ export default class Client extends EventEmitterWebAuthnSender { this.logger.info('Started sharing directory: ' + this.sharedDirectory.name); } + handleSharedDirectoryInfoRequest(buffer: ArrayBuffer) { + const req = this.codec.decodeSharedDirectoryInfoRequest(buffer); + // TODO(isaiah): remove debug once message is handled. + this.logger.debug( + 'Received SharedDirectoryInfoRequest: ' + JSON.stringify(req) + ); + // TODO(isaiah): here's where we'll respond with SharedDirectoryInfoResponse + } + protected send( data: string | ArrayBufferLike | Blob | ArrayBufferView ): void { diff --git a/packages/teleport/src/lib/tdp/codec.test.ts b/packages/teleport/src/lib/tdp/codec.test.ts index 7b9a53d4f..d0ac2ce56 100644 --- a/packages/teleport/src/lib/tdp/codec.test.ts +++ b/packages/teleport/src/lib/tdp/codec.test.ts @@ -104,9 +104,8 @@ test('decodes message types', () => { const { buffer: pngFrameBuf } = makeBuf(MessageType.PNG_FRAME); const { buffer: clipboardBuf } = makeBuf(MessageType.CLIPBOARD_DATA); const { buffer: errorBuf } = makeBuf(MessageType.ERROR); - const { buffer: invalidBuf } = makeBuf( - MessageType.SHARED_DIRECTORY_ACKNOWLEDGE + 1 - ); + let invalid = MessageType.__LAST; + const { buffer: invalidBuf } = makeBuf(invalid); expect(codec.decodeMessageType(pngFrameBuf)).toEqual(MessageType.PNG_FRAME); expect(codec.decodeMessageType(clipboardBuf)).toEqual( @@ -115,9 +114,7 @@ test('decodes message types', () => { expect(codec.decodeMessageType(errorBuf)).toEqual(MessageType.ERROR); expect(() => { codec.decodeMessageType(invalidBuf); - }).toThrow( - `invalid message type: ${MessageType.SHARED_DIRECTORY_ACKNOWLEDGE + 1}` - ); + }).toThrow(`invalid message type: ${invalid}`); }); test('decodes errors', () => { diff --git a/packages/teleport/src/lib/tdp/codec.ts b/packages/teleport/src/lib/tdp/codec.ts index 29c347905..ccc1e848d 100644 --- a/packages/teleport/src/lib/tdp/codec.ts +++ b/packages/teleport/src/lib/tdp/codec.ts @@ -39,6 +39,8 @@ export enum MessageType { MFA_JSON = 10, SHARED_DIRECTORY_ANNOUNCE = 11, SHARED_DIRECTORY_ACKNOWLEDGE = 12, + SHARED_DIRECTORY_INFO_REQUEST = 13, + __LAST, // utility value } // 0 is left button, 1 is middle button, 2 is right button @@ -99,6 +101,13 @@ export type SharedDirectoryAcknowledge = { directoryId: number; }; +// | message type (13) | completion_id uint32 | directory_id uint32 | path_length uint32 | path []byte | +export type SharedDirectoryInfoRequest = { + completionId: number; + directoryId: number; + path: string; +}; + export enum SharedDirectoryErrCode { // nil (no error, operation succeeded) Nil = 0, @@ -451,7 +460,7 @@ export default class Codec { // decodeClipboardData decodes clipboard data decodeClipboardData(buffer: ArrayBuffer): ClipboardData { return { - data: this._decodeStringMessage(buffer), + data: this.decodeStringMessage(buffer), }; } @@ -460,7 +469,7 @@ export default class Codec { // Throws an error on an invalid or unexpected MessageType value. decodeMessageType(buffer: ArrayBuffer): MessageType { const messageType = new DataView(buffer).getUint8(0); - if (!(messageType in MessageType)) { + if (!(messageType in MessageType) || messageType === MessageType.__LAST) { throw new Error(`invalid message type: ${messageType}`); } return messageType; @@ -469,43 +478,57 @@ export default class Codec { // decodeError decodes a raw tdp ERROR message and returns it as a string // | message type (9) | message_length uint32 | message []byte decodeErrorMessage(buffer: ArrayBuffer): string { - return this._decodeStringMessage(buffer); + return this.decodeStringMessage(buffer); } // decodeMfaChallenge decodes a raw tdp MFA challenge message and returns it as a string (of a json). // | message type (10) | mfa_type byte | message_length uint32 | json []byte decodeMfaJson(buffer: ArrayBuffer): MfaJson { let dv = new DataView(buffer); - const mfaType = String.fromCharCode(dv.getUint8(1)); + let offset = 0; + offset += byteLength; // eat message type + const mfaType = String.fromCharCode(dv.getUint8(offset)); + offset += byteLength; // eat mfa_type if (mfaType !== 'n' && mfaType !== 'u') { throw new Error(`invalid mfa type ${mfaType}, should be "n" or "u"`); } - const jsonString = this.decoder.decode(new Uint8Array(buffer.slice(6))); + offset += uint32Length; // eat message_length + const jsonString = this.decoder.decode( + new Uint8Array(buffer.slice(offset)) + ); return { mfaType, jsonString }; } - // _decodeStringMessage decodes a tdp message of the form + // decodeStringMessage decodes a tdp message of the form // | message type (N) | message_length uint32 | message []byte - _decodeStringMessage(buffer: ArrayBuffer): string { + private decodeStringMessage(buffer: ArrayBuffer): string { // slice(5) ensures we skip the message type and message_length - return this.decoder.decode(new Uint8Array(buffer.slice(5))); + const offset = 0 + byteLength + uint32Length; // eat message type and message_length + return this.decoder.decode(new Uint8Array(buffer.slice(offset))); } // decodePngFrame decodes a raw tdp PNG frame message and returns it as a PngFrame // | message type (2) | left uint32 | top uint32 | right uint32 | bottom uint32 | data []byte | // https://github.com/gravitational/teleport/blob/master/rfd/0037-desktop-access-protocol.md#2---png-frame - decodePngFrame(buffer: ArrayBuffer, onload: (PngFrame) => any): PngFrame { - let dv = new DataView(buffer); + decodePngFrame( + buffer: ArrayBuffer, + onload: (pngFrame: PngFrame) => any + ): PngFrame { + const dv = new DataView(buffer); const image = new Image(); - const pngFrame = { - left: dv.getUint32(1), - top: dv.getUint32(5), - right: dv.getUint32(9), - bottom: dv.getUint32(13), - data: image, - }; + let offset = 0; + offset += byteLength; // eat message type + const left = dv.getUint32(offset); + offset += uint32Length; // eat left + const top = dv.getUint32(offset); + offset += uint32Length; // eat top + const right = dv.getUint32(offset); + offset += uint32Length; // eat right + const bottom = dv.getUint32(offset); + offset += uint32Length; // eat bottom + const pngFrame = { left, top, right, bottom, data: image }; pngFrame.data.onload = onload(pngFrame); - pngFrame.data.src = this.asBase64Url(buffer); + pngFrame.data.src = this.asBase64Url(buffer, offset); return pngFrame; } @@ -527,9 +550,30 @@ export default class Codec { }; } + // | message type (13) | completion_id uint32 | directory_id uint32 | path_length uint32 | path []byte | + decodeSharedDirectoryInfoRequest( + buffer: ArrayBuffer + ): SharedDirectoryInfoRequest { + 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 + offset += uint32Length; // eat path_length + const path = this.decoder.decode(new Uint8Array(buffer.slice(offset))); + + return { + completionId, + directoryId, + path, + }; + } + // asBase64Url creates a data:image uri from the png data part of a PNG_FRAME tdp message. - private asBase64Url(buffer: ArrayBuffer): string { - return `data:image/png;base64,${arrayBufferToBase64(buffer.slice(17))}`; + private asBase64Url(buffer: ArrayBuffer, offset: number): string { + return `data:image/png;base64,${arrayBufferToBase64(buffer.slice(offset))}`; } }