From 085378b7dc9c2df113f7bb389fcd2e195dbce846 Mon Sep 17 00:00:00 2001 From: Malloc of Kuzkycyziklistan Date: Tue, 23 May 2017 16:00:51 -0500 Subject: [PATCH] tfw you realize js doesn't support 8byte ints wow --- client/src/Connection.ts | 62 +++++++++-- client/src/Extensions.ts | 119 ++++++++++++++++----- client/src/FileCache.ts | 27 ++--- client/src/Packet.ts | 68 ++++++++++-- client/src/RenderContext.ts | 3 + protocol.md | 4 +- server/Libraries/Square/ArrayExtensions.cs | 5 +- server/Socks/Packet.cs | 8 +- 8 files changed, 230 insertions(+), 66 deletions(-) create mode 100644 client/src/RenderContext.ts diff --git a/client/src/Connection.ts b/client/src/Connection.ts index aa742e9..d5a7269 100644 --- a/client/src/Connection.ts +++ b/client/src/Connection.ts @@ -1,17 +1,61 @@ class Connection { - private static Sock: WebSocket = null; - private static _IsOpen: boolean = false; - public static get IsOpen(): boolean { - return Connection._IsOpen; + private static sock: WebSocket = null; + private static _isOpen: boolean = false; + private static onOpenFunc: () => void = null; + private static onCloseFunc: () => void = null; + public static get isOpen(): boolean { + return Connection._isOpen; } - public static Initialize(): void { - Connection.Sock + public static open(onOpen: () => void = null): void { + if(Connection._isOpen) + return; + + // FLAG replace hard coded url with one loaded from a config file + Connection.sock = new WebSocket("ws://localhost:6770"); + + Connection.onOpenFunc = onOpen; + Connection.sock.onopen = Connection.onOpen; + Connection.sock.onmessage = Connection.onMessage; + Connection.sock.onclose = Connection.onClose; } - public static Close(): void { - + private static onOpen(event: any): void { + Connection._isOpen = true; + + if(Connection.onOpenFunc) + Connection.onOpenFunc(); } - private static + private static onMessage(event: any): void { + var msg = Packet.fromBytes(event.data); + console.log(msg); + + switch(msg.id) { + case kPacketId.KeyExchange: + + break; + case kPacketId.LoginAttempt: + + break; + case kPacketId.RegistrationAttempt: + + break; + } + } + + private static onClose(event: any): void { + Connection._isOpen = false; + + if(Connection.onCloseFunc) + Connection.onCloseFunc(); + } + + public static close(onClose: () => void = null): void { + if(!Connection._isOpen) + return; + + Connection.onCloseFunc = onClose; + Connection.sock.close(); + } } \ No newline at end of file diff --git a/client/src/Extensions.ts b/client/src/Extensions.ts index aa94afd..cdd3ccb 100644 --- a/client/src/Extensions.ts +++ b/client/src/Extensions.ts @@ -28,13 +28,13 @@ String.prototype.replaceAll = function(needle: any, replace: any, ignoreCase: bo } return retval; } -}; +} String.prototype.contains = function(needle: string, ignoreCase: boolean = false): boolean { return ignoreCase ? this.toLowerCase().indexOf(needle.toLowerCase()) != -1 : this.indexOf(needle) != -1; -}; +} String.prototype.stripCharacters = function(chars: string) { var copy = this; @@ -42,18 +42,18 @@ String.prototype.stripCharacters = function(chars: string) { copy = copy.replaceAll(chars.split(""), ""); return copy; -}; +} String.prototype.hasUnicodeCharacters = function() { for(var i = 0; i < this.length; i++) { if(this.charCodeAt(i) > 127) return true; } return false; -}; +} String.prototype.byteLength = function() { return utf8.encode(this).length; -}; +} String.prototype.toByteArray = function() { var str = utf8.encode(this); @@ -62,7 +62,7 @@ String.prototype.toByteArray = function() { ret[i] = str.charCodeAt(i); return ret; -}; +} // ** DATE EXTENSIONS ** \\ @@ -78,27 +78,34 @@ interface Date { } Date.unixNow = function() { - return (new Date()).toUnixTime(); -}; + return (new Date).toUnixTime(); +} Date.prototype.toUnixTime = function() { return Math.floor(this.getTime()/1000); -}; +} /*Date.prototype.ToDateTimeString = function() { return this.toDateString() +" @ "+ this.getHours().zeroPad() +":"+ this.getMinutes().zeroPad() +":"+ this.getSeconds().zeroPad(); -}; +} Date.prototype.ToTimeString = function() { return this.getHours().zeroPad() +":"+ this.getMinutes().zeroPad() +":"+ this.getSeconds().zeroPad(); -};*/ +}*/ // ** NUMBER EXTENSIONS ** \\ interface Number { zeroPad(mag?: number): string; - packBytes(bytes: number): Uint8Array; + + packInt16(): Uint8Array; + packUint16(): Uint8Array; + packInt32(): Uint8Array; + packUint32(): Uint8Array; + + packFloat(): Uint8Array; + packDouble(): Uint8Array; } Number.prototype.zeroPad = function(mag: number = 1): string { @@ -106,28 +113,86 @@ Number.prototype.zeroPad = function(mag: number = 1): string { for(; this < Math.pow(10, mag) && mag != 0; --mag) ret = "0" + ret; return ret; -}; +} -Number.prototype.packBytes = function(bytes: number) { - var ret = new Uint8Array(bytes); - for(var i = 0; i < bytes; i++) - ret[i] = (this & (0xFF << 8 * (bytes - 1 - i))) >>> 8 * (bytes - 1 - i); - return ret; -}; +Number.prototype.packInt16 = function(): Uint8Array { + var buffer = new ArrayBuffer(2); + new DataView(buffer).setInt16(0, this, false); + return new Uint8Array(buffer); +} + +Number.prototype.packUint16 = function(): Uint8Array { + var buffer = new ArrayBuffer(2); + new DataView(buffer).setUint16(0, this, false); + return new Uint8Array(buffer); +} + +Number.prototype.packInt32 = function(): Uint8Array { + var buffer = new ArrayBuffer(4); + new DataView(buffer).setInt32(0, this, false); + return new Uint8Array(buffer); +} + +Number.prototype.packUint32 = function(): Uint8Array { + var buffer = new ArrayBuffer(4); + new DataView(buffer).setUint32(0, this, false); + return new Uint8Array(buffer); +} + +Number.prototype.packFloat = function(): Uint8Array { + var buffer = new ArrayBuffer(4); + new DataView(buffer).setFloat32(0, this, false); + return new Uint8Array(buffer); +} + +Number.prototype.packDouble = function(): Uint8Array { + var buffer = new ArrayBuffer(8); + new DataView(buffer).setFloat64(0, this, false); + return new Uint8Array(buffer); +} // ** UINT8ARRAY EXTENSIONS ** \\ interface Uint8Array { - unpackBytes(): number; + unpackInt16(offset?: number): number; + unpackUint16(offset?: number): number; + unpackInt32(offset?: number): number; + unpackUint32(offset?: number): number; + + unpackFloat(offset?: number): number; + unpackDouble(offset?: number): number; } -Uint8Array.prototype.unpackBytes = function(): number { - var ret = 0; - for(var i = 0; i < this.length; i++) - ret = ret | ((this[i] & 0xFF) << 8*(this.length - 1 - i)); - return ret; -}; +Uint8Array.prototype.unpackInt16 = function(offset: number = 0): number { + var buffer = this.buffer; + return new DataView(buffer).getInt16(offset, false); +} + +Uint8Array.prototype.unpackUint16 = function(offset: number = 0): number { + var buffer = this.buffer; + return new DataView(buffer).getUint16(offset, false); +} + +Uint8Array.prototype.unpackInt32 = function(offset: number = 0): number { + var buffer = this.buffer; + return new DataView(buffer).getInt32(offset, false); +} + +Uint8Array.prototype.unpackUint32 = function(offset: number = 0): number { + var buffer = this.buffer; + return new DataView(buffer).getUint32(offset, false); +} + +Uint8Array.prototype.unpackFloat = function(offset: number = 0): number { + var buffer = this.buffer; + return new DataView(buffer).getFloat32(offset, false); +} + +Uint8Array.prototype.unpackDouble = function(offset: number = 0): number { + var buffer = this.buffer; + return new DataView(buffer).getFloat64(offset, false); +} Uint8Array.prototype.toString = function(): string { var chunkSize = 4096; @@ -137,4 +202,4 @@ Uint8Array.prototype.toString = function(): string { raw += String.fromCharCode.apply(null, this.subarray(chunkSize*i, chunkSize*i + chunkSize)); } return utf8.decode(raw); -}; \ No newline at end of file +} \ No newline at end of file diff --git a/client/src/FileCache.ts b/client/src/FileCache.ts index 9e8053b..dce9442 100644 --- a/client/src/FileCache.ts +++ b/client/src/FileCache.ts @@ -2,18 +2,17 @@ class FileCache { private static dbHandle: IDBDatabase = null; public static initCache(success: ()=>void, error: (error: string)=>void): void { - var request = window.indexedDB.open("fileCache", 2); + var request = window.indexedDB.open("fileCache", 3); request.onupgradeneeded = (event: any) => { var db: IDBDatabase = event.target.result; - if(db.objectStoreNames.contains("hashes")) - db.deleteObjectStore("hashes"); - if(!db.objectStoreNames.contains("files")) - db.createObjectStore("files", {keyPath: "Name", autoIncrement: false}); + var stores = db.objectStoreNames; + for(var i in stores) + db.deleteObjectStore(stores[i]); - if(!db.objectStoreNames.contains("metadata")) - db.createObjectStore("metadata", {keyPath: "Name", autoIncrement: false}); + db.createObjectStore("files", {keyPath: "name", autoIncrement: false}); + db.createObjectStore("metadata", {keyPath: "name", autoIncrement: false}); }; request.onerror = (event: any) => { @@ -36,7 +35,7 @@ class FileCache { }; request.onerror = (event: any) => { - error(event); + error("Could not get metadata for file "+ fileName); }; } @@ -46,27 +45,29 @@ class FileCache { store.put(meta); } - public static getFile(fileName: string, success: (data: Uint8Array)=>void, error: (error: string)=>void): void { + public static getFile(fileName: string, success: (name: string, data: Uint8Array)=>void, error: (error: string)=>void): void { var query = FileCache.dbHandle.transaction("files"); var store = query.objectStore("files"); var request = store.get(fileName); request.onsuccess = () => { - success(request.result); + success(request.result.name, request.result.data); }; request.onerror = (event: any) => { - error(event); + error("Could not get contents for file "+ fileName); }; } public static setFile(fileName: string, data: Uint8Array) { var query = FileCache.dbHandle.transaction("files", "readwrite"); var store = query.objectStore("files"); - store.put(data, fileName); + store.put({name: fileName, data: data}); } } class FileMeta { - + name: string; + type: string; + hash: string; } \ No newline at end of file diff --git a/client/src/Packet.ts b/client/src/Packet.ts index dfd23d9..a332234 100644 --- a/client/src/Packet.ts +++ b/client/src/Packet.ts @@ -10,11 +10,6 @@ class Packet { return this._id; } - private _isLegal: boolean = true; - public get isLegal(): boolean { - return this._isLegal; - } - private _regions: Uint8Array[] = []; public get regions(): Uint8Array[] { return this._regions; @@ -28,9 +23,50 @@ class Packet { public getRegionString(region: number): string { return this.getRegion(region).toString(); } + public addRegion(data: Uint8Array): void { + this._regions.push(data); + } + + public Packet(id: kPacketId, regions: any[]) { + this._id = id; + regions.forEach(region => { + if(typeof region == "string") + this._regions.push((region).toByteArray()); + else if(region instanceof Uint8Array) + this._regions.push(region); + }); + } + + public static fromBytes(bytes: Uint8Array): Packet { + var packet = new Packet; + packet._id = bytes[0]; + var regionCount = bytes[1]; + var regionLengths = []; + var ptr = 2; + for(var i = 0; i < regionCount; ++i) { + if(bytes[ptr] < 0xFE) + regionLengths.push(bytes[ptr]); + else if(bytes[ptr] == 0xFE) { + regionLengths.push(bytes.unpackUint16(ptr + 1)); + ptr += 2; + } else { + regionLengths.push(bytes.unpackUint32(ptr + 1)); + ptr += 4; + } + + ++ptr; + } + + for(var i = 0; i < regionCount; ++i) { + packet.regions.push(bytes.subarray(ptr, ptr + regionLengths[i])); + ptr += regionLengths[i]; + } + + return packet; + } public getBytes(): Uint8Array { - var headerSize = 1, bodySize = 0; + var headerSize = 2, bodySize = 0; this._regions.forEach(region => { bodySize += region.byteLength; @@ -42,10 +78,26 @@ class Packet { }); var buffer = new Uint8Array(headerSize + bodySize); - var headerPtr = 1, bodyPtr = 0; + var headerPtr = 2, bodyPtr = headerSize; buffer[0] = this._id % 256; + buffer[1] = this._regions.length; this._regions.forEach(region => { - + var regionLength = region.byteLength; + if(regionLength < 0xFE) + buffer[headerPtr] = regionLength; + else if(regionLength >= 0xFE && regionLength <= 0xFFFF) { + buffer[headerPtr] = 0xFE; + buffer.set(regionLength.packUint16(), headerPtr + 1); + headerPtr += 2; + } else { + buffer[headerPtr] = 0xFF; + buffer.set(regionLength.packUint32(), headerPtr + 1); + headerPtr += 4; + } + ++headerPtr; + + buffer.set(region, bodyPtr); + bodyPtr += regionLength; }); return buffer; diff --git a/client/src/RenderContext.ts b/client/src/RenderContext.ts new file mode 100644 index 0000000..2db7518 --- /dev/null +++ b/client/src/RenderContext.ts @@ -0,0 +1,3 @@ +class RenderContext { + +} \ No newline at end of file diff --git a/protocol.md b/protocol.md index 416ecbe..f8bb3e3 100644 --- a/protocol.md +++ b/protocol.md @@ -27,7 +27,7 @@ The message body immediately follows the header with no separator, and consists All numbers, unless otherwise specified, are the string representation of a base 10 number. Common exceptions are listed below: -* User IDs: 8 bytes, integer, unsigned +* User IDs: Hex string, 8 bytes unsigned * Co-ordinates: 8 bytes, double-precision float * Big Int: Hex string, variable size @@ -255,7 +255,7 @@ A packet ID may have a specific "direction" of communication, in that an endpoin Because epoch time is not standardized across systems, an intermediate layer of date/time transmission must be used between the client and server so as to handle time dependent interactions. Therefore, a "sockstamp" will be used in place of the context-dependent implementations of epoch time. -A sockstamp is a sequence of six bytes that represent a fully qualified date and time on the Gregorian calendar. For the best use of space without obfuscating the data too much, the year's lower four bits and the four bits signifying the month are shared in the same byte, but no other components are joined. +A sockstamp is a sequence of six bytes that represent a fully qualified UTC date and time on the Gregorian calendar. For the best use of space without obfuscating the data too much, the year's lower four bits and the four bits signifying the month are shared in the same byte, but no other components are joined. The 12 bits signifying the year are an unsigned quanitity, and indicate the number of years since 0 AD; any date prior to the year of Christ's birth cannot be represented in this format, but this should never be necessary. The effective range of years that can be expressed by this format is 1 AD to 4095 AD. Because the year 0 AD is not a legal year in the Gregorian calendar, this value should never be zero. diff --git a/server/Libraries/Square/ArrayExtensions.cs b/server/Libraries/Square/ArrayExtensions.cs index 9e6f00f..305e8a1 100644 --- a/server/Libraries/Square/ArrayExtensions.cs +++ b/server/Libraries/Square/ArrayExtensions.cs @@ -41,10 +41,7 @@ namespace Square { } public static byte[] NetworkToHostOrder(this byte[] bytes) { - if(BitConverter.IsLittleEndian) - return bytes.Reverse().ToArray(); - else - return bytes; + return bytes.HostToNetworkOrder(); } public static Single UnpackFloat(this byte[] bytes) diff --git a/server/Socks/Packet.cs b/server/Socks/Packet.cs index dcc00cc..9a841e5 100644 --- a/server/Socks/Packet.cs +++ b/server/Socks/Packet.cs @@ -91,15 +91,17 @@ namespace Server { return null; var header = new List(); + header.Add((byte)Id); + header.Add((byte)RegionCount); IEnumerable body = new byte[0]; foreach(var region in Regions) { - if(region.Length < 254) + if(region.Length < 0xFE) header.Add((byte)region.Length); else if(region.Length <= 0xFFFF) { - header.Add(254); + header.Add(0xFE); header.AddRange(((UInt16)region.Length).Pack()); } else { - header.Add(255); + header.Add(0xFF); header.AddRange(region.Length.Pack()); }