Compare commits
4 Commits
5f872e8287
...
0da5b85173
Author | SHA1 | Date |
---|---|---|
|
0da5b85173 | |
|
f455af3589 | |
|
351aad8300 | |
|
cddf92e432 |
|
@ -0,0 +1,13 @@
|
|||
root = true
|
||||
|
||||
# Default
|
||||
[*]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
tab_width = 2
|
||||
|
||||
|
||||
[*.cs]
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
tab_width = 4
|
10
flake.nix
10
flake.nix
|
@ -14,10 +14,20 @@
|
|||
devShells = forEachSupportedSystem ({ pkgs }: {
|
||||
default = pkgs.mkShell {
|
||||
packages = with pkgs; [
|
||||
# Frontend
|
||||
bun
|
||||
sqlite
|
||||
sqls
|
||||
sql-studio
|
||||
# Backend
|
||||
(dotnetCorePackages.combinePackages [
|
||||
dotnetCorePackages.sdk_9_0
|
||||
dotnetCorePackages.aspnetcore_9_0
|
||||
dotnetCorePackages.sdk_8_0
|
||||
])
|
||||
nuget
|
||||
omnisharp-roslyn
|
||||
csharpier
|
||||
|
||||
# LSP
|
||||
typescript-language-server
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
printWidth: 100
|
||||
useTabs: false
|
||||
tabWidth: 2
|
||||
endOfLine: auto
|
|
@ -0,0 +1,3 @@
|
|||
obj
|
||||
bin
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
using System.Net;
|
||||
using Microsoft.OpenApi.Models;
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
builder.Services.AddEndpointsApiExplorer();
|
||||
builder.Services.AddSwaggerGen(c =>
|
||||
{
|
||||
c.SwaggerDoc("v1", new OpenApiInfo
|
||||
{
|
||||
Title = "FPGA Web Lab API",
|
||||
Description = "Use FPGA in the cloud",
|
||||
Version = "v1"
|
||||
});
|
||||
});
|
||||
|
||||
var app = builder.Build();
|
||||
if (app.Environment.IsDevelopment())
|
||||
{
|
||||
app.UseSwagger();
|
||||
app.UseSwaggerUI(c =>
|
||||
{
|
||||
c.SwaggerEndpoint("/swagger/v1/swagger.json", "FPAG WebLab API V1");
|
||||
});
|
||||
}
|
||||
|
||||
// Setup UDP Server
|
||||
var udpServer = new UDPServer(33000);
|
||||
udpServer.Start();
|
||||
|
||||
// Router
|
||||
app.MapGet("/", () => "Hello World!");
|
||||
app.MapPut("/api/SendString", Router.API.SendString);
|
||||
app.MapPut("/api/SendAddrPackage", Router.API.SendAddrPackage);
|
||||
app.MapPut("/api/SendDataPackage", Router.API.SendDataPackage);
|
||||
|
||||
app.Run("http://localhost:5000");
|
||||
|
||||
// Close UDP Server
|
||||
Console.WriteLine("UDP Server is Closing now...");
|
||||
udpServer.Stop();
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
"$schema": "https://json.schemastore.org/launchsettings.json",
|
||||
"profiles": {
|
||||
"http": {
|
||||
"commandName": "Project",
|
||||
"dotnetRunMessages": true,
|
||||
"launchBrowser": true,
|
||||
"applicationUrl": "http://localhost:5188",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
},
|
||||
"https": {
|
||||
"commandName": "Project",
|
||||
"dotnetRunMessages": true,
|
||||
"launchBrowser": true,
|
||||
"applicationUrl": "https://localhost:7070;http://localhost:5188",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
},
|
||||
"AllowedHosts": "*"
|
||||
}
|
|
@ -1,66 +0,0 @@
|
|||
import { type } from "./common"
|
||||
import _ from "lodash"
|
||||
import { expect, test } from "bun:test"
|
||||
|
||||
const CYCLES = 10000
|
||||
test("Test Integer and Unsigned Integer", () => {
|
||||
for (let i = 0; i < CYCLES; i++) {
|
||||
|
||||
// Unsigned Integer
|
||||
expect(type.UInteger.safeParse(_.random(0, Math.pow(2, 53), false)).success).toBeTrue()
|
||||
expect(type.UInt8.safeParse(_.random(0, Math.pow(2, 8) - 1, false)).success).toBeTrue()
|
||||
expect(type.UInt8.safeParse(_.random(Math.pow(2, 8), Math.pow(2, 53), false)).success).toBeFalse()
|
||||
expect(type.UInt16.safeParse(_.random(0, Math.pow(2, 16) - 1, false)).success).toBeTrue()
|
||||
expect(type.UInt16.safeParse(_.random(Math.pow(2, 16), Math.pow(2, 53), false)).success).toBeFalse()
|
||||
expect(type.UInt32.safeParse(_.random(0, Math.pow(2, 32) - 1, false)).success).toBeTrue()
|
||||
expect(type.UInt32.safeParse(_.random(Math.pow(2, 32), Math.pow(2, 53), false)).success).toBeFalse()
|
||||
|
||||
// Integer
|
||||
expect(type.Integer.safeParse(_.random(-Math.pow(2, 52), Math.pow(2, 52) - 1, false)).success).toBeTrue()
|
||||
expect(type.Int8.safeParse(_.random(-Math.pow(2, 7), Math.pow(2, 7) - 1, false)).success).toBeTrue()
|
||||
expect(type.Int8.safeParse(_.random(Math.pow(2, 7), Math.pow(2, 52), false)).success).toBeFalse()
|
||||
expect(type.Int8.safeParse(_.random(-Math.pow(2, 52), -Math.pow(2, 7) - 1, false)).success).toBeFalse()
|
||||
expect(type.Int16.safeParse(_.random(-Math.pow(2, 15), Math.pow(2, 15) - 1, false)).success).toBeTrue()
|
||||
expect(type.Int16.safeParse(_.random(Math.pow(2, 15), Math.pow(2, 52), false)).success).toBeFalse()
|
||||
expect(type.Int16.safeParse(_.random(-Math.pow(2, 52), -Math.pow(2, 15) - 1, false)).success).toBeFalse()
|
||||
expect(type.Int32.safeParse(_.random(-Math.pow(2, 31), Math.pow(2, 31) - 1, false)).success).toBeTrue()
|
||||
expect(type.Int32.safeParse(_.random(Math.pow(2, 31), Math.pow(2, 52), false)).success).toBeFalse()
|
||||
expect(type.Int32.safeParse(_.random(-Math.pow(2, 52), -Math.pow(2, 31) - 1, false)).success).toBeFalse()
|
||||
}
|
||||
})
|
||||
|
||||
test("Test Number Processor Function", () => {
|
||||
// Convert Number to Uint8Array
|
||||
expect(type.numberToBytes(0xFF, 1).unwrap()[0]).toBe(255)
|
||||
expect(type.numberToBytes(0xAAAA, 2).unwrap()).toEqual(new Uint8Array([0xAA, 0xAA]))
|
||||
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)
|
||||
const num2 = _.random(CYCLES / 2, false)
|
||||
|
||||
expect(type.numberMatch(num1, num2)).toBe((num1 & num2) === num2 ? true : false)
|
||||
expect(type.numberMatch(num1, num2, "True", "False")).toBe((num1 & num2) === num2 ? "True" : "False")
|
||||
}
|
||||
|
||||
// Number Set, Unset, Toggle and Get Bit
|
||||
expect(type.numberSetBit(0, 5)).toBe(0b10000)
|
||||
expect(type.numberUnsetBit(0b1111, 3)).toBe(0b1011)
|
||||
expect(type.numberToggleBit(0b1010, 3)).toBe(0b1110)
|
||||
expect(type.numberBit(0b1100, 2)).toBe(0)
|
||||
|
||||
// Get High / Low Bits Num
|
||||
expect(type.numberHighBitsNum(0xFF).unwrap()).toBe(8)
|
||||
expect(type.numberHighBitsNum(0xAA).unwrap()).toBe(4)
|
||||
expect(type.numberLowBitsNum(0xFF, 8).unwrap()).toBe(0)
|
||||
expect(type.numberLowBitsNum(0xAA, 8).unwrap()).toBe(4)
|
||||
})
|
191
server/common.ts
191
server/common.ts
|
@ -1,191 +0,0 @@
|
|||
import _ from "lodash"
|
||||
import { Option, Some, None, Result, Ok, Err } from "ts-results-es";
|
||||
import { z } from "zod";
|
||||
|
||||
export function UNUSED(_: unknown): void { }
|
||||
|
||||
export namespace type {
|
||||
|
||||
const NUMBER_MAX_LENGTH = 32
|
||||
|
||||
|
||||
export const ErrorTypeSchema = z.union([
|
||||
z.literal("Not Integer"),
|
||||
z.literal("Not Unsigned Integer"),
|
||||
z.literal("Not 32Bits Integer")
|
||||
])
|
||||
|
||||
export type ErrorType = z.infer<typeof ErrorTypeSchema>
|
||||
|
||||
export const Integer = z.number().int()
|
||||
export const UInteger = z.number().int().nonnegative()
|
||||
|
||||
export const UInt8 = UInteger.lt(Math.pow(2, 8))
|
||||
export const UInt16 = UInteger.lt(Math.pow(2, 16))
|
||||
export const UInt32 = UInteger.lt(Math.pow(2, 32))
|
||||
|
||||
export const Int8 = Integer.lt(Math.pow(2, 7)).gte(-Math.pow(2, 8))
|
||||
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, isRightHigh: boolean = false)
|
||||
: Result<Uint8Array, ErrorType> {
|
||||
// Check Integer
|
||||
if (!Int32.safeParse(num).success && !UInt32.lte(32).safeParse(bytesLength).success) {
|
||||
console.error(`Number : ${num}, 2 ^ 31 = ${2 ^ 31}`)
|
||||
console.error(Int32.safeParse(num).error?.message)
|
||||
return new Err("Not 32Bits Integer")
|
||||
}
|
||||
|
||||
var array = new Uint8Array(bytesLength)
|
||||
|
||||
if (isRightHigh) {
|
||||
for (let i = 0; i < bytesLength; i++) {
|
||||
array[bytesLength - 1 - i] = ((num >> (i << 3)) & 0xFF)
|
||||
}
|
||||
} else {
|
||||
for (let i = 0; i < bytesLength; i++) {
|
||||
array[i] = ((num >> (i << 3)) & 0xFF)
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
destNum: number
|
||||
): boolean;
|
||||
|
||||
export function numberMatch<T>(
|
||||
srcNum: number,
|
||||
destNum: number,
|
||||
True: T,
|
||||
False: T
|
||||
): T;
|
||||
|
||||
export function numberMatch<T>(
|
||||
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 - 1))
|
||||
}
|
||||
|
||||
export function numberUnsetBit(num: number, loc: number): number {
|
||||
return num & ~(1 << (loc - 1))
|
||||
}
|
||||
|
||||
export function numberToggleBit(num: number, loc: number): number {
|
||||
return num ^ (1 << (loc - 1))
|
||||
}
|
||||
|
||||
export function numberBit(num: number, loc: number): number {
|
||||
return (num >> (loc - 1)) & 1
|
||||
}
|
||||
|
||||
export function numberHighBitsNum(num: number, maxLen: number = NUMBER_MAX_LENGTH): Result<number, ErrorType> {
|
||||
if (!Int32.safeParse(num).success && maxLen > 32) {
|
||||
return new Err("Not 32Bits Integer")
|
||||
}
|
||||
|
||||
let cnt = 0
|
||||
for (let i = 0; i < maxLen; i++) {
|
||||
if (num & (1 << i)) {
|
||||
cnt++
|
||||
}
|
||||
}
|
||||
return new Ok(cnt)
|
||||
}
|
||||
|
||||
export function numberLowBitsNum(num: number, maxLen: number): Result<number, ErrorType> {
|
||||
if (!Int32.safeParse(num).success && maxLen > 32) {
|
||||
return new Err("Not 32Bits Integer")
|
||||
}
|
||||
|
||||
let cnt = 0
|
||||
for (let i = 0; i < maxLen; i++) {
|
||||
if (!(num & (1 << i))) {
|
||||
cnt++
|
||||
}
|
||||
}
|
||||
return new Ok(cnt)
|
||||
}
|
||||
|
||||
export function isStringArray(obj: any): obj is Array<string> {
|
||||
return z.string().array().safeParse(obj).success
|
||||
}
|
||||
|
||||
export function isNumberArray(obj: any): obj is Array<number> {
|
||||
return z.number().array().safeParse(obj).success
|
||||
}
|
||||
}
|
||||
|
||||
export namespace fun {
|
||||
|
||||
export function randomFromArray(array: Array<any>) {
|
||||
return array[_.random(0, array.length - 1, false)]
|
||||
}
|
||||
|
||||
export function sqlConditionFromArray(
|
||||
columnName: string,
|
||||
array: Array<string>,
|
||||
type: "AND" | "OR"
|
||||
): Option<string> {
|
||||
let condition: string = ""
|
||||
const len = array.length
|
||||
if (len == 0) {
|
||||
return None
|
||||
}
|
||||
for (let i = 0; i < len; i++) {
|
||||
condition.concat(`${columnName}=${array[i]}`)
|
||||
if (i != len - 1) {
|
||||
condition.concat(` ${type} `)
|
||||
}
|
||||
}
|
||||
|
||||
return new Some(condition)
|
||||
}
|
||||
|
||||
export function sqlConditionFromString(
|
||||
columnName: string,
|
||||
str: string,
|
||||
type: "AND" | "OR",
|
||||
delimiter: string = " "
|
||||
): Option<string> {
|
||||
if (str.length == 0) {
|
||||
return None
|
||||
}
|
||||
|
||||
const array = str.split(delimiter)
|
||||
return sqlConditionFromArray(columnName, array, type)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1,65 +0,0 @@
|
|||
import { test, expect } from "bun:test"
|
||||
import * as db from "./database.ts"
|
||||
import _, { rearg } from "lodash"
|
||||
import { None, Ok, Option, Some } from "ts-results-es"
|
||||
|
||||
// Test basic function for database
|
||||
test("DataBase", () => {
|
||||
const allTables = db.allTables()
|
||||
expect(allTables).toBeArray()
|
||||
expect(allTables).toEqual(["Users", "Boards"])
|
||||
expect(db.BoardTable.countAll()).toBe(0)
|
||||
expect(db.UserTable.countAll()).toBe(0)
|
||||
})
|
||||
|
||||
// Test Boards table function
|
||||
const boardsNumber = 10
|
||||
const rooms = ["A1", "A1", "A1", "A1", "A1", "A2", "A2", "A2", "A2", "A2"]
|
||||
test("Find something empty", () => {
|
||||
const findEmptyByID = db.BoardTable.find(_.random(0, boardsNumber))
|
||||
expect(findEmptyByID).toEqual(Ok(None))
|
||||
const findEmptyByName = db.BoardTable.find("Hello", "World")
|
||||
expect(findEmptyByName).toEqual(Ok(None))
|
||||
})
|
||||
|
||||
test("Add some boards", () => {
|
||||
const boardsArray: Array<db.Board> = []
|
||||
for (let i = 0; i < boardsNumber; i++) {
|
||||
boardsArray.push({
|
||||
id: i,
|
||||
name: `Board ${i}`,
|
||||
room: rooms[i],
|
||||
ipv4: `192.168.172.${i}`,
|
||||
port: i,
|
||||
})
|
||||
}
|
||||
const retAdd = db.BoardTable.addFromArray(boardsArray)
|
||||
const isAddOk = retAdd.isOk()
|
||||
expect(isAddOk).toBeTrue()
|
||||
})
|
||||
|
||||
test("Get boards table column unique element", () => {
|
||||
expect(db.BoardTable.rooms().unwrap()).toEqual(["A1", "A2"])
|
||||
})
|
||||
|
||||
test("Find something from boards table", () => {
|
||||
expect(db.BoardTable.find(1)).toEqual(Ok(Some({
|
||||
id: 1,
|
||||
name: `Board ${1}`,
|
||||
room: rooms[1],
|
||||
ipv4: `192.168.172.${1}`,
|
||||
port: 1,
|
||||
} as db.Board)))
|
||||
expect(db.BoardTable.find("Board 3", "A1")).toEqual(Ok(Some({
|
||||
id: 3,
|
||||
name: `Board ${3}`,
|
||||
room: rooms[3],
|
||||
ipv4: `192.168.172.${3}`,
|
||||
port: 3,
|
||||
} as db.Board)))
|
||||
})
|
||||
|
||||
test("Get count of elements from boards table", () => {
|
||||
expect(db.BoardTable.countByName("Board 1")).toBe(1)
|
||||
expect(db.BoardTable.countByRoom("A1")).toBe(5)
|
||||
})
|
|
@ -1,478 +0,0 @@
|
|||
import { Database, type Changes } from "bun:sqlite";
|
||||
import _ from "lodash";
|
||||
import { Ok, Err, Result, None, Some, Option } from "ts-results-es";
|
||||
import { z } from "zod";
|
||||
import { fun, type } from "./common";
|
||||
|
||||
const db = new Database("lab.sqlite", { strict: true })
|
||||
initDB(db);
|
||||
|
||||
// Error Type
|
||||
const BoardErrorTypeSchema = z.union([
|
||||
z.literal("Wrong Type"),
|
||||
z.literal("No Such Board(s)"),
|
||||
z.literal("ID Conflict"),
|
||||
z.literal("Name Conflict")
|
||||
])
|
||||
|
||||
export type BoardErrorType = z.infer<typeof BoardErrorTypeSchema>
|
||||
|
||||
const boardSchema = z.object({
|
||||
id: z.number().nonnegative(),
|
||||
name: z.string(),
|
||||
room: z.string(),
|
||||
ipv4: z.string().ip({ version: "v4" }),
|
||||
ipv6: z.string().ip({ version: "v6" }),
|
||||
port: z.number().nonnegative().lte(65535),
|
||||
cmdID: z.number().nonnegative()
|
||||
}).partial({
|
||||
ipv6: true,
|
||||
cmdID: true
|
||||
})
|
||||
|
||||
export type Board = z.infer<typeof boardSchema>
|
||||
export type BoardColumn = keyof Board
|
||||
|
||||
export function isBoard(obj: any): obj is Board {
|
||||
return boardSchema.safeParse(obj).success
|
||||
}
|
||||
|
||||
export function isBoardArray(obj: any): obj is Array<Board> {
|
||||
return boardSchema.array().safeParse(obj).success
|
||||
}
|
||||
|
||||
export function isBoardColumn(obj: any): obj is BoardColumn {
|
||||
return boardSchema.keyof().safeParse(obj).success
|
||||
}
|
||||
|
||||
const userSchema = z.object({
|
||||
id: z.number().nonnegative(),
|
||||
name: z.string(),
|
||||
password: z.string(),
|
||||
boardID: z.number(),
|
||||
}).partial({
|
||||
boardID: true,
|
||||
})
|
||||
|
||||
export type User = z.infer<typeof userSchema>
|
||||
export type UserColumn = keyof User
|
||||
|
||||
export function isUser(obj: any): obj is User {
|
||||
return userSchema.safeParse(obj).success
|
||||
}
|
||||
|
||||
export function isUserArray(obj: any): obj is Array<User> {
|
||||
return userSchema.array().safeParse(obj).success
|
||||
}
|
||||
|
||||
export function isUserColumn(obj: any): obj is UserColumn {
|
||||
return userSchema.keyof().safeParse(obj).success
|
||||
}
|
||||
|
||||
function initDB(db: Database) {
|
||||
const tables = allTables()
|
||||
|
||||
if (!tables.includes("Users")) {
|
||||
db.query(`
|
||||
CREATE TABLE Users(
|
||||
id INT PRIMARY KEY NOT NULL,
|
||||
name TEXT NOT NULL,
|
||||
password TEXT NOT NULL
|
||||
);
|
||||
`).run();
|
||||
}
|
||||
|
||||
if (!tables.includes("Boards"))
|
||||
db.query(`
|
||||
CREATE TABLE Boards(
|
||||
id INT PRIMARY KEY NOT NULL,
|
||||
name TEXT NOT NULL,
|
||||
room TEXT NOT NULL,
|
||||
ipv4 CHAR(16) NOT NULL,
|
||||
ipv6 CHAR(46) ,
|
||||
port INT NOT NULL
|
||||
)
|
||||
`).run();
|
||||
}
|
||||
|
||||
export function allTables(): Array<string> {
|
||||
const query = db.query(`SELECT name FROM sqlite_master WHERE type='table'`)
|
||||
var tables = new Array()
|
||||
// Flaten array
|
||||
for (const item of query.values()) {
|
||||
tables.push(item[0])
|
||||
}
|
||||
|
||||
query.finalize()
|
||||
return tables
|
||||
}
|
||||
|
||||
export function tableColumnName(table: string): Array<string> {
|
||||
const query = db.query(`PRAGMA table_info(${table})`)
|
||||
|
||||
var columnName = new Array()
|
||||
for (const column of query.values()) {
|
||||
columnName.push(column[1])
|
||||
}
|
||||
|
||||
return columnName
|
||||
}
|
||||
|
||||
export namespace BoardTable {
|
||||
|
||||
export function add(board: Board): Result<Changes, BoardErrorType> {
|
||||
if (!isBoard(board)) {
|
||||
return new Err("Wrong Type")
|
||||
}
|
||||
|
||||
// Ensure no id conflict
|
||||
if (board.id == 0) {
|
||||
const cnt = countAll()
|
||||
board.id = cnt + 1
|
||||
} else {
|
||||
const retID = includes(board.id)
|
||||
if (retID.isOk()) {
|
||||
if (retID.value) return new Err("ID Conflict")
|
||||
} else {
|
||||
return new Err("Wrong Type")
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure no name conflict in the same room
|
||||
{
|
||||
const retName = includes(board.name, board.room)
|
||||
if (retName.isOk()) {
|
||||
if (retName.value) {
|
||||
return new Err("Name Conflict")
|
||||
}
|
||||
} else {
|
||||
return new Err("Wrong Type")
|
||||
}
|
||||
}
|
||||
|
||||
const query = db.query(`
|
||||
INSERT INTO Boards VALUES
|
||||
(${board.id},
|
||||
'${board.name}',
|
||||
'${board.room}',
|
||||
'${board.ipv4}',
|
||||
'${_.isUndefined(board.ipv6) ? "NULL" : board.ipv6}',
|
||||
${board.port});
|
||||
`)
|
||||
|
||||
return Ok(query.run())
|
||||
}
|
||||
|
||||
export function addFromArray(array: Array<Board>)
|
||||
: Result<Array<Changes>, BoardErrorType> {
|
||||
|
||||
let arrayChanges: Array<Changes> = []
|
||||
for (const item of array) {
|
||||
const ret = add(item)
|
||||
if (ret.isErr()) {
|
||||
return ret
|
||||
} else {
|
||||
arrayChanges.push(ret.value)
|
||||
}
|
||||
}
|
||||
|
||||
return new Ok(arrayChanges)
|
||||
}
|
||||
|
||||
export function all(): Option<Array<Board>> {
|
||||
const query = db.query(`SELECT * FROM Boards`)
|
||||
const ret = query.all()
|
||||
query.finalize()
|
||||
|
||||
if (isBoardArray(ret)) {
|
||||
return Some(ret)
|
||||
} else {
|
||||
return None
|
||||
}
|
||||
}
|
||||
|
||||
export function countAll(): number {
|
||||
const query = db.query(`SELECT COUNT(*) FROM Boards`)
|
||||
return query.values()[0][0] as number
|
||||
}
|
||||
|
||||
export function countByName(name: string): number {
|
||||
const query = db.query(`SELECT * FROM Boards WHERE name=${name}`)
|
||||
return query.values()[0][0] as number
|
||||
}
|
||||
|
||||
export function countByRoom(room: string): number {
|
||||
const query = db.query(`SELECT * FROM Boards WHERE room=${room}`)
|
||||
return query.values()[0][0] as number
|
||||
}
|
||||
|
||||
export function remove(name: string, room: string): Result<Board, BoardErrorType>
|
||||
export function remove(id: number): Result<Board, BoardErrorType>
|
||||
export function remove(board: Board): Result<Board, BoardErrorType>
|
||||
export function remove(arg1: any, arg2?: any): Result<Board, BoardErrorType> {
|
||||
let retBoard
|
||||
let condition: string
|
||||
if (isBoard(arg1)) {
|
||||
retBoard = _.cloneDeep(arg1)
|
||||
condition = `id=${arg1.id}`
|
||||
|
||||
} else if (_.isNumber(arg1)) {
|
||||
retBoard = find(arg1)
|
||||
if (retBoard.isOk() && retBoard.value.isSome()) {
|
||||
retBoard = _.cloneDeep(retBoard.value.value)
|
||||
condition = `id=${arg1}`
|
||||
} else {
|
||||
return new Err("Wrong Type")
|
||||
}
|
||||
|
||||
} else if (_.isString(arg1) && _.isString(arg2)) {
|
||||
retBoard = find(arg1, arg2)
|
||||
if (retBoard.isOk() && retBoard.value.isSome()) {
|
||||
retBoard = _.cloneDeep(retBoard.value.value)
|
||||
condition = `name=${arg1}`
|
||||
} else {
|
||||
return new Err("No Such Board(s)")
|
||||
}
|
||||
|
||||
} else {
|
||||
return new Err("Wrong Type")
|
||||
}
|
||||
|
||||
const query = db.query(`DELETE FROM Boards WHERE ${condition}`)
|
||||
query.run()
|
||||
|
||||
return new Ok(retBoard)
|
||||
}
|
||||
|
||||
export function removeAll(): Option<Array<Board>> {
|
||||
const array = all()
|
||||
const query = db.query(`DELETE FROM Boards`)
|
||||
query.run()
|
||||
|
||||
|
||||
if (array.isSome()) {
|
||||
return new Some(array.value)
|
||||
} else {
|
||||
return None
|
||||
}
|
||||
}
|
||||
|
||||
export function removeByCondition(condition: string): Result<Array<Board>, BoardErrorType> {
|
||||
const rsArr = findByCondition(condition)
|
||||
if (rsArr.isNone()) {
|
||||
return new Err("No Such Board(s)")
|
||||
}
|
||||
|
||||
const query = db.query(`DELETE FROM Boards WHERE ${condition}`)
|
||||
query.run()
|
||||
|
||||
return new Ok(rsArr.value)
|
||||
}
|
||||
|
||||
export function removeByValue(columnName: string, val: string | Array<string>)
|
||||
: Result<Array<Board>, BoardErrorType> {
|
||||
|
||||
if (!isBoardColumn(columnName)) {
|
||||
return new Err("Wrong Type")
|
||||
}
|
||||
|
||||
let condition: string
|
||||
if (_.isString(val)) {
|
||||
const retCond = fun.sqlConditionFromString(columnName, val, "OR")
|
||||
if (retCond.isSome()) {
|
||||
condition = retCond.value
|
||||
} else {
|
||||
return new Err("Wrong Type")
|
||||
}
|
||||
} else if (type.isStringArray(val)) {
|
||||
const retCond = fun.sqlConditionFromArray(columnName, val, "OR")
|
||||
if (retCond.isSome()) {
|
||||
condition = retCond.value
|
||||
} else {
|
||||
return new Err("Wrong Type")
|
||||
}
|
||||
} else {
|
||||
return new Err("Wrong Type")
|
||||
}
|
||||
|
||||
return removeByCondition(condition)
|
||||
}
|
||||
|
||||
export function removeByName(name: string | Array<string>)
|
||||
: Result<Array<Board>, BoardErrorType> {
|
||||
return removeByValue("name", name)
|
||||
}
|
||||
|
||||
export function removeByRoom(room: string | Array<string>)
|
||||
: Result<Array<Board>, BoardErrorType> {
|
||||
|
||||
return removeByValue("room", room)
|
||||
}
|
||||
|
||||
export function rooms(): Option<Array<string>> {
|
||||
const query = db.query(`SELECT DISTINCT room FROM Boards`)
|
||||
let rooms: Array<string> = []
|
||||
const retVal = query.values()
|
||||
|
||||
if (retVal.length > 0) {
|
||||
for (const item of retVal) {
|
||||
rooms.push(item[0] as string)
|
||||
}
|
||||
return new Some(rooms)
|
||||
} else {
|
||||
return None
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export function find(name: string, room: string): Result<Option<Board>, BoardErrorType>
|
||||
export function find(id: number): Result<Option<Board>, BoardErrorType>
|
||||
export function find(arg1: any, arg2?: any): Result<Option<Board>, BoardErrorType> {
|
||||
let condition: string
|
||||
if (_.isNumber(arg1)) {
|
||||
condition = `id=${arg1}`
|
||||
|
||||
} else if (_.isString(arg1) && _.isString(arg2)) {
|
||||
condition = `name='${arg1}' AND room='${arg2}'`
|
||||
|
||||
} else {
|
||||
return new Err("Wrong Type")
|
||||
}
|
||||
|
||||
const query = db.query(`SELECT * FROM Boards WHERE ${condition}`)
|
||||
|
||||
const spRet = boardSchema.safeParse(query.get())
|
||||
|
||||
if (spRet.success) {
|
||||
return new Ok(Some(spRet.data))
|
||||
} else {
|
||||
return new Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
export function findByName(name: string | Array<string>): Result<Option<Array<Board>>, BoardErrorType> {
|
||||
let condition: string
|
||||
if (_.isString(name)) {
|
||||
const retCond = fun.sqlConditionFromString("name", name, "OR")
|
||||
if (retCond.isSome()) {
|
||||
condition = retCond.value
|
||||
} else {
|
||||
return new Err("Wrong Type")
|
||||
}
|
||||
} else if (type.isStringArray(name)) {
|
||||
const retCond = fun.sqlConditionFromArray("name", name, "OR")
|
||||
if (retCond.isSome()) {
|
||||
condition = retCond.value
|
||||
} else {
|
||||
return new Err("Wrong Type")
|
||||
}
|
||||
} else {
|
||||
return new Err("Wrong Type")
|
||||
}
|
||||
|
||||
return new Ok(findByCondition(condition))
|
||||
}
|
||||
|
||||
export function findByCondition(condition: string): Option<Array<Board>> {
|
||||
const query = db.query(`SELECT * FROM Boards WHERE ${condition}`)
|
||||
const ret = query.all()
|
||||
if (isBoardArray(ret)) {
|
||||
return new Some(ret)
|
||||
} else {
|
||||
return None
|
||||
}
|
||||
}
|
||||
|
||||
export function findByValue(columnName: string, val: string | Array<string>)
|
||||
: Result<Option<Array<Board>>, BoardErrorType> {
|
||||
|
||||
if (!isBoardColumn(columnName)) {
|
||||
return new Err("Wrong Type")
|
||||
}
|
||||
|
||||
let condition: string
|
||||
if (_.isString(val)) {
|
||||
const retCond = fun.sqlConditionFromString(columnName, val, "OR")
|
||||
if (retCond.isSome()) {
|
||||
condition = retCond.value
|
||||
} else {
|
||||
return new Err("Wrong Type")
|
||||
}
|
||||
} else if (type.isStringArray(val)) {
|
||||
const retCond = fun.sqlConditionFromArray(columnName, val, "OR")
|
||||
if (retCond.isSome()) {
|
||||
condition = retCond.value
|
||||
} else {
|
||||
return new Err("Wrong Type")
|
||||
}
|
||||
} else {
|
||||
return new Err("Wrong Type")
|
||||
}
|
||||
|
||||
return new Ok(findByCondition(condition))
|
||||
}
|
||||
|
||||
export function includes(name: string, room?: string): Result<boolean, BoardErrorType>
|
||||
export function includes(id: number): Result<boolean, BoardErrorType>
|
||||
export function includes(arg1: any, arg2?: any): Result<boolean, BoardErrorType> {
|
||||
let condition: string
|
||||
if (_.isUndefined(arg2)) {
|
||||
if (_.isNumber(arg1)) {
|
||||
condition = `id=${arg1}`
|
||||
|
||||
} else if (_.isString(arg1)) {
|
||||
condition = `name='${arg1}'`
|
||||
|
||||
} else {
|
||||
return new Err("Wrong Type")
|
||||
}
|
||||
} else {
|
||||
if (_.isString(arg1) && _.isString(arg2)) {
|
||||
condition = `name='${arg1} AND room=${arg2}'`
|
||||
} else {
|
||||
return new Err("Wrong Type")
|
||||
}
|
||||
}
|
||||
|
||||
const query = db.query(`SELECT COUNT(*) FROM Boards WHERE ${condition}`)
|
||||
const num = query.values()[0][0] as number
|
||||
|
||||
return new Ok(num > 0 ? true : false)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export namespace UserTable {
|
||||
|
||||
export function countAll(): number {
|
||||
const query = db.query(`SELECT COUNT(*) FROM Users`)
|
||||
return query.values()[0][0] as number
|
||||
}
|
||||
|
||||
|
||||
export function find(id: number): Result<Option<User>, "Wrong Type">
|
||||
export function find(name: string): Result<Option<User>, "Wrong Type">
|
||||
export function find(arg: any): Result<Option<User>, "Wrong Type"> {
|
||||
let condition: string
|
||||
if (_.isNumber(arg)) {
|
||||
condition = `id=${arg}`
|
||||
} else if (_.isString(arg)) {
|
||||
condition = `name=${arg}`
|
||||
} else {
|
||||
return new Err("Wrong Type")
|
||||
}
|
||||
|
||||
const query = db.query(`SELECT * FROM Users WHERE name='${arg}'`)
|
||||
|
||||
const spRet = userSchema.safeParse(query.get())
|
||||
|
||||
if (spRet.success) {
|
||||
return new Ok(Some(spRet.data))
|
||||
} else {
|
||||
return new Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -1,21 +0,0 @@
|
|||
import { createBunServeHandler } from "trpc-bun-adapter";
|
||||
import { appRouter } from "./router.ts"
|
||||
|
||||
Bun.serve(createBunServeHandler({
|
||||
router: appRouter,
|
||||
responseMeta() {
|
||||
return {
|
||||
status: 200,
|
||||
headers: {
|
||||
"Access-Control-Allow-Origin": "*",
|
||||
"Access-Control-Allow-Methods": "GET, PUT, POST, OPTIONS",
|
||||
"Access-Control-Allow-Headers": "Content-Type, Authorization"
|
||||
}
|
||||
}
|
||||
}
|
||||
}, {
|
||||
port: 3002,
|
||||
fetch() {
|
||||
return Response.json({ message: "Not Found" }, { status: 404 });
|
||||
}
|
||||
}))
|
|
@ -1 +0,0 @@
|
|||
import { MsgProtocol } from "./msgProtocol";
|
|
@ -1,79 +0,0 @@
|
|||
import { type TransferListItem } from "worker_threads";
|
||||
import { z } from "zod";
|
||||
import { UNUSED } from "./common";
|
||||
|
||||
export namespace MsgProtocol {
|
||||
const QueryDataSchema = z.object({ type: z.literal("Query"), args: z.any() })
|
||||
const ResultDataSchema = z.object({ type: z.literal("Result"), result: z.any() })
|
||||
const ErrorDataSchema = z.object({ type: z.literal("Error"), error: z.string() })
|
||||
|
||||
const MessageSchema = z.object({
|
||||
command: z.string(),
|
||||
data: z.discriminatedUnion("type", [QueryDataSchema, ResultDataSchema, ErrorDataSchema]),
|
||||
dest: z.string(),
|
||||
src: z.string(),
|
||||
})
|
||||
const MessageQuerySchema = z.object({
|
||||
command: z.string(),
|
||||
data: QueryDataSchema,
|
||||
dest: z.string(),
|
||||
src: z.string(),
|
||||
})
|
||||
const MessageResultSchema = z.object({
|
||||
command: z.string(),
|
||||
data: ResultDataSchema,
|
||||
dest: z.string(),
|
||||
src: z.string(),
|
||||
})
|
||||
const MessageErrorSchema = z.object({
|
||||
command: z.string(),
|
||||
data: ErrorDataSchema,
|
||||
dest: z.string(),
|
||||
src: z.string(),
|
||||
})
|
||||
const MessageHandlerSchema = z.function()
|
||||
.args(z.union([MessageResultSchema, MessageErrorSchema]))
|
||||
.returns(z.void())
|
||||
|
||||
export type Message = z.infer<typeof MessageSchema>
|
||||
export type MessageQuery = z.infer<typeof MessageQuerySchema>
|
||||
export type MessageResult = z.infer<typeof MessageResultSchema>
|
||||
export type MessageError = z.infer<typeof MessageErrorSchema>
|
||||
export type MessageHandler = z.infer<typeof MessageHandlerSchema>
|
||||
|
||||
export function isMessage(obj: any): obj is Message {
|
||||
return MessageSchema.safeParse(obj).success
|
||||
}
|
||||
export function isMessageQuery(obj: any): obj is MessageQuery {
|
||||
return MessageQuerySchema.safeParse(obj).success
|
||||
}
|
||||
export function isMessageResult(obj: any): obj is MessageResult {
|
||||
return MessageResultSchema.safeParse(obj).success
|
||||
}
|
||||
export function isMessageError(obj: any): obj is MessageError {
|
||||
return MessageErrorSchema.safeParse(obj).success
|
||||
}
|
||||
|
||||
export function genMessageResult(result: any, srcMsg: MessageQuery): MessageResult {
|
||||
return {
|
||||
command: srcMsg.command,
|
||||
dest: srcMsg.src,
|
||||
src: srcMsg.dest,
|
||||
data: {
|
||||
type: "Result",
|
||||
result: result
|
||||
}
|
||||
} as MessageResult
|
||||
}
|
||||
export function genMessageError(error: string, srcMsg: MessageQuery): MessageError {
|
||||
return {
|
||||
command: srcMsg.command,
|
||||
dest: srcMsg.src,
|
||||
src: srcMsg.dest,
|
||||
data: {
|
||||
type: "Error",
|
||||
error: error
|
||||
}
|
||||
} as MessageError
|
||||
}
|
||||
}
|
|
@ -1,12 +0,0 @@
|
|||
import { router, publicProcedure } from "./trpc.ts"
|
||||
|
||||
export const appRouter = router({
|
||||
api: router({
|
||||
status: publicProcedure.query(() => "OK"),
|
||||
signUp: publicProcedure.query((opts) => {
|
||||
|
||||
})
|
||||
})
|
||||
});
|
||||
|
||||
export type AppRouter = typeof appRouter;
|
|
@ -0,0 +1,16 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="DotNext" Version="5.19.1" />
|
||||
<PackageReference Include="Microsoft.OpenApi" Version="1.6.23" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="8.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
|
@ -0,0 +1,83 @@
|
|||
using DotNext;
|
||||
|
||||
namespace Common
|
||||
{
|
||||
class NumberProcessor
|
||||
{
|
||||
public static Result<byte[]> NumberToBytes(ulong num, uint length, bool isRightHigh = false)
|
||||
{
|
||||
if (length > 8)
|
||||
{
|
||||
throw new ArgumentException(
|
||||
"Unsigned long number can't over 8 bytes(64 bits).",
|
||||
nameof(length)
|
||||
);
|
||||
}
|
||||
|
||||
var arr = new byte[length];
|
||||
|
||||
if (isRightHigh)
|
||||
{
|
||||
for (var i = 0; i < length; i++)
|
||||
{
|
||||
arr[length - 1 - i] = Convert.ToByte((num >> (i << 3)) & (0xFF));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (var i = 0; i < length; i++)
|
||||
{
|
||||
arr[i] = Convert.ToByte((num >> (i << 3)) & (0xFF));
|
||||
}
|
||||
}
|
||||
return arr;
|
||||
}
|
||||
|
||||
public static Result<ulong> BytesToNumber(byte[] bytes, bool isRightLeft = false)
|
||||
{
|
||||
if (bytes.Length > 8)
|
||||
{
|
||||
throw new ArgumentException(
|
||||
"Unsigned long number can't over 8 bytes(64 bits).",
|
||||
nameof(bytes)
|
||||
);
|
||||
}
|
||||
|
||||
ulong num = 0;
|
||||
int len = bytes.Length;
|
||||
|
||||
if (isRightLeft)
|
||||
{
|
||||
for (var i = 0; i < len; i++)
|
||||
{
|
||||
num += Convert.ToUInt64(bytes[len - 1 - i] << (i << 3));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (var i = 0; i < len; i++)
|
||||
{
|
||||
num += Convert.ToUInt64(bytes[i] << (i << 3));
|
||||
}
|
||||
}
|
||||
|
||||
return num;
|
||||
}
|
||||
|
||||
public static Result<byte[]> StringToBytes(string str, int numBase = 16)
|
||||
{
|
||||
var len = str.Length;
|
||||
var bytesLen = len / 2;
|
||||
var bytes = new byte[bytesLen];
|
||||
|
||||
for (var i = 0; i < bytesLen; i++)
|
||||
{
|
||||
bytes[i] = Convert.ToByte(str.Substring(i * 2, 2), 16);
|
||||
}
|
||||
|
||||
return bytes;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
using System.Net;
|
||||
using Common;
|
||||
|
||||
namespace Router
|
||||
{
|
||||
class API
|
||||
{
|
||||
public static void SendString(string address, int port, string text)
|
||||
{
|
||||
var endPoint = new IPEndPoint(IPAddress.Parse(address), port);
|
||||
UDPClientPool.AsyncSendString(endPoint, [text]);
|
||||
}
|
||||
|
||||
public static void SendAddrPackage(
|
||||
string address,
|
||||
int port,
|
||||
WebProtocol.BurstType burstType,
|
||||
byte commandID,
|
||||
bool isWrite,
|
||||
byte burstLength,
|
||||
UInt32 devAddress)
|
||||
{
|
||||
WebProtocol.SendAddrPackOptions opts;
|
||||
opts.burstType = burstType;
|
||||
opts.commandID = commandID;
|
||||
opts.isWrite = isWrite;
|
||||
opts.burstLength = burstLength;
|
||||
opts.address = devAddress;
|
||||
|
||||
var endPoint = new IPEndPoint(IPAddress.Parse(address), port);
|
||||
UDPClientPool.SendAddrPack(endPoint, new WebProtocol.SendAddrPackage(opts));
|
||||
}
|
||||
|
||||
public static void SendDataPackage(string address, int port, string data)
|
||||
{
|
||||
var endPoint = new IPEndPoint(IPAddress.Parse(address), port);
|
||||
UDPClientPool.SendDataPack(endPoint,
|
||||
new WebProtocol.SendDataPackage(NumberProcessor.StringToBytes(data).Value));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,79 @@
|
|||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using System.Text;
|
||||
|
||||
class UDPClientPool
|
||||
{
|
||||
private static IPAddress localhost = IPAddress.Parse("127.0.0.1");
|
||||
|
||||
public static void SendString(IPEndPoint endPoint, string[] stringArray)
|
||||
{
|
||||
Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
|
||||
|
||||
byte[] sendbuf = Encoding.ASCII.GetBytes(stringArray[0]);
|
||||
|
||||
socket.SendTo(sendbuf, endPoint);
|
||||
}
|
||||
|
||||
public async static void AsyncSendString(IPEndPoint endPoint, string[] stringArray)
|
||||
{
|
||||
await Task.Run(() => { SendString(endPoint, stringArray); });
|
||||
}
|
||||
|
||||
public static void SendBytes(IPEndPoint endPoint, byte[] buf)
|
||||
{
|
||||
Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
|
||||
socket.SendTo(buf, endPoint);
|
||||
}
|
||||
|
||||
public async static void AsyncSendBytes(IPEndPoint endPoint, byte[] buf)
|
||||
{
|
||||
await Task.Run(() => { SendBytes(endPoint, buf); });
|
||||
}
|
||||
|
||||
public static void SendAddrPack(IPEndPoint endPoint, WebProtocol.SendAddrPackage pkg)
|
||||
{
|
||||
Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
|
||||
socket.SendTo(pkg.ToBytes(), endPoint);
|
||||
}
|
||||
|
||||
public async static void AsyncSendAddrPack(IPEndPoint endPoint, WebProtocol.SendAddrPackage pkg)
|
||||
{
|
||||
await Task.Run(() => { SendAddrPack(endPoint, pkg); });
|
||||
}
|
||||
|
||||
public static void SendDataPack(IPEndPoint endPoint, WebProtocol.SendDataPackage pkg)
|
||||
{
|
||||
Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
|
||||
socket.SendTo(pkg.ToBytes(), endPoint);
|
||||
}
|
||||
|
||||
public async static void AsyncSendDataPack(IPEndPoint endPoint, WebProtocol.SendDataPackage pkg)
|
||||
{
|
||||
await Task.Run(() => { SendDataPack(endPoint, pkg); });
|
||||
}
|
||||
|
||||
public static void SendLocalHost(int port, string[] stringArray)
|
||||
{
|
||||
Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
|
||||
|
||||
byte[] sendbuf = Encoding.ASCII.GetBytes(stringArray[0]);
|
||||
IPEndPoint ep = new IPEndPoint(localhost, port);
|
||||
|
||||
socket.SendTo(sendbuf, ep);
|
||||
}
|
||||
|
||||
public static void CycleSendLocalHost(int times, int sleepMilliSeconds, int port, string[] stringArray)
|
||||
{
|
||||
Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
|
||||
byte[] sendbuf = Encoding.ASCII.GetBytes(stringArray[0]);
|
||||
IPEndPoint ep = new IPEndPoint(localhost, port);
|
||||
|
||||
while (times-- >= 0)
|
||||
{
|
||||
socket.SendTo(sendbuf, ep);
|
||||
|
||||
Thread.Sleep(sleepMilliSeconds);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,93 @@
|
|||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using System.Text;
|
||||
|
||||
public class UDPServer
|
||||
{
|
||||
private int listenPort;
|
||||
private UdpClient listener;
|
||||
private IPEndPoint groupEP;
|
||||
|
||||
public UDPServer(int port)
|
||||
{
|
||||
// Construction
|
||||
listenPort = port;
|
||||
try
|
||||
{
|
||||
listener = new UdpClient(listenPort);
|
||||
groupEP = new IPEndPoint(IPAddress.Any, listenPort);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Console.WriteLine(e.ToString());
|
||||
throw new ArgumentException(
|
||||
$"Not currect port num: {port}",
|
||||
nameof(port)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private void ReceiveHandler(IAsyncResult res)
|
||||
{
|
||||
var remoteEP = new IPEndPoint(IPAddress.Any, listenPort);
|
||||
byte[] bytes = listener.EndReceive(res, ref remoteEP);
|
||||
|
||||
var sign = bytes[0];
|
||||
|
||||
string recvData;
|
||||
if (sign == (byte)WebProtocol.PackSign.SendAddr)
|
||||
{
|
||||
var resData = WebProtocol.SendAddrPackage.FromBytes(bytes);
|
||||
if (resData.IsSuccessful)
|
||||
recvData = resData.Value.ToString();
|
||||
else
|
||||
recvData = resData.Error.ToString();
|
||||
}
|
||||
else if (sign == (byte)WebProtocol.PackSign.SendData)
|
||||
{
|
||||
recvData = "";
|
||||
}
|
||||
else if (sign == (byte)WebProtocol.PackSign.RecvData)
|
||||
{
|
||||
recvData = "";
|
||||
}
|
||||
else if (sign == (byte)WebProtocol.PackSign.RecvResp)
|
||||
{
|
||||
recvData = "";
|
||||
}
|
||||
else
|
||||
{
|
||||
recvData = Encoding.ASCII.GetString(bytes, 0, bytes.Length);
|
||||
}
|
||||
|
||||
string remoteStr = (remoteEP is null) ? "Unknown" : $"{remoteEP.Address.ToString()}:{remoteEP.Port.ToString()}";
|
||||
Console.WriteLine($"Receive Data from {remoteStr} at {DateTime.Now.ToString()}:");
|
||||
Console.WriteLine($"Original Data: {BitConverter.ToString(bytes).Replace("-", " ")}");
|
||||
if (recvData.Length != 0) Console.WriteLine(recvData);
|
||||
Console.WriteLine();
|
||||
|
||||
listener.BeginReceive(new AsyncCallback(ReceiveHandler), null);
|
||||
}
|
||||
|
||||
public void Start()
|
||||
{
|
||||
try
|
||||
{
|
||||
listener.BeginReceive(new AsyncCallback(ReceiveHandler), null);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Console.WriteLine(e.ToString());
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
listener.Close();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,195 @@
|
|||
using DotNext;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace WebProtocol
|
||||
{
|
||||
public enum PackSign
|
||||
{
|
||||
SendAddr = 0x00,
|
||||
SendData = 0xFF,
|
||||
RecvData = 0x0F,
|
||||
RecvResp = 0xF0,
|
||||
}
|
||||
|
||||
public enum BurstType
|
||||
{
|
||||
ExtendBurst = 0b00,
|
||||
FixedBurst = 0b01,
|
||||
}
|
||||
|
||||
public struct SendAddrPackOptions
|
||||
{
|
||||
public BurstType burstType;
|
||||
public byte commandID;
|
||||
public bool isWrite;
|
||||
public byte burstLength;
|
||||
public UInt32 address;
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return JsonConvert.SerializeObject(this);
|
||||
}
|
||||
}
|
||||
|
||||
public struct RecvPackOptions
|
||||
{
|
||||
public byte commandID;
|
||||
public bool isSuccess;
|
||||
}
|
||||
|
||||
public struct SendAddrPackage
|
||||
{
|
||||
readonly byte sign = (byte)PackSign.SendAddr;
|
||||
readonly byte commandType;
|
||||
readonly byte burstLength;
|
||||
readonly byte _reserved = 0;
|
||||
readonly UInt32 address;
|
||||
|
||||
public SendAddrPackage(SendAddrPackOptions opts)
|
||||
{
|
||||
byte byteBurstType = Convert.ToByte((byte)opts.burstType << 5);
|
||||
byte byteCommandID = Convert.ToByte((opts.commandID & 0x03) << 3);
|
||||
byte byteIsWrite = (opts.isWrite ? (byte)0x01 : (byte)0x00);
|
||||
this.commandType = Convert.ToByte(byteBurstType | byteCommandID | byteIsWrite);
|
||||
this.burstLength = opts.burstLength;
|
||||
this.address = opts.address;
|
||||
}
|
||||
|
||||
public SendAddrPackage(BurstType burstType, byte commandID, bool isWrite)
|
||||
{
|
||||
byte byteBurstType = Convert.ToByte((byte)burstType << 5);
|
||||
byte byteCommandID = Convert.ToByte((commandID & 0x03) << 3);
|
||||
byte byteIsWrite = (isWrite ? (byte)0x01 : (byte)0x00);
|
||||
this.commandType = Convert.ToByte(byteBurstType | byteCommandID | byteIsWrite);
|
||||
}
|
||||
|
||||
public SendAddrPackage(byte commandType, byte burstLength, UInt32 address)
|
||||
{
|
||||
this.commandType = commandType;
|
||||
this.burstLength = burstLength;
|
||||
this.address = address;
|
||||
}
|
||||
|
||||
public byte[] ToBytes()
|
||||
{
|
||||
var arr = new byte[8];
|
||||
arr[0] = sign;
|
||||
arr[1] = commandType;
|
||||
arr[2] = burstLength;
|
||||
arr[3] = _reserved;
|
||||
|
||||
var bytesAddr = Common.NumberProcessor.NumberToBytes(address, 4).Value;
|
||||
Array.Copy(bytesAddr, 0, arr, 4, bytesAddr.Length);
|
||||
|
||||
return arr;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
SendAddrPackOptions opts;
|
||||
opts.burstType = (BurstType)(commandType >> 5);
|
||||
opts.commandID = Convert.ToByte((commandType >> 3) & 0b0011);
|
||||
opts.isWrite = Convert.ToBoolean(commandType & 0x01);
|
||||
opts.burstLength = burstLength;
|
||||
opts.address = address;
|
||||
|
||||
return JsonConvert.SerializeObject(opts);
|
||||
}
|
||||
|
||||
public static Result<SendAddrPackage> FromBytes(byte[] bytes, bool checkSign = true)
|
||||
{
|
||||
if (bytes.Length != 8)
|
||||
{
|
||||
throw new ArgumentException(
|
||||
"Bytes are not equal to 8 bytes.",
|
||||
nameof(bytes)
|
||||
);
|
||||
}
|
||||
|
||||
if (checkSign && bytes[0] != (byte)PackSign.SendAddr)
|
||||
{
|
||||
throw new ArgumentException(
|
||||
"The sign of bytes is not SendAddr Package, but you can disable it by set checkSign false.",
|
||||
nameof(bytes)
|
||||
);
|
||||
}
|
||||
|
||||
var address = Common.NumberProcessor.BytesToNumber(bytes[4..]).Value;
|
||||
return new SendAddrPackage(bytes[1], bytes[2], Convert.ToUInt32(address));
|
||||
}
|
||||
}
|
||||
|
||||
public struct SendDataPackage
|
||||
{
|
||||
readonly byte sign = (byte)PackSign.SendData;
|
||||
readonly byte[] _reserved = new byte[3];
|
||||
readonly byte[] bodyData;
|
||||
|
||||
public SendDataPackage(byte[] bodyData)
|
||||
{
|
||||
this.bodyData = bodyData;
|
||||
}
|
||||
|
||||
public byte[] ToBytes()
|
||||
{
|
||||
var bodyDataLen = bodyData.Length;
|
||||
var arr = new byte[4 + bodyDataLen];
|
||||
|
||||
arr[0] = sign;
|
||||
|
||||
Array.Copy(bodyData, 0, arr, 4, bodyDataLen);
|
||||
|
||||
return arr;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
public struct RecvDataPackage
|
||||
{
|
||||
readonly byte sign = (byte)PackSign.RecvData;
|
||||
readonly byte commandID;
|
||||
readonly byte resp;
|
||||
readonly byte _reserved = 0;
|
||||
readonly byte[] bodyData;
|
||||
|
||||
public RecvDataPackage(byte commandID, byte resp, byte[] bodyData)
|
||||
{
|
||||
this.commandID = commandID;
|
||||
this.resp = resp;
|
||||
this.bodyData = bodyData;
|
||||
}
|
||||
|
||||
public RecvPackOptions Options()
|
||||
{
|
||||
RecvPackOptions opts;
|
||||
opts.commandID = commandID;
|
||||
opts.isSuccess = Convert.ToBoolean((resp >> 1) == 0b01 ? true : false);
|
||||
|
||||
return opts;
|
||||
}
|
||||
}
|
||||
|
||||
public struct RecvRespPackage
|
||||
{
|
||||
readonly byte sign = (byte)PackSign.RecvResp;
|
||||
readonly byte commandID;
|
||||
readonly byte resp;
|
||||
readonly byte _reserved = 0;
|
||||
|
||||
public RecvRespPackage(byte commandID, byte resp)
|
||||
{
|
||||
this.commandID = commandID;
|
||||
this.resp = resp;
|
||||
}
|
||||
|
||||
public RecvPackOptions Options()
|
||||
{
|
||||
RecvPackOptions opts;
|
||||
opts.commandID = commandID;
|
||||
opts.isSuccess = Convert.ToBoolean((resp >> 1) == 0b01 ? true : false);
|
||||
|
||||
return opts;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,6 +0,0 @@
|
|||
import { initTRPC } from '@trpc/server';
|
||||
|
||||
const t = initTRPC.create();
|
||||
|
||||
export const router = t.router;
|
||||
export const publicProcedure = t.procedure;
|
|
@ -1,22 +0,0 @@
|
|||
import { expect, test } from "bun:test"
|
||||
import { udpServer, udpClient } from "./udp"
|
||||
|
||||
const udpServerAddr = "127.0.0.1"
|
||||
const udpServerPort = await udpServer.port()
|
||||
|
||||
console.info("Ready to Test UDP")
|
||||
|
||||
test("Test Send String", async () => {
|
||||
const str = "Hello My Server"
|
||||
const retSend = await udpClient.sendString(str, udpServerPort, udpServerAddr)
|
||||
expect(retSend).toBe(true)
|
||||
|
||||
const retReceive = await udpServer.lastestData(udpServerAddr)
|
||||
if (retReceive.isSome()) {
|
||||
const data = retReceive.value
|
||||
expect(data.body).toBe(str)
|
||||
expect(data.address).toBe(udpServerAddr)
|
||||
} else {
|
||||
expect().fail("Not Receive Anything")
|
||||
}
|
||||
})
|
|
@ -1,80 +0,0 @@
|
|||
import { resolve } from "path";
|
||||
import Tinypool from "tinypool";
|
||||
import type { UDPReceiveProcotol, UDPSendProtocol } from "./udpProtocol";
|
||||
import type { udp } from "bun";
|
||||
import { Option } from "ts-results-es";
|
||||
|
||||
const SERVER_PATH = resolve(__dirname, "./udpServer.ts")
|
||||
const CLIENT_PATH = resolve(__dirname, "./udpClient.ts")
|
||||
|
||||
export type UDPBodyType = (
|
||||
udp.Data |
|
||||
UDPSendProtocol.CmdPackage |
|
||||
UDPSendProtocol.DataPackage |
|
||||
UDPReceiveProcotol.ReadPackage |
|
||||
UDPReceiveProcotol.WritePackage
|
||||
)
|
||||
|
||||
type UDPDataType<T extends UDPBodyType = UDPBodyType> = {
|
||||
address: string,
|
||||
body: T,
|
||||
port: number,
|
||||
date?: string,
|
||||
}
|
||||
|
||||
|
||||
const udpClientsPool = new Tinypool({
|
||||
filename: CLIENT_PATH,
|
||||
workerData: {}
|
||||
})
|
||||
|
||||
const udpServerPool = new Tinypool({
|
||||
filename: SERVER_PATH,
|
||||
workerData: {},
|
||||
maxThreads: 1,
|
||||
minThreads: 1,
|
||||
})
|
||||
|
||||
export namespace udpServer {
|
||||
|
||||
export async function port(): Promise<number> {
|
||||
return udpServerPool.run(null, { name: "port" })
|
||||
}
|
||||
|
||||
export async function lastestData(address: string): Promise<Option<UDPDataType<udp.Data>>> {
|
||||
return udpServerPool.run(address, { name: "lastestData" })
|
||||
}
|
||||
|
||||
export async function oldestData(address: string): Promise<Option<UDPDataType<udp.Data>>> {
|
||||
return udpServerPool.run(address, { name: "oldestData" })
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export namespace udpClient {
|
||||
|
||||
export async function sendString(data: string, port: number, address: string): Promise<boolean> {
|
||||
return udpClientsPool.run({
|
||||
body: data,
|
||||
port: port,
|
||||
address: address
|
||||
} as UDPDataType<string>, { name: "sendString" })
|
||||
}
|
||||
|
||||
export async function sendUint8Array(data: Uint8Array, port: number, address: string): Promise<boolean> {
|
||||
return udpClientsPool.run({
|
||||
body: data,
|
||||
port: port,
|
||||
address: address
|
||||
} as UDPDataType<Uint8Array>, { name: "sendUint8Array" })
|
||||
}
|
||||
|
||||
export async function sendBunData(data: udp.Data, port: number, address: string): Promise<boolean> {
|
||||
return udpClientsPool.run({
|
||||
body: data,
|
||||
port: port,
|
||||
address: address
|
||||
} as UDPDataType<udp.Data>, { name: "sendUint8Array" })
|
||||
}
|
||||
}
|
||||
export type { UDPDataType }
|
|
@ -1,17 +0,0 @@
|
|||
import type { udp } from "bun"
|
||||
import type { UDPDataType } from "./udp"
|
||||
|
||||
const udpClient = await Bun.udpSocket({})
|
||||
|
||||
export function sendString(data: UDPDataType<string>): boolean {
|
||||
return udpClient.send(data.body, data.port, data.address)
|
||||
}
|
||||
|
||||
export function sendUint8Array(data: UDPDataType<Uint8Array>): boolean {
|
||||
return udpClient.send(data.body, data.port, data.address)
|
||||
}
|
||||
|
||||
export function sendBunData(data: UDPDataType<udp.Data>): boolean {
|
||||
return udpClient.send(data.body, data.port, data.address)
|
||||
}
|
||||
|
|
@ -1,201 +0,0 @@
|
|||
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";
|
||||
|
||||
const PKG_SIGN_ADDR = 0x00
|
||||
const PKG_SIGN_DATA = 0xFF
|
||||
const PKG_SIGN_READ = 0x0F
|
||||
const PKG_SIGN_WRITE = 0xF0
|
||||
|
||||
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 CmdPackageOptions = z.infer<typeof CmdPackageOptionsSchema>
|
||||
|
||||
export function isCmdPackageOptions(obj: any): obj is CmdPackageOptions {
|
||||
return CmdPackageOptionsSchema.safeParse(obj).success
|
||||
}
|
||||
|
||||
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(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: 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): Result<null, "Not 8Bits Unsigned Integer"> {
|
||||
if (!type.UInt8.safeParse(len).success) {
|
||||
return new Err("Not 8Bits Unsigned Integer")
|
||||
}
|
||||
this.burstLength = len
|
||||
return new Ok(null)
|
||||
}
|
||||
|
||||
setAddress(addr: number): Result<null, "Not 32Bits Unsigned Integer"> {
|
||||
if (!type.UInt32.safeParse(addr).success) {
|
||||
return new Err("Not 32Bits Unsigned Integer")
|
||||
}
|
||||
this.address = addr
|
||||
return new Ok(null)
|
||||
}
|
||||
|
||||
options(): CmdPackageOptions {
|
||||
return {
|
||||
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")
|
||||
}
|
||||
}
|
||||
|
||||
toUint8Array(): Uint8Array {
|
||||
var array = new Uint8Array(8)
|
||||
|
||||
array[0] = this.ID
|
||||
array[1] = this.commandType
|
||||
array[2] = this.burstLength
|
||||
array[3] = this._reserved
|
||||
|
||||
// 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 function isCmdPackage(obj: any): obj is CmdPackage {
|
||||
return obj instanceof CmdPackage
|
||||
}
|
||||
|
||||
export class DataPackage {
|
||||
private ID: number = PKG_SIGN_DATA
|
||||
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 function isDataPackage(obj: any): obj is DataPackage {
|
||||
return obj instanceof DataPackage
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
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 function isReadPackage(obj: any): obj is ReadPackage {
|
||||
return obj instanceof ReadPackage
|
||||
}
|
||||
|
||||
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
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
export function isWritePackage(obj: any): obj is WritePackage {
|
||||
return obj instanceof WritePackage
|
||||
}
|
||||
|
||||
}
|
|
@ -1,97 +0,0 @@
|
|||
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";
|
||||
|
||||
// Bun Data Type
|
||||
interface BinaryTypeList {
|
||||
arraybuffer: ArrayBuffer;
|
||||
buffer: Buffer;
|
||||
uint8array: Uint8Array;
|
||||
}
|
||||
type BinaryType = keyof BinaryTypeList;
|
||||
|
||||
const receivedData: Map<string, Array<{
|
||||
body: udp.Data,
|
||||
port: number,
|
||||
date: string
|
||||
}>> = new Map()
|
||||
|
||||
const udpServer = await Bun.udpSocket({
|
||||
port: 33000,
|
||||
socket: {
|
||||
data(
|
||||
_socket: udp.Socket<BinaryType>,
|
||||
data: BinaryTypeList[BinaryType],
|
||||
port: number,
|
||||
address: string,
|
||||
) {
|
||||
// Add Received Data
|
||||
let arrayData = receivedData.get(address)
|
||||
if (_.isUndefined(arrayData)) {
|
||||
|
||||
} else {
|
||||
receivedData.set(address, [])
|
||||
arrayData.push({
|
||||
body: data,
|
||||
port: port,
|
||||
date: new Date().toUTCString()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
function port(): number {
|
||||
return udpServer.port
|
||||
}
|
||||
|
||||
function lastestData(address: string): Option<UDPDataType<udp.Data>> {
|
||||
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,
|
||||
body: data.body,
|
||||
port: data.port,
|
||||
date: data.date,
|
||||
})
|
||||
}
|
||||
|
||||
function oldestData(address: string): Option<UDPDataType<udp.Data>> {
|
||||
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,
|
||||
body: data.body,
|
||||
port: data.port,
|
||||
date: data.date,
|
||||
})
|
||||
}
|
||||
|
||||
export { port, lastestData, oldestData }
|
||||
|
|
@ -1,8 +1,7 @@
|
|||
<script setup lang="ts">
|
||||
import { useThemeStore } from './stores/theme';
|
||||
|
||||
const theme = useThemeStore()
|
||||
import { useThemeStore } from "./stores/theme";
|
||||
|
||||
const theme = useThemeStore();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
|
|
@ -23,9 +23,8 @@
|
|||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
</script>
|
||||
<script setup lang="ts"></script>
|
||||
|
||||
<style scoped>
|
||||
@import "@/assets/main.css"
|
||||
@import "@/assets/main.css";
|
||||
</style>
|
||||
|
|
|
@ -1,26 +0,0 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
// Enable latest features
|
||||
"lib": [
|
||||
"ESNext"
|
||||
],
|
||||
"target": "ESNext",
|
||||
"module": "ESNext",
|
||||
"moduleDetection": "force",
|
||||
"jsx": "react-jsx",
|
||||
"allowJs": true,
|
||||
// Bundler mode
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"verbatimModuleSyntax": true,
|
||||
"noEmit": true,
|
||||
// Best practices
|
||||
"strict": true,
|
||||
"skipLibCheck": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
// Some stricter flags
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noPropertyAccessFromIndexSignature": true
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue