Skip to content
This repository was archived by the owner on Feb 8, 2024. It is now read-only.
Merged
47 changes: 44 additions & 3 deletions packages/teleport/src/lib/tdp/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import Codec, {
SharedDirectoryMoveResponse,
SharedDirectoryReadResponse,
SharedDirectoryWriteResponse,
SharedDirectoryCreateResponse,
FileSystemObject,
} from './codec';
import {
Expand Down Expand Up @@ -81,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
Expand All @@ -102,7 +103,9 @@ export default class Client extends EventEmitterWebAuthnSender {
};
}

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<void> {
try {
const messageType = this.codec.decodeMessageType(buffer);
switch (messageType) {
Expand Down Expand Up @@ -136,6 +139,14 @@ export default class Client extends EventEmitterWebAuthnSender {
case MessageType.SHARED_DIRECTORY_INFO_REQUEST:
this.handleSharedDirectoryInfoRequest(buffer);
break;
case MessageType.SHARED_DIRECTORY_CREATE_REQUEST:
// 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);
Comment thread
ibeckermayer marked this conversation as resolved.
break;
case MessageType.SHARED_DIRECTORY_READ_REQUEST:
this.handleSharedDirectoryReadRequest(buffer);
break;
Expand Down Expand Up @@ -272,6 +283,32 @@ export default class Client extends EventEmitterWebAuthnSender {
}
}

async handleSharedDirectoryCreateRequest(buffer: ArrayBuffer) {
const req = this.codec.decodeSharedDirectoryCreateRequest(buffer);

try {
await this.sdManager.create(req.path, req.fileType);
const info = await this.sdManager.getInfo(req.path);
this.sendSharedDirectoryCreateResponse({
completionId: req.completionId,
errCode: SharedDirectoryErrCode.Nil,
fso: this.toFso(info),
});
} catch (e) {
this.sendSharedDirectoryCreateResponse({
completionId: req.completionId,
errCode: SharedDirectoryErrCode.Failed,
fso: {
lastModified: BigInt(0),
fileType: FileType.File,
size: BigInt(0),
path: req.path,
},
});
this.handleError(e, TdpClientEvent.CLIENT_ERROR, false);
}
}

async handleSharedDirectoryReadRequest(buffer: ArrayBuffer) {
const req = this.codec.decodeSharedDirectoryReadRequest(buffer);
try {
Expand Down Expand Up @@ -454,6 +491,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));
}
Expand Down
66 changes: 66 additions & 0 deletions packages/teleport/src/lib/tdp/codec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ export enum MessageType {
SHARED_DIRECTORY_ACKNOWLEDGE = 12,
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,
Expand Down Expand Up @@ -124,6 +126,21 @@ 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 (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 |
export type SharedDirectoryReadRequest = {
completionId: number;
Expand Down Expand Up @@ -573,6 +590,31 @@ export default class Codec {
]).buffer;
}

// | message type (16) | completion_id uint32 | err_code uint32 | file_system_object fso |
encodeSharedDirectoryCreateResponse(
res: SharedDirectoryCreateResponse
): Message {
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);
offset += byteLength;
view.setUint32(offset, res.completionId);
offset += uint32Length;
view.setUint32(offset, res.errCode);
offset += uint32Length;

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 |
encodeSharedDirectoryReadResponse(res: SharedDirectoryReadResponse): Message {
const bufLen =
Expand Down Expand Up @@ -800,6 +842,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
Expand Down
4 changes: 2 additions & 2 deletions packages/teleport/src/lib/tdp/playerClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ export class PlayerClient extends Client {
}

// Overrides Client implementation.
processMessage(buffer: ArrayBuffer) {
async processMessage(buffer: ArrayBuffer): Promise<void> {
const json = JSON.parse(this.textDecoder.decode(buffer));

if (json.message === 'end') {
Expand All @@ -60,7 +60,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));
}
}

Expand Down
27 changes: 27 additions & 0 deletions packages/teleport/src/lib/tdp/sharedDirectoryManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Comment thread
ibeckermayer marked this conversation as resolved.

// SharedDirectoryManager manages a FileSystemDirectoryHandle for use
// by the TDP client. Most of its methods can potentially throw errors
Expand Down Expand Up @@ -143,6 +144,32 @@ export class SharedDirectoryManager {
return writeData.length;
}

/**
* Creates a new file or directory (determined by fileType) at path.
* 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<void> {
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
Expand Down