diff --git a/server/common.test.ts b/server/common.test.ts index 4cb9800..1a513d0 100644 --- a/server/common.test.ts +++ b/server/common.test.ts @@ -36,6 +36,13 @@ test("Test Number Processor Function", () => { expect(type.numberToBytes(0x12345678, 4).unwrap()).toEqual(new Uint8Array([0x78, 0x56, 0x34, 0x12])) expect(type.numberToBytes(0x12345678, 4, true).unwrap()).toEqual(new Uint8Array([0x12, 0x34, 0x56, 0x78])) + // Convert Uint8Array to Number + expect(type.bytesToNumber(new Uint8Array([0xFF]))).toBe(255) + expect(type.bytesToNumber(new Uint8Array([0xAA, 0xAA]))).toEqual(0xAAAA) + expect(type.bytesToNumber(new Uint8Array([0x78, 0x56, 0x34, 0x12]))).toEqual(0x12345678) + expect(type.bytesToNumber(new Uint8Array([0x12, 0x34, 0x56, 0x78]), true)).toEqual(0x12345678) + + // Number Match for (let i = 0; i < CYCLES; i++) { const num1 = _.random(CYCLES / 2, false) diff --git a/server/common.ts b/server/common.ts index 6264b2e..f8b5d9d 100644 --- a/server/common.ts +++ b/server/common.ts @@ -28,7 +28,7 @@ export namespace type { export const Int16 = Integer.lt(Math.pow(2, 15)).gte(-Math.pow(2, 16)) export const Int32 = Integer.lt(Math.pow(2, 31)).gte(-Math.pow(2, 32)) - export function numberToBytes(num: number, bytesLength: number, reverse: boolean = false) + export function numberToBytes(num: number, bytesLength: number, isRightHigh: boolean = false) : Result { // Check Integer if (!Int32.safeParse(num).success && !UInt32.lte(32).safeParse(bytesLength).success) { @@ -39,7 +39,7 @@ export namespace type { var array = new Uint8Array(bytesLength) - if (reverse) { + if (isRightHigh) { for (let i = 0; i < bytesLength; i++) { array[bytesLength - 1 - i] = ((num >> (i << 3)) & 0xFF) } @@ -52,6 +52,21 @@ export namespace type { return new Ok(array) } + export function bytesToNumber(bytes: Uint8Array, isRightHigh: boolean = false): number { + let num = 0 + const len = bytes.length + if (isRightHigh) { + for (let i = 0; i < len; i++) { + num += bytes[len - 1 - i] << (i << 3) + } + } else { + for (let i = 0; i < len; i++) { + num += bytes[i] << (i << 3) + } + } + return num + } + export function numberMatch( srcNum: number, diff --git a/server/udp.ts b/server/udp.ts index 1a56ab2..33c67a4 100644 --- a/server/udp.ts +++ b/server/udp.ts @@ -1,10 +1,15 @@ import { resolve } from "path"; -import { type udp } from "bun"; import Tinypool from "tinypool"; +import type { UDPReceiveProcotol, UDPSendProtocol } from "./udpProtocol"; type UDPDataType = { address: string, - data: udp.Data, + data: ( + UDPSendProtocol.CmdPackage | + UDPSendProtocol.DataPackage | + UDPReceiveProcotol.ReadPackage | + UDPReceiveProcotol.WritePackage + ), port: number, date?: string, } diff --git a/server/udpClient.ts b/server/udpClient.ts index 5fc65c6..cf4cd1d 100644 --- a/server/udpClient.ts +++ b/server/udpClient.ts @@ -3,6 +3,6 @@ import type { UDPDataType } from "./udp" const udpClient = await Bun.udpSocket({}) export function send(data: UDPDataType): boolean { - return udpClient.send(data.data, data.port, data.address) + return udpClient.send(data.data.toUint8Array(), data.port, data.address) } diff --git a/server/udpProtocol.ts b/server/udpProtocol.ts index e75d06d..67b7aa3 100644 --- a/server/udpProtocol.ts +++ b/server/udpProtocol.ts @@ -1,52 +1,86 @@ import { type } from "./common"; import { z } from "zod"; import _ from "lodash"; +import { Err, Ok, type Result } from "ts-results-es"; +import { isUint8Array } from "util/types"; -export namespace SendProtocol { - const CMD_BURST_TYPE = 0b1100_0000 - const CMD_TASK_ID = 0b0011_0000 - const CMD_READ_WRITE_TYPE = 0b0000_0001 +const PKG_SIGN_ADDR = 0x00 +const PKG_SIGN_DATA = 0xFF +const PKG_SIGN_READ = 0x0F +const PKG_SIGN_WRITE = 0xF0 - const AddrPackageOptionsSchema = z.object({ +export namespace UDPSendProtocol { + + const CMDLOC_BURST_TYPE = 5 + const CMDLOC_TASK_ID = 3 + const CMDLOC_READ_WRITE_TYPE = 0 + + const CmdPackageOptionsSchema = z.object({ burstType: z.enum(["Fixed", "Extend"]), taskID: z.number().nonnegative().lt(4), readWriteType: z.enum(["r", "w"]) }) - export type AddrPackageOptions = z.infer + export type CmdPackageOptions = z.infer - export function isAddrPackageOptions(obj: any): obj is AddrPackageOptions { - return AddrPackageOptionsSchema.safeParse(obj).success + export function isCmdPackageOptions(obj: any): obj is CmdPackageOptions { + return CmdPackageOptionsSchema.safeParse(obj).success } - export class AddrPackage { - private ID: number = 0x00 + export class CmdPackage { + private ID: number = PKG_SIGN_ADDR private commandType: number = 0 private burstLength: number = 0 private _reserved: number = 0 private address: number = 0 - constructor(options: AddrPackageOptions) { - this.setCommandType(options) + + constructor(array: Uint8Array) + constructor(options: CmdPackageOptions) + constructor(arg: any) { + if (isCmdPackageOptions(arg)) { + this.setCommandType(arg) + } else if (isUint8Array(arg)) { + this.ID = arg[0] + this.commandType = arg[1] + this.burstLength = arg[2] + this._reserved = arg[3] + this.address = type.bytesToNumber(arg.slice(4)) + } else { + throw new Err("Wrong Type") + } } - setCommandType(options: AddrPackageOptions) { - const validOptions = AddrPackageOptionsSchema.parse(options) - this.commandType = + setCommandType(options: CmdPackageOptions) { + const validOptions = CmdPackageOptionsSchema.parse(options) + this.commandType = ( + ((validOptions.burstType === "Fixed") ? 1 << CMDLOC_BURST_TYPE : 0) | + (validOptions.taskID << CMDLOC_TASK_ID) | + ((validOptions.readWriteType === "r") ? 1 << CMDLOC_READ_WRITE_TYPE : 0) + ) } - setBurstLength(len: number) { + setBurstLength(len: number): Result { + if (!type.UInt8.safeParse(len).success) { + return new Err("Not 8Bits Unsigned Integer") + } this.burstLength = len + return new Ok(null) } - setAddress(addr: number) { + setAddress(addr: number): Result { + if (!type.UInt32.safeParse(addr).success) { + return new Err("Not 32Bits Unsigned Integer") + } this.address = addr + return new Ok(null) } - options(): AddrPackageOptions { + options(): CmdPackageOptions { return { - burstType: type.numberMatch(this.commandType, CMD_BURST_TYPE, "Extend", "Fixed"), - readWriteType: type.numberMatch(this.commandType, CMD_READ_WRITE_TYPE, "w", "r") + burstType: type.numberMatch(this.commandType, CMDLOC_BURST_TYPE, "Extend", "Fixed"), + taskID: (this.commandType >> CMDLOC_TASK_ID) & 0b0011, + readWriteType: type.numberMatch(this.commandType, CMDLOC_READ_WRITE_TYPE, "w", "r") } } @@ -58,18 +92,16 @@ export namespace SendProtocol { array[2] = this.burstLength array[3] = this._reserved - let addressBytes = type.numberToBytes(this.address, 4) - array[4] = addressBytes[0] - array[5] = addressBytes[1] - array[6] = addressBytes[2] - array[7] = addressBytes[3] + // Already check address length at the begining of set address + let addressBytes = type.numberToBytes(this.address, 4).unwrap() + array.set(addressBytes, 4) return array } } export class DataPackage { - private ID: number = 0xFF + private ID: number = PKG_SIGN_DATA private _reserved: number = 0 private body: Uint8Array @@ -90,4 +122,63 @@ export namespace SendProtocol { } -export namespace ReceiveProcotol { } +export namespace UDPReceiveProcotol { + + export class ReadPackage { + private ID: number = PKG_SIGN_READ + private taskID: number = 0 + private resp: number = 0 + private _reserved: number = 0 + body: Uint8Array + + constructor(array: Uint8Array) { + if (array.length < 5) { + throw new Err("Not Long Enough") + } + this.ID = array[0] + this.taskID = array[1] + this.resp = array[2] + this._reserved = array[3] + this.body = new Uint8Array(array.slice(4)) + } + + toUint8Array(): Uint8Array { + let array = new Uint8Array(4 + this.body.length) + + array[0] = this.ID + array[1] = this.taskID + array[2] = this.resp + array[3] = this._reserved + array.set(this.body, 4) + + return array + } + } + + export class WritePackage { + private ID: number = PKG_SIGN_WRITE + private taskID: number = 0 + private resp: number = 0 + private _reserved: number = 0 + + + constructor(array: Uint8Array) { + if (array.length < 4) { + throw new Err("Not Long Enough") + } + this.ID = array[0] + this.taskID = array[1] + this.resp = array[2] + this._reserved = array[3] + } + + toUint8Array(): Uint8Array { + return new Uint8Array([ + this.ID, + this.taskID, + this.resp, + this._reserved + ]) + } + } +}