Skip to content
This repository was archived by the owner on Feb 8, 2024. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions packages/shared/libs/logger.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ export class Logger {
this.log('info', ...args);
}

debug(...args) {
this.log('debug', ...args);
}

error(...args) {
this.log('error', ...args);
}
Expand Down
12 changes: 12 additions & 0 deletions packages/teleport/src/lib/tdp/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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}`);
}
Expand Down Expand Up @@ -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(
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is debug a temporary logger (like we remove it once TODO is implemented)? if not, what is diff between info and debug?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In chrome dev tools, I see it as its own section "No verbose"
image

I haven't checked as to whether its a no-op in production builds and/or how it works in other browsers. I'll take a look.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@kimlisa debug here is equivalent to console.debug(), whether its displayed in the console depends on the console settings. I will add a TODO to remove it.

'Received SharedDirectoryInfoRequest: ' + JSON.stringify(req)
);
// TODO(isaiah): here's where we'll respond with SharedDirectoryInfoResponse
}

protected send(
data: string | ArrayBufferLike | Blob | ArrayBufferView
): void {
Expand Down
9 changes: 3 additions & 6 deletions packages/teleport/src/lib/tdp/codec.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -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', () => {
Expand Down
84 changes: 64 additions & 20 deletions packages/teleport/src/lib/tdp/codec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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),
};
}

Expand All @@ -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;
Expand All @@ -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;
}
Expand All @@ -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))}`;
}
}

Expand Down