From 3d462f88aaca02565cafbd470c2e1600f4312afc Mon Sep 17 00:00:00 2001 From: SikongJueluo Date: Thu, 27 Mar 2025 18:00:08 +0800 Subject: [PATCH] rewrite udp and its protocol, also add some common function and type --- server/common.ts | 78 +++++++++++++++++++++++----- server/udp.ts | 28 +++++++++- server/udpClient.ts | 32 ++---------- server/udpPackage.ts | 116 ------------------------------------------ server/udpProtocol.ts | 93 +++++++++++++++++++++++++++++++++ server/udpServer.ts | 76 ++++++++++++++++++++++++--- 6 files changed, 258 insertions(+), 165 deletions(-) delete mode 100644 server/udpPackage.ts create mode 100644 server/udpProtocol.ts diff --git a/server/common.ts b/server/common.ts index 63effc7..18fc612 100644 --- a/server/common.ts +++ b/server/common.ts @@ -1,30 +1,73 @@ import _ from "lodash" -import { Option, Some, None } from "ts-results-es"; +import { Option, Some, None, Result, Ok, Err } from "ts-results-es"; import { z } from "zod"; -export const uint8 = z.number().nonnegative().lt(Math.pow(2, 8)) -export const uint16 = z.number().nonnegative().lt(Math.pow(2, 16)) -export const uint32 = z.number().nonnegative().lt(Math.pow(2, 32)) - -export const int8 = z.number().lt(Math.pow(2, 7)).gte(-Math.pow(2, 8)) -export const int16 = z.number().lt(Math.pow(2, 15)).gte(-Math.pow(2, 16)) -export const int32 = z.number().lt(Math.pow(2, 31)).gte(-Math.pow(2, 32)) - export function UNUSED(_: unknown): void { } -export namespace fun { - export function numberToBytes(num: number, bytesLength: number): Uint8Array { +export namespace type { + + export const Integer = z.number().int() + export const UInteger = z.number().int().nonnegative() + + export const UInt8 = z.number().int().nonnegative().lt(Math.pow(2, 8)) + export const UInt16 = z.number().int().nonnegative().lt(Math.pow(2, 16)) + export const UInt32 = z.number().int().nonnegative().lt(Math.pow(2, 32)) + + export const Int8 = z.number().int().lt(Math.pow(2, 7)).gte(-Math.pow(2, 8)) + export const Int16 = z.number().int().lt(Math.pow(2, 15)).gte(-Math.pow(2, 16)) + export const Int32 = z.number().int().lt(Math.pow(2, 31)).gte(-Math.pow(2, 32)) + + export function numberToBytes(num: number, bytesLength: number): Result { + // Check Integer + if (!(Integer.safeParse(num).success && Integer.safeParse(bytesLength).success)) { + return new Err("Not Integer") + } + var array = new Uint8Array(bytesLength) for (let i = 0; i < bytesLength; i++) { array[i] = num & (0xFF << (i << 3)) } - return array + return new Ok(array) } - export function randomFromArray(array: Array) { - return array[_.random(0, array.length - 1, false)] + + export function numberMatch( + srcNum: number, + destNum: number + ): boolean; + + export function numberMatch( + srcNum: number, + destNum: number, + True: T, + False: T + ): T; + + export function numberMatch( + srcNum: number, + destNum: number, + True: T = true as T, + False: T = false as T + ): T { + const ret = (srcNum & destNum) === destNum; + return ret ? True : False; + } + + export function numberMatchEnum() { + + } + + export function numberSetBit(num: number, loc: number): number { + return num | (1 << loc) + } + + export function numberUnsetBit(num: number, loc: number): number { + return num | (~1 << loc) + } + + export function numberHighBits(num: number) { } export function isStringArray(obj: any): obj is Array { @@ -34,6 +77,13 @@ export namespace fun { export function isNumberArray(obj: any): obj is Array { return z.number().array().safeParse(obj).success } +} + +export namespace fun { + + export function randomFromArray(array: Array) { + return array[_.random(0, array.length - 1, false)] + } export function sqlConditionFromArray( columnName: string, diff --git a/server/udp.ts b/server/udp.ts index 1622af1..1a56ab2 100644 --- a/server/udp.ts +++ b/server/udp.ts @@ -1,11 +1,35 @@ import { resolve } from "path"; +import { type udp } from "bun"; import Tinypool from "tinypool"; +type UDPDataType = { + address: string, + data: udp.Data, + port: number, + date?: string, +} + const udpClientsPool = new Tinypool({ filename: resolve(__dirname, "./udpClient.ts"), workerData: {} }) -export function send() { - udpClientsPool.run({}, { name: "send" }) +const udpServer = new Tinypool({ + filename: resolve(__dirname, "./udpServer.ts"), + workerData: {}, + maxThreads: 1, + minThreads: 1, +}) + +async function udpServerPort(): Promise { + return udpServer.run(null, { name: "port" }) } + +async function uploadBitStream(): Promise { + let ret = await udpClientsPool.run({} as UDPDataType, { name: "send" }) + + return ret +} + +export { udpServerPort, uploadBitStream } +export type { UDPDataType } diff --git a/server/udpClient.ts b/server/udpClient.ts index 2bb7828..5fc65c6 100644 --- a/server/udpClient.ts +++ b/server/udpClient.ts @@ -1,30 +1,8 @@ -import { type udp } from "bun"; -import { MsgProtocol } from "./msgProtocol"; -import { parentPort } from "worker_threads"; - -declare var self: Worker +import type { UDPDataType } from "./udp" const udpClient = await Bun.udpSocket({}) -parentPort?.on("message", (msg: MsgProtocol.MessageQuery) => { - if (MsgProtocol.isMessageQuery(msg)) { - switch (msg.command) { - case "send": { - const args = msg.data.args as { - data: udp.Data, - port: number, - address: string, - } - udpClient.send(args.data, args.port, args.address) - postMessage(MsgProtocol.genMessageResult("Send Successfully", msg)) - break - } - default: { - postMessage(MsgProtocol.genMessageError("No Such Command", msg)) - break - } - } - } else { - postMessage(MsgProtocol.genMessageError("No Currenct Destination", msg)) - } -}) +export function send(data: UDPDataType): boolean { + return udpClient.send(data.data, data.port, data.address) +} + diff --git a/server/udpPackage.ts b/server/udpPackage.ts deleted file mode 100644 index a914c9a..0000000 --- a/server/udpPackage.ts +++ /dev/null @@ -1,116 +0,0 @@ -import { fun } from "./common"; -import { z } from "zod"; -import _ from "lodash"; - -export namespace EquipmentPackage { - const HEADER_LENGTH = 8 - const BYTES_RETURN_ACK = 0b0001_0000 - const BYTES_TRANSFORM_TYPE = 0b0000_1000 - const BYTES_READ_WRITE_TYPE = 0b0000_0100 - - const HeaderOptionsSchema = z.object({ - returnAck: z.boolean(), - transformType: z.enum(["Fixed", "Extend"]), - readWriteType: z.enum(["r", "w"]) - }).partial() - - export type HeaderOptions = z.infer - - export function isHeaderOptions(obj: any): obj is HeaderOptions { - return HeaderOptionsSchema.safeParse(obj).success - } - - export class Header { - private commandType: number = 0 - private bytesLength: number = 0 - private commmandID: number = 0 - private _reserved: number = 0 - private address: number = 0 - - constructor(options: HeaderOptions) { - this.setCommandType(options) - } - - setCommandType(options: HeaderOptions) { - const validOptions = HeaderOptionsSchema.parse(options) - this.commandType = - (validOptions.returnAck === true ? BYTES_RETURN_ACK : 0) | - (validOptions.transformType === "Extend" ? BYTES_TRANSFORM_TYPE : 0) | - (validOptions.readWriteType === "w" ? BYTES_READ_WRITE_TYPE : 0) - } - - setBytesLength(length: number) { - this.bytesLength = length - } - - setAddress(address: number) { - this.address = address - } - - getCommandType(): HeaderOptions { - return { - returnAck: ((this.commandType & BYTES_RETURN_ACK) === BYTES_RETURN_ACK ? true : false), - transformType: ((this.commandType & BYTES_TRANSFORM_TYPE) === BYTES_TRANSFORM_TYPE ? "Extend" : "Fixed"), - readWriteType: ((this.commandType & BYTES_READ_WRITE_TYPE) === BYTES_READ_WRITE_TYPE ? "w" : "r") - } - } - - incCommandID() { - this.commmandID++ - } - - clearCommandID() { - this.commmandID = 0 - } - - toUint8Array(): Uint8Array { - var array = new Uint8Array(HEADER_LENGTH) - - array[0] = this.commandType - array[1] = this.bytesLength - array[2] = this.commmandID - array[3] = this._reserved - array[4] = this._reserved - - let addressBytes = fun.numberToBytes(this.address, 3) - array[5] = addressBytes[0] - array[6] = addressBytes[1] - array[7] = addressBytes[2] - - return array - } - } - - export class Package { - - private header: Header - private body: Uint8Array - - - constructor(header: Header | HeaderOptions, body?: Uint8Array) { - if (header instanceof Header) { - this.header = header - } else if (isHeaderOptions(header)) { - this.header = new Header(header) - } else { - throw Error("Create EquipmentSocket Failure!") - } - - this.body = (body === undefined) ? new Uint8Array(0) : _.cloneDeep(body) - } - - toUint8Array(): Uint8Array { - const header = this.header.toUint8Array() - const bodyLength = this.body.length - - let total = new Uint8Array(header.length + bodyLength) - total.set(header) - if (bodyLength > 0) - total.set(this.body, header.length) - - return total - } - - } -} - diff --git a/server/udpProtocol.ts b/server/udpProtocol.ts new file mode 100644 index 0000000..e75d06d --- /dev/null +++ b/server/udpProtocol.ts @@ -0,0 +1,93 @@ +import { type } from "./common"; +import { z } from "zod"; +import _ from "lodash"; + +export namespace SendProtocol { + const CMD_BURST_TYPE = 0b1100_0000 + const CMD_TASK_ID = 0b0011_0000 + const CMD_READ_WRITE_TYPE = 0b0000_0001 + + const AddrPackageOptionsSchema = z.object({ + burstType: z.enum(["Fixed", "Extend"]), + taskID: z.number().nonnegative().lt(4), + readWriteType: z.enum(["r", "w"]) + }) + + export type AddrPackageOptions = z.infer + + export function isAddrPackageOptions(obj: any): obj is AddrPackageOptions { + return AddrPackageOptionsSchema.safeParse(obj).success + } + + export class AddrPackage { + private ID: number = 0x00 + private commandType: number = 0 + private burstLength: number = 0 + private _reserved: number = 0 + private address: number = 0 + + constructor(options: AddrPackageOptions) { + this.setCommandType(options) + } + + setCommandType(options: AddrPackageOptions) { + const validOptions = AddrPackageOptionsSchema.parse(options) + this.commandType = + } + + setBurstLength(len: number) { + this.burstLength = len + } + + setAddress(addr: number) { + this.address = addr + } + + options(): AddrPackageOptions { + return { + burstType: type.numberMatch(this.commandType, CMD_BURST_TYPE, "Extend", "Fixed"), + readWriteType: type.numberMatch(this.commandType, CMD_READ_WRITE_TYPE, "w", "r") + } + } + + toUint8Array(): Uint8Array { + var array = new Uint8Array(8) + + array[0] = this.ID + array[1] = this.commandType + 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] + + return array + } + } + + export class DataPackage { + private ID: number = 0xFF + private _reserved: number = 0 + private body: Uint8Array + + constructor(body: Uint8Array) { + this.body = body + } + + toUint8Array(): Uint8Array { + var array = new Uint8Array(4 + this.body.length) + + array[0] = this.ID + array.fill(this._reserved, 1, 4) + array.set(this.body, 4) + + return array + } + } + +} + +export namespace ReceiveProcotol { } diff --git a/server/udpServer.ts b/server/udpServer.ts index 42f7eb5..fbec1e7 100644 --- a/server/udpServer.ts +++ b/server/udpServer.ts @@ -1,5 +1,8 @@ import _ from "lodash" import { type udp } from "bun" +import type { UDPDataType } from "./udp"; +import { None, Some, type Option } from "ts-results-es"; +import { z } from "zod"; declare var self: Worker export type UDPServerMsgType = "port" @@ -8,12 +11,14 @@ interface BinaryTypeList { arraybuffer: ArrayBuffer; buffer: Buffer; uint8array: Uint8Array; - // TODO: DataView - // dataview: DataView; } type BinaryType = keyof BinaryTypeList; - +const receivedData: Map> = new Map() const udpServer = await Bun.udpSocket({ port: 33000, @@ -24,12 +29,71 @@ const udpServer = await Bun.udpSocket({ port: number, address: string, ) { - // todo : Handle Recieved Data + // Add Received Data + let arrayData = receivedData.get(address) + if (_.isUndefined(arrayData)) { + + } else { + receivedData.set(address, []) + arrayData.push({ + data: data, + port: port, + date: new Date().toUTCString() + }) + } } } }) - -export function udpServerPort(): number { +function port(): number { return udpServer.port } + +function lastestData(address: string): Option { + if (!z.string().ip().safeParse(address).success) { + return None + } + + const arrayData = receivedData.get(address) + if (_.isUndefined(arrayData)) { + return None + } + + const data = arrayData.pop() + if (_.isUndefined(data)) { + return None + } + + return Some({ + address: address, + data: data.data, + port: data.port, + date: data.date, + }) +} + +function oldestData(address: string): Option { + if (!z.string().ip().safeParse(address).success) { + return None + } + + const arrayData = receivedData.get(address) + if (_.isUndefined(arrayData)) { + return None + } + + const data = arrayData.shift() + if (_.isUndefined(data)) { + return None + } + + return Some({ + address: address, + data: data.data, + port: data.port, + date: data.date, + }) +} + +export { port, lastestData, oldestData } +