finish database, udp pool

This commit is contained in:
SikongJueluo 2025-03-22 21:50:56 +08:00
parent 53eeac5272
commit 12caccd2ca
No known key found for this signature in database
11 changed files with 395 additions and 5 deletions

3
.gitignore vendored
View File

@ -29,3 +29,6 @@ coverage
*.sw?
*.tsbuildinfo
# Generated Files
*.sqlite

View File

@ -6,12 +6,14 @@
"dependencies": {
"@trpc/client": "^10.45.2",
"@trpc/server": "^10.45.2",
"@types/lodash": "^4.17.16",
"log-symbols": "^7.0.0",
"pinia": "^3.0.1",
"trpc-bun-adapter": "^1.2.2",
"ts-log": "^2.2.7",
"vue": "^3.5.13",
"vue-router": "4",
"zod": "^3.24.2",
},
"devDependencies": {
"@tailwindcss/postcss": "^4.0.12",
@ -243,6 +245,8 @@
"@types/estree": ["@types/estree@1.0.6", "", {}, "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw=="],
"@types/lodash": ["@types/lodash@4.17.16", "", {}, "sha512-HX7Em5NYQAXKW+1T+FiuG27NGwzJfCX3s1GjOa7ujxZa52kjJLOr4FUxT+giF6Tgxv1e+/czV/iTtBw27WTU9g=="],
"@types/node": ["@types/node@22.13.9", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-acBjXdRJ3A6Pb3tqnw9HZmyR3Fiol3aGxRCK1x3d+6CDAMjl7I649wpSd+yNURCjbOUGu9tqtLKnTGxmK6CyGw=="],
"@types/ws": ["@types/ws@8.5.14", "", { "dependencies": { "@types/node": "*" } }, "sha512-bd/YFLW+URhBzMXurx7lWByOu+xzU9+kb3RboOteXYDfW+tr+JZa99OyNmPINEGB/ahzKrEuc8rcv4gnpJmxTw=="],
@ -557,6 +561,8 @@
"yoctocolors": ["yoctocolors@2.1.1", "", {}, "sha512-GQHQqAopRhwU8Kt1DDM8NjibDXHC8eoh1erhGAJPEyveY9qqVeXvVikNKrDz69sHowPMorbPUrH/mx8c50eiBQ=="],
"zod": ["zod@3.24.2", "", {}, "sha512-lY7CDW43ECgW9u1TcT3IoXHflywfVqDYze4waEz812jR/bZ8FHDsl7pFQoSZTz5N+2NqRXs8GBwnAwo3ZNxqhQ=="],
"@vue/devtools-core/nanoid": ["nanoid@5.1.3", "", { "bin": { "nanoid": "bin/nanoid.js" } }, "sha512-zAbEOEr7u2CbxwoMRlz/pNSpRP0FdAU4pRaYunCdEezWohXFs+a0Xw7RfkKaezMsmSM1vttcLthJtwRnVtOfHQ=="],
"cross-spawn/which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="],

View File

@ -15,6 +15,12 @@
default = pkgs.mkShell {
packages = with pkgs; [
bun
sqlite
sqls
dbeaver-bin
# LSP
typescript-language-server
];
shellHook = ''
export PATH=$PATH:$HOME/.bun/bin

View File

@ -15,12 +15,14 @@
"dependencies": {
"@trpc/client": "^10.45.2",
"@trpc/server": "^10.45.2",
"@types/lodash": "^4.17.16",
"log-symbols": "^7.0.0",
"pinia": "^3.0.1",
"trpc-bun-adapter": "^1.2.2",
"ts-log": "^2.2.7",
"vue": "^3.5.13",
"vue-router": "4"
"vue-router": "4",
"zod": "^3.24.2"
},
"devDependencies": {
"@tailwindcss/postcss": "^4.0.12",

11
server/common.ts Normal file
View File

@ -0,0 +1,11 @@
export function numberToBytes(num: number, bytesLength: number): Uint8Array {
var array = new Uint8Array(bytesLength)
var i;
for (i = 0; i < bytesLength; i++) {
array[i] = num & (0xFF << (i << 3))
}
return array
}

View File

@ -1,7 +1,131 @@
import { Database } from "bun:sqlite";
import { isNumber, isString } from "lodash";
import { z } from "zod";
const db = new Database("lab.sqlite", { strict: true })
initDB();
export function addUser(name: string, password: string) {
const query = db.query()
const boardSchema = z.object({
id: z.number(),
name: z.string(),
room: z.string(),
ipv4: z.string().ip({ version: "v4" }),
ipv6: z.string().ip({ version: "v6" }),
port: z.number().nonnegative(),
cmdID: z.number().nonnegative()
}).partial({
ipv6: true,
cmdID: true
})
export type Board = z.infer<typeof boardSchema>
export function isBoard(obj: any): obj is Board {
return boardSchema.safeParse(obj).success
}
const userSchema = z.object({
id: z.number(),
name: z.string(),
password: z.string(),
boardID: z.number(),
}).partial({
boardID: true,
})
export type User = z.infer<typeof userSchema>
export function isUser(obj: any): obj is User {
return userSchema.safeParse(obj).success
}
function initDB() {
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 function allBoards() {
const query = db.query(`SELECT * FROM Boards`)
const boards = query.all()
query.finalize()
return boards
}
export function findBoard(id?: number | string): Board {
let condition: string
if (isNumber(id)) {
condition = `id=${id}`
} else if (isString(id)) {
condition = `name='${id}'`
} else {
throw new Error("Failure: Wrong type when find board")
}
const query = db.query(`SELECT * FROM Boards WHERE ${condition}`)
const spRet = boardSchema.safeParse(query.get())
if (spRet.success) {
return spRet.data
} else {
throw new Error(`Not Found ${id} FPGA Board`)
}
}
export function findUser(name: string): User {
const query = db.query(`SELECT * FROM Users WHERE name='${name}'`)
const spRet = userSchema.safeParse(query.get())
if (spRet.success) {
return spRet.data
} else {
throw new Error(`Failure: Not found ${name} User`)
}
}

159
server/equipment.ts Normal file
View File

@ -0,0 +1,159 @@
import type { Board } from "./database";
import { boardSchema, findBoard } from "./database";
import { numberToBytes } from "./common";
import { z } from "zod";
import _ from "lodash";
import { udpSocketPool } from "./udp";
export namespace EquipmentPackage {
const HEADER_LENGTH = 8
const BYTES_RETURN_ACK = 0b0001_0000
const BYTES_TRANSFORM_TYPE = 0b0000_1000
const BYTES_READ_WRITE_TYPE = 0b0000_0100
const HeaderOptionsSchema = z.object({
returnAck: z.boolean(),
transformType: z.enum(["Fixed", "Extend"]),
readWriteType: z.enum(["r", "w"])
}).partial()
export type HeaderOptions = z.infer<typeof HeaderOptionsSchema>
export function isHeaderOptions(obj: any): obj is HeaderOptions {
return HeaderOptionsSchema.safeParse(obj).success
}
export class Header {
private commandType: number = 0
private bytesLength: number = 0
private commmandID: number = 0
private _reserved: number = 0
private address: number = 0
constructor(options: HeaderOptions) {
this.setCommandType(options)
}
setCommandType(options: HeaderOptions) {
const validOptions = HeaderOptionsSchema.parse(options)
this.commandType =
(validOptions.returnAck === true ? BYTES_RETURN_ACK : 0) |
(validOptions.transformType === "Extend" ? BYTES_TRANSFORM_TYPE : 0) |
(validOptions.readWriteType === "w" ? BYTES_READ_WRITE_TYPE : 0)
}
setBytesLength(length: number) {
this.bytesLength = length
}
setAddress(address: number) {
this.address = address
}
getCommandType(): HeaderOptions {
return {
returnAck: ((this.commandType & BYTES_RETURN_ACK) === BYTES_RETURN_ACK ? true : false),
transformType: ((this.commandType & BYTES_TRANSFORM_TYPE) === BYTES_TRANSFORM_TYPE ? "Extend" : "Fixed"),
readWriteType: ((this.commandType & BYTES_READ_WRITE_TYPE) === BYTES_READ_WRITE_TYPE ? "w" : "r")
}
}
incCommandID() {
this.commmandID++
}
clearCommandID() {
this.commmandID = 0
}
toUint8Array(): Uint8Array {
var array = new Uint8Array(HEADER_LENGTH)
array[0] = this.commandType
array[1] = this.bytesLength
array[2] = this.commmandID
array[3] = this._reserved
array[4] = this._reserved
let addressBytes = numberToBytes(this.address, 3)
array[5] = addressBytes[0]
array[6] = addressBytes[1]
array[7] = addressBytes[2]
return array
}
}
export class Package {
private header: Header
private body: Uint8Array
constructor(header: Header | HeaderOptions, body?: Uint8Array) {
if (header instanceof Header) {
this.header = header
} else if (isHeaderOptions(header)) {
this.header = new Header(header)
} else {
throw Error("Create EquipmentSocket Failure!")
}
this.body = (body === undefined) ? new Uint8Array(0) : _.cloneDeep(body)
}
toUint8Array(): Uint8Array {
const header = this.header.toUint8Array()
const bodyLength = this.body.length
let total = new Uint8Array(header.length + bodyLength)
total.set(header)
if (bodyLength > 0)
total.set(this.body, header.length)
return total
}
}
}
export class Equipment {
board: Board
constructor(name?: string)
constructor(board?: Board)
constructor(arg1: any) {
if (boardSchema.safeParse(arg1).success) {
this.board = arg1
} else if (typeof arg1 === "string") {
try {
const board = findBoard(arg1)
this.board = boardSchema.parse(board)
} catch (error) {
throw new Error("Equipment Construction Failure")
}
} else {
throw new Error("Equipment Construction Failure")
}
}
send(header: EquipmentPackage.HeaderOptions) {
const pack = new EquipmentPackage.Package(header)
return udpSocketPool.send(pack.toUint8Array(), this.board.port, this.board.ipv4)
}
// TODO: add params file
uploadBitStream() {
const header: EquipmentPackage.HeaderOptions = {
returnAck: true,
transformType: "Extend",
readWriteType: "w"
}
return this.send(header)
}
}

View File

@ -1,8 +1,12 @@
import { router, publicProcedure } from "./trpc.ts"
// import { addUser } from "./database.ts";
export const appRouter = router({
api: router({
status: publicProcedure.query(() => "OK"),
signUp: publicProcedure.query((opts) => {
})
})
});

5
server/test.ts Normal file
View File

@ -0,0 +1,5 @@
import { allTables } from "./database";
const tables = allTables();
console.log(tables);

58
server/udp.ts Normal file
View File

@ -0,0 +1,58 @@
import type { udp } from "bun"
const udpServer = await Bun.udpSocket({
port: 33000,
socket: {
data(_socket, _buf, _port, _addr) {
// todo : Handle Recieved Data
}
}
})
export const udpSocketPool = await createUDPSocketPool(5)
type bunUDPSocket = udp.Socket<"buffer">
export function getUDPServerPort() {
return udpServer.port
}
export class UDPSocketPool {
freeSockets: Set<bunUDPSocket> = new Set()
busySockets: Set<bunUDPSocket> = new Set()
getFreeSocket(): bunUDPSocket {
const socket = this.freeSockets.values().next().value
if (socket !== undefined) {
this.busySockets.add(socket)
this.freeSockets.delete(socket)
} else {
throw Error("Failure: Create udp socket failed")
}
return socket
}
releaseSocket(socket: any) {
this.freeSockets.add(socket)
this.busySockets.delete(socket)
}
send(data: udp.Data, port: number, hostname: string) {
const socket = this.getFreeSocket()
socket.send(data, port, hostname)
this.releaseSocket(socket)
}
}
export async function createUDPSocketPool(depth: number): Promise<UDPSocketPool> {
const pool = new UDPSocketPool()
for (var i = 0; i < depth; i++) {
const socket = await Bun.udpSocket({})
pool.freeSockets.add(socket)
}
return pool
}

View File

@ -23,6 +23,7 @@
<script lang="ts" setup>
import { client } from '@/client';
import { TRPCClientError } from '@trpc/client';
var bitstream = null;
@ -41,9 +42,20 @@ function handleFileChange(event: Event): void {
}
async function uploadBitStream() {
const serverStatus = await client.api.status.query();
try {
const serverStatus = await client.api.status.query();
if (serverStatus != "OK") {
throw new Error("Server Busy...")
}
} catch (error) {
if (error instanceof TRPCClientError) {
console.error("Can't connect to Server!")
} else {
console.error(error)
}
}
console.log(serverStatus)
}
function checkFileType(file: File) {