Skip to content
This repository has been archived by the owner on Feb 8, 2024. It is now read-only.

Commit

Permalink
updating to use browser TextEncoder api. Unfortunately its another on…
Browse files Browse the repository at this point in the history
…e jsdom hasn't caught up to yet
  • Loading branch information
ibeckermayer committed Aug 17, 2021
1 parent 0d25ce5 commit d13b31d
Show file tree
Hide file tree
Showing 3 changed files with 130 additions and 179 deletions.
212 changes: 105 additions & 107 deletions packages/teleport/src/lib/DesktopAccess/codec.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,125 +9,123 @@ function getRandomInt(min, max) {
describe('codec', () => {
const codec = new Codec();

describe('encoding', () => {
it('encodes the screen spec', () => {
const w = getRandomInt(10, 100);
const h = getRandomInt(10, 100);
const message = codec.encScreenSpec(w, h);
const view = new DataView(message);
expect(view.getUint8(0)).toEqual(MessageType.CLIENT_SCREEN_SPEC);
expect(view.getUint32(1)).toEqual(w);
expect(view.getUint32(5)).toEqual(h);
});
test('encodes the screen spec', () => {
const w = getRandomInt(10, 100);
const h = getRandomInt(10, 100);
const message = codec.encScreenSpec(w, h);
const view = new DataView(message);
expect(view.getUint8(0)).toEqual(MessageType.CLIENT_SCREEN_SPEC);
expect(view.getUint32(1)).toEqual(w);
expect(view.getUint32(5)).toEqual(h);
});

it('encodes mouse moves', () => {
const x = getRandomInt(0, 100);
const y = getRandomInt(0, 100);
const message = codec.encMouseMove(x, y);
const view = new DataView(message);
expect(view.getUint8(0)).toEqual(MessageType.MOUSE_MOVE);
expect(view.getUint32(1)).toEqual(x);
expect(view.getUint32(5)).toEqual(y);
});
test('encodes mouse moves', () => {
const x = getRandomInt(0, 100);
const y = getRandomInt(0, 100);
const message = codec.encMouseMove(x, y);
const view = new DataView(message);
expect(view.getUint8(0)).toEqual(MessageType.MOUSE_MOVE);
expect(view.getUint32(1)).toEqual(x);
expect(view.getUint32(5)).toEqual(y);
});

it('encodes mouse buttons', () => {
[0, 1, 2].forEach(button => {
[ButtonState.DOWN, ButtonState.UP].forEach(state => {
const message = codec.encMouseButton(button as MouseButton, state);
const view = new DataView(message);
expect(view.getUint8(0)).toEqual(MessageType.MOUSE_BUTTON);
expect(view.getUint8(1)).toEqual(button);
expect(view.getUint8(2)).toEqual(state);
});
test('encodes mouse buttons', () => {
[0, 1, 2].forEach(button => {
[ButtonState.DOWN, ButtonState.UP].forEach(state => {
const message = codec.encMouseButton(button as MouseButton, state);
const view = new DataView(message);
expect(view.getUint8(0)).toEqual(MessageType.MOUSE_BUTTON);
expect(view.getUint8(1)).toEqual(button);
expect(view.getUint8(2)).toEqual(state);
});
});
});

describe('encodes username and password', () => {
// Tests for more than we probably need to support for username and password.
// Tests inspired by https://github.com/google/closure-library/blob/master/closure/goog/crypt/crypt_test.js (Apache License)
it('encodes typical characters', () => {
// Create test vals + known UTF8 encodings
const username = 'Hello';
const usernameUTF8 = [0x0048, 0x0065, 0x006c, 0x006c, 0x006f];
const password = 'world!*@123';
const passwordUTF8 = [
0x0077,
0x006f,
0x0072,
0x006c,
0x0064,
0x0021,
0x002a,
0x0040,
0x0031,
0x0032,
0x0033,
];
// Username/password tests inspired by https://github.com/google/closure-library/blob/master/closure/goog/crypt/crypt_test.js (Apache License)
// Test skipped until jsdom adds support for TextEncoder (https://github.com/jsdom/jsdom/issues/2524)
// eslint-disable-next-line jest/no-disabled-tests
test.skip('encodes typical characters for username and password', () => {
// Create test vals + known UTF8 encodings
const username = 'Hello';
const usernameUTF8 = [0x0048, 0x0065, 0x006c, 0x006c, 0x006f];
const password = 'world!*@123';
const passwordUTF8 = [
0x0077,
0x006f,
0x0072,
0x006c,
0x0064,
0x0021,
0x002a,
0x0040,
0x0031,
0x0032,
0x0033,
];

// Encode test vals
const message = codec.encUsernamePassword(username, password);
const view = new DataView(message);
// Encode test vals
const message = codec.encUsernamePassword(username, password);
const view = new DataView(message);

// Walk through output
let offset = 0;
expect(view.getUint8(offset++)).toEqual(
MessageType.USERNAME_PASSWORD_RESPONSE
);
expect(view.getUint32(offset)).toEqual(usernameUTF8.length);
offset += 4;
usernameUTF8.forEach(byte => {
expect(view.getUint8(offset++)).toEqual(byte);
});
expect(view.getUint32(offset)).toEqual(passwordUTF8.length);
offset += 4;
passwordUTF8.forEach(byte => {
expect(view.getUint8(offset++)).toEqual(byte);
});
});
// Walk through output
let offset = 0;
expect(view.getUint8(offset++)).toEqual(
MessageType.USERNAME_PASSWORD_RESPONSE
);
expect(view.getUint32(offset)).toEqual(usernameUTF8.length);
offset += 4;
usernameUTF8.forEach(byte => {
expect(view.getUint8(offset++)).toEqual(byte);
});
expect(view.getUint32(offset)).toEqual(passwordUTF8.length);
offset += 4;
passwordUTF8.forEach(byte => {
expect(view.getUint8(offset++)).toEqual(byte);
});
});

it('encodes utf8 characters correctly up to 3 bytes', () => {
const first3RangesString = '\u0000\u007F\u0080\u07FF\u0800\uFFFF';
const first3RangesUTF8 = [
0x00,
0x7f,
0xc2,
0x80,
0xdf,
0xbf,
0xe0,
0xa0,
0x80,
0xef,
0xbf,
0xbf,
];
const message = codec.encUsernamePassword(
first3RangesString,
first3RangesString
);
const view = new DataView(message);
let offset = 0;
expect(view.getUint8(offset++)).toEqual(
MessageType.USERNAME_PASSWORD_RESPONSE
);
expect(view.getUint32(offset)).toEqual(first3RangesUTF8.length);
offset += 4;
first3RangesUTF8.forEach(byte => {
expect(view.getUint8(offset++)).toEqual(byte);
});
expect(view.getUint32(offset)).toEqual(first3RangesUTF8.length);
offset += 4;
first3RangesUTF8.forEach(byte => {
expect(view.getUint8(offset++)).toEqual(byte);
});
});
// Test skipped until jsdom adds support for TextEncoder (https://github.com/jsdom/jsdom/issues/2524)
// eslint-disable-next-line jest/no-disabled-tests
test.skip('encodes utf8 characters correctly up to 3 bytes for username and password', () => {
const first3RangesString = '\u0000\u007F\u0080\u07FF\u0800\uFFFF';
const first3RangesUTF8 = [
0x00,
0x7f,
0xc2,
0x80,
0xdf,
0xbf,
0xe0,
0xa0,
0x80,
0xef,
0xbf,
0xbf,
];
const message = codec.encUsernamePassword(
first3RangesString,
first3RangesString
);
const view = new DataView(message);
let offset = 0;
expect(view.getUint8(offset++)).toEqual(
MessageType.USERNAME_PASSWORD_RESPONSE
);
expect(view.getUint32(offset)).toEqual(first3RangesUTF8.length);
offset += 4;
first3RangesUTF8.forEach(byte => {
expect(view.getUint8(offset++)).toEqual(byte);
});
expect(view.getUint32(offset)).toEqual(first3RangesUTF8.length);
offset += 4;
first3RangesUTF8.forEach(byte => {
expect(view.getUint8(offset++)).toEqual(byte);
});
});

describe('decoding', () => {
it.todo(`TODO: jest uses jsdom to emulate a browser environment during the tests, but jsdom does not currently
// todo until jsdom adds support for Blob.arrayBuffer() (https://github.com/jsdom/jsdom/issues/2555)
test.todo(`TODO: decoding -- jest uses jsdom to emulate a browser environment during the tests, but jsdom does not currently
support Blob.arrayBuffer() (used in our decoding functions) and thus it is difficult to test.
I think I've come up with a hacky workaround but @awly and I agreed to put this aside
for the time being; all will be tested manually for now by necessity during development.`);
});
});
50 changes: 25 additions & 25 deletions packages/teleport/src/lib/DesktopAccess/codec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import { stringToUtf8ByteArray } from "./stringToUtf8ByteArray";

export type Message = ArrayBuffer;

export enum MessageType {
Expand Down Expand Up @@ -43,7 +41,7 @@ export default class Codec {

private _keyScancodes = {
Unidentified: 0x0000,
"": 0x0000,
'': 0x0000,
Escape: 0x0001,
Digit0: 0x0002,
Digit1: 0x0003,
Expand Down Expand Up @@ -195,9 +193,9 @@ export default class Codec {
encScreenSpec(w: number, h: number): Message {
const buffer = new ArrayBuffer(9);
const view = new DataView(buffer);
view.setUint8(0, MessageType.CLIENT_SCREEN_SPEC)
view.setUint32(1, w)
view.setUint32(5, h)
view.setUint8(0, MessageType.CLIENT_SCREEN_SPEC);
view.setUint32(1, w);
view.setUint32(5, h);
return buffer;
}

Expand Down Expand Up @@ -239,29 +237,31 @@ export default class Codec {
// encUsernamePassword encodes the username and password response
encUsernamePassword(username: string, password: string): Message {
// Encode username/pass to utf8
const usernameUtf8array = stringToUtf8ByteArray(username)
const passwordUtf8array = stringToUtf8ByteArray(password)
let encoder = new TextEncoder();
const usernameUtf8array = encoder.encode(username);
const passwordUtf8array = encoder.encode(password);

// initialize buffer and corresponding view
// numbers correspond to message spec
// | message type (8) | user_length uint32 | username []byte | pass_length uint32 | password []byte
const bufLen = 1 + 4 + usernameUtf8array.length + 4 + passwordUtf8array.length
const buffer = new ArrayBuffer(bufLen)
const view = new DataView(buffer)
let offset = 0
const bufLen =
1 + 4 + usernameUtf8array.length + 4 + passwordUtf8array.length;
const buffer = new ArrayBuffer(bufLen);
const view = new DataView(buffer);
let offset = 0;

// set data
view.setUint8(offset++, MessageType.USERNAME_PASSWORD_RESPONSE)
view.setUint32(offset, usernameUtf8array.length)
offset += 4 // 4 bytes to offset 32-bit uint
view.setUint8(offset++, MessageType.USERNAME_PASSWORD_RESPONSE);
view.setUint32(offset, usernameUtf8array.length);
offset += 4; // 4 bytes to offset 32-bit uint
usernameUtf8array.forEach(byte => {
view.setUint8(offset++, byte)
})
view.setUint32(offset, passwordUtf8array.length)
offset += 4
view.setUint8(offset++, byte);
});
view.setUint32(offset, passwordUtf8array.length);
offset += 4;
passwordUtf8array.forEach(byte => {
view.setUint8(offset++, byte)
})
view.setUint8(offset++, byte);
});

return buffer;
}
Expand All @@ -270,13 +270,13 @@ export default class Codec {
// TODO: need to iterate on protocol in order to syncronize clipboards
// see https://gravitational.slack.com/archives/D0275RJQHUY/p1629130769002200
encClipboard() {
throw new Error("Not implemented")
throw new Error('Not implemented');
}

// decClipboard decodes clipboard data
// TODO: see docstring for encClipboard
decClipboard() {
throw new Error("Not implemented")
throw new Error('Not implemented');
}

// decMessageType decodes the MessageType from a raw blob of data
Expand All @@ -287,7 +287,7 @@ export default class Codec {
return blob
.slice(0, 1)
.arrayBuffer()
.then((buffer) => {
.then(buffer => {
const messageType = new DataView(buffer).getUint8(0);
if (messageType === MessageType.PNG_FRAME) {
return MessageType.PNG_FRAME;
Expand All @@ -307,7 +307,7 @@ export default class Codec {
return blob
.slice(1, 17)
.arrayBuffer()
.then((buf) => {
.then(buf => {
let dv = new DataView(buf);
return {
left: dv.getUint32(0),
Expand Down
47 changes: 0 additions & 47 deletions packages/teleport/src/lib/DesktopAccess/stringToUtf8ByteArray.ts

This file was deleted.

0 comments on commit d13b31d

Please sign in to comment.