diff --git a/server/common.test.ts b/server/common.test.ts new file mode 100644 index 0000000..4cb9800 --- /dev/null +++ b/server/common.test.ts @@ -0,0 +1,59 @@ +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])) + + // 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) +}) diff --git a/server/common.ts b/server/common.ts index 18fc612..6264b2e 100644 --- a/server/common.ts +++ b/server/common.ts @@ -6,27 +6,47 @@ 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 + export const Integer = z.number().int() export const UInteger = z.number().int().nonnegative() - export const UInt8 = z.number().int().nonnegative().lt(Math.pow(2, 8)) - export const UInt16 = z.number().int().nonnegative().lt(Math.pow(2, 16)) - export const UInt32 = z.number().int().nonnegative().lt(Math.pow(2, 32)) + export const 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 = z.number().int().lt(Math.pow(2, 7)).gte(-Math.pow(2, 8)) - export const Int16 = z.number().int().lt(Math.pow(2, 15)).gte(-Math.pow(2, 16)) - export const Int32 = z.number().int().lt(Math.pow(2, 31)).gte(-Math.pow(2, 32)) + export 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): Result { + export function numberToBytes(num: number, bytesLength: number, reverse: boolean = false) + : Result { // Check Integer - if (!(Integer.safeParse(num).success && Integer.safeParse(bytesLength).success)) { - return new Err("Not 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) - for (let i = 0; i < bytesLength; i++) { - array[i] = num & (0xFF << (i << 3)) + if (reverse) { + 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) @@ -60,14 +80,47 @@ export namespace type { } export function numberSetBit(num: number, loc: number): number { - return num | (1 << loc) + return num | (1 << (loc - 1)) } export function numberUnsetBit(num: number, loc: number): number { - return num | (~1 << loc) + return num & ~(1 << (loc - 1)) } - export function numberHighBits(num: number) { + 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 { + 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 { + 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 { diff --git a/server/database.ts b/server/database.ts index bc6a101..8bc507e 100644 --- a/server/database.ts +++ b/server/database.ts @@ -2,23 +2,20 @@ 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 } from "./common"; +import { fun, type } from "./common"; const db = new Database("lab.sqlite", { strict: true }) initDB(db); // Error Type -export const BOARD_ERR_WRONG_TYPE = "Wrong type" -export const BOARD_ERR_NO_BOARDS = "No such Boards" -export const BOARD_ERR_ID_CONFLICT = "ID conflict" -export const BOARD_ERR_NAME_CONFLICT = "Name conflict in one room" +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 = ( - typeof BOARD_ERR_WRONG_TYPE | - typeof BOARD_ERR_NO_BOARDS | - typeof BOARD_ERR_ID_CONFLICT | - typeof BOARD_ERR_NAME_CONFLICT -) +export type BoardErrorType = z.infer const boardSchema = z.object({ id: z.number().nonnegative(), @@ -125,7 +122,7 @@ export namespace BoardTable { export function add(board: Board): Result { if (!isBoard(board)) { - return new Err(BOARD_ERR_WRONG_TYPE) + return new Err("Wrong Type") } // Ensure no id conflict @@ -135,9 +132,9 @@ export namespace BoardTable { } else { const retID = includes(board.id) if (retID.isOk()) { - if (retID.value) return new Err(BOARD_ERR_ID_CONFLICT) + if (retID.value) return new Err("ID Conflict") } else { - return new Err(BOARD_ERR_WRONG_TYPE) + return new Err("Wrong Type") } } @@ -146,10 +143,10 @@ export namespace BoardTable { const retName = includes(board.name, board.room) if (retName.isOk()) { if (retName.value) { - return new Err(BOARD_ERR_NAME_CONFLICT) + return new Err("Name Conflict") } } else { - return new Err("Wrong type") + return new Err("Wrong Type") } } @@ -225,7 +222,7 @@ export namespace BoardTable { retBoard = _.cloneDeep(retBoard.value.value) condition = `id=${arg1}` } else { - return new Err(BOARD_ERR_WRONG_TYPE) + return new Err("Wrong Type") } } else if (_.isString(arg1) && _.isString(arg2)) { @@ -234,11 +231,11 @@ export namespace BoardTable { retBoard = _.cloneDeep(retBoard.value.value) condition = `name=${arg1}` } else { - return new Err(BOARD_ERR_NO_BOARDS) + return new Err("No Such Board(s)") } } else { - return new Err(BOARD_ERR_WRONG_TYPE) + return new Err("Wrong Type") } const query = db.query(`DELETE FROM Boards WHERE ${condition}`) @@ -263,7 +260,7 @@ export namespace BoardTable { export function removeByCondition(condition: string): Result, BoardErrorType> { const rsArr = findByCondition(condition) if (rsArr.isNone()) { - return new Err(BOARD_ERR_NO_BOARDS) + return new Err("No Such Board(s)") } const query = db.query(`DELETE FROM Boards WHERE ${condition}`) @@ -276,7 +273,7 @@ export namespace BoardTable { : Result, BoardErrorType> { if (!isBoardColumn(columnName)) { - return new Err(BOARD_ERR_WRONG_TYPE) + return new Err("Wrong Type") } let condition: string @@ -285,17 +282,17 @@ export namespace BoardTable { if (retCond.isSome()) { condition = retCond.value } else { - return new Err(BOARD_ERR_WRONG_TYPE) + return new Err("Wrong Type") } - } else if (fun.isStringArray(val)) { + } else if (type.isStringArray(val)) { const retCond = fun.sqlConditionFromArray(columnName, val, "OR") if (retCond.isSome()) { condition = retCond.value } else { - return new Err(BOARD_ERR_WRONG_TYPE) + return new Err("Wrong Type") } } else { - return new Err(BOARD_ERR_WRONG_TYPE) + return new Err("Wrong Type") } return removeByCondition(condition) @@ -339,7 +336,7 @@ export namespace BoardTable { condition = `name='${arg1}' AND room='${arg2}'` } else { - return new Err(BOARD_ERR_WRONG_TYPE) + return new Err("Wrong Type") } const query = db.query(`SELECT * FROM Boards WHERE ${condition}`) @@ -360,17 +357,17 @@ export namespace BoardTable { if (retCond.isSome()) { condition = retCond.value } else { - return new Err(BOARD_ERR_WRONG_TYPE) + return new Err("Wrong Type") } - } else if (fun.isStringArray(name)) { + } else if (type.isStringArray(name)) { const retCond = fun.sqlConditionFromArray("name", name, "OR") if (retCond.isSome()) { condition = retCond.value } else { - return new Err(BOARD_ERR_WRONG_TYPE) + return new Err("Wrong Type") } } else { - return new Err(BOARD_ERR_WRONG_TYPE) + return new Err("Wrong Type") } return new Ok(findByCondition(condition)) @@ -390,7 +387,7 @@ export namespace BoardTable { : Result>, BoardErrorType> { if (!isBoardColumn(columnName)) { - return new Err(BOARD_ERR_WRONG_TYPE) + return new Err("Wrong Type") } let condition: string @@ -399,17 +396,17 @@ export namespace BoardTable { if (retCond.isSome()) { condition = retCond.value } else { - return new Err(BOARD_ERR_WRONG_TYPE) + return new Err("Wrong Type") } - } else if (fun.isStringArray(val)) { + } else if (type.isStringArray(val)) { const retCond = fun.sqlConditionFromArray(columnName, val, "OR") if (retCond.isSome()) { condition = retCond.value } else { - return new Err(BOARD_ERR_WRONG_TYPE) + return new Err("Wrong Type") } } else { - return new Err(BOARD_ERR_WRONG_TYPE) + return new Err("Wrong Type") } return new Ok(findByCondition(condition)) @@ -427,13 +424,13 @@ export namespace BoardTable { condition = `name='${arg1}'` } else { - return new Err(BOARD_ERR_WRONG_TYPE) + return new Err("Wrong Type") } } else { if (_.isString(arg1) && _.isString(arg2)) { condition = `name='${arg1} AND room=${arg2}'` } else { - return new Err(BOARD_ERR_WRONG_TYPE) + return new Err("Wrong Type") } }