Compare commits

...

4 Commits

Author SHA1 Message Date
SikongJueluo 0da5b85173
finish web test api 2025-03-31 20:24:26 +08:00
SikongJueluo f455af3589
add basic web protocol 2025-03-29 20:24:15 +08:00
SikongJueluo 351aad8300
init csharp server 2025-03-29 19:02:18 +08:00
SikongJueluo cddf92e432
init branch 2025-03-29 13:05:01 +08:00
31 changed files with 622 additions and 1368 deletions

13
.editorconfig Normal file
View File

@ -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

View File

@ -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

4
server/.csharpierrc.yaml Normal file
View File

@ -0,0 +1,4 @@
printWidth: 100
useTabs: false
tabWidth: 2
endOfLine: auto

3
server/.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
obj
bin

41
server/Program.cs Normal file
View File

@ -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();

View File

@ -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"
}
}
}
}

View File

@ -0,0 +1,8 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}

9
server/appsettings.json Normal file
View File

@ -0,0 +1,9 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}

View File

@ -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)
})

View File

@ -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)
}
}

View File

@ -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)
})

View File

@ -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)
}
}
}

View File

@ -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 });
}
}))

View File

@ -1 +0,0 @@
import { MsgProtocol } from "./msgProtocol";

View File

@ -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
}
}

View File

@ -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;

16
server/server.csproj Normal file
View File

@ -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>

83
server/src/Common.cs Normal file
View File

@ -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;
}
}
}

41
server/src/Router.cs Normal file
View File

@ -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));
}
}
}

View File

@ -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);
}
}
}

93
server/src/UdpServer.cs Normal file
View File

@ -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();
}
}

195
server/src/WebProtocol.cs Normal file
View File

@ -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;
}
}
}

View File

@ -1,6 +0,0 @@
import { initTRPC } from '@trpc/server';
const t = initTRPC.create();
export const router = t.router;
export const publicProcedure = t.procedure;

View File

@ -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")
}
})

View File

@ -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 }

View File

@ -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)
}

View File

@ -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
}
}

View File

@ -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 }

View File

@ -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>

View File

@ -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>

View File

@ -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
}
}