/*
* Copyright (c) 2025 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { float32, int32, int64, float32FromBits } from "@koalaui/common"
import { pointer, KUint8ArrayPtr } from "./InteropTypes"
import { KBuffer } from "./buffer"
import { NativeBuffer } from "./NativeBuffer"
import { InteropNativeModule } from "./InteropNativeModule"
import { Tags, CallbackResource } from "./SerializerBase";
export class DeserializerBase {
private position = 0
private readonly buffer: KBuffer
private readonly length: int32
private static customDeserializers: CustomDeserializer | undefined = new DateDeserializer()
static registerCustomDeserializer(deserializer: CustomDeserializer) {
let current = DeserializerBase.customDeserializers
if (current == undefined) {
DeserializerBase.customDeserializers = deserializer
} else {
while (current!.next != undefined) {
current = current!.next
}
current!.next = deserializer
}
}
constructor(buffer: KUint8ArrayPtr, length: int32) {
this.buffer = new KBuffer(buffer)
this.length = length
}
static get<T extends DeserializerBase>(
factory: (args: Uint8Array, length: int32) => T,
args: Uint8Array, length: int32): T {
// TBD: Use cache
return factory(args, length);
}
asArray(): KUint8ArrayPtr {
return this.buffer.buffer
}
currentPosition(): int32 {
return this.position
}
resetCurrentPosition(): void {
this.position = 0
}
private checkCapacity(value: int32) {
if (value > this.length) {
throw new Error(`${value} is less than remaining buffer length`)
}
}
readInt8(): int32 {
this.checkCapacity(1)
const value = this.buffer.get(this.position)
this.position += 1
return value
}
readInt32(): int32 {
this.checkCapacity(4)
let res: int32 = 0;
for (let i = 0; i < 4; i++) {
let byteVal = this.buffer.get(this.position + i) as int32;
byteVal &= 0xff
res = (res | byteVal << (8 * i)) as int32;
}
this.position += 4
return res
}
readPointer(): pointer {
this.checkCapacity(8)
let res: int64 = 0;
for (let i = 0; i < 8; i++) {
let byteVal = this.buffer.get(this.position + i) as int64;
byteVal &= 0xff
res = (res | byteVal << (8 * i)) as int64;
}
this.position += 8
return res
}
readInt64(): int64 {
this.checkCapacity(8)
let res: int64 = 0;
for (let i = 0; i < 8; i++) {
let byteVal = this.buffer.get(this.position + i) as int64;
byteVal &= 0xff
res = (res | byteVal << (8 * i)) as int64;
}
this.position += 8
return res
}
readFloat32(): float32 {
this.checkCapacity(4)
let res: int32 = 0;
for (let i = 0; i < 4; i++) {
let byteVal = this.buffer.get(this.position + i) as int32;
byteVal &= 0xff
res = (res | byteVal << (8 * i)) as int32;
}
this.position += 4
return float32FromBits(res)
}
readBoolean(): boolean {
this.checkCapacity(1)
const value = this.buffer.get(this.position)
this.position += 1
return value == 1
}
readFunction(): int32 {
// TODO: not exactly correct.
const id = this.readInt32()
return id
}
// readMaterialized(): object {
// const ptr = this.readPointer()
// return { ptr: ptr }
// }
readCallbackResource(): CallbackResource {
return ({
resourceId: this.readInt32(),
hold: this.readPointer(),
release: this.readPointer(),
} as CallbackResource)
}
readString(): string {
const length = this.readInt32()
this.checkCapacity(length)
// read without null-terminated byte
const value = InteropNativeModule._Utf8ToString(this.buffer.buffer, this.position, length)
this.position += length
return value
}
readCustomObject(kind: string): object {
let current = DeserializerBase.customDeserializers
while (current) {
if (current!.supports(kind)) {
return current!.deserialize(this, kind)
}
current = current!.next
}
// consume tag
const tag = this.readInt8()
throw Error(`${kind} is not supported`)
}
readNumber(): number | undefined {
const tag = this.readInt8()
if (tag == Tags.UNDEFINED) {
return undefined
} else if (tag == Tags.INT32) {
return this.readInt32()
} else if (tag == Tags.FLOAT32) {
return this.readFloat32()
} else {
throw new Error(`Unknown number tag: ${tag}`)
}
}
static lengthUnitFromInt(unit: int32): string {
let suffix: string
switch (unit) {
case 0:
suffix = "px"
break
case 1:
suffix = "vp"
break
case 3:
suffix = "%"
break
case 4:
suffix = "lpx"
break
default:
suffix = "<unknown>"
}
return suffix
}
readBuffer(): NativeBuffer {
/* not implemented */
const resource = this.readCallbackResource()
const data = this.readPointer()
const length = this.readInt64()
return NativeBuffer.wrap(data, length, resource.resourceId, resource.hold, resource.release)
}
readUint8ClampedArray(): Uint8ClampedArray {
throw new Error("Not implemented")
}
}
export abstract class CustomDeserializer {
protected supported: string
protected constructor(supported_: string) {
this.supported = supported_
}
supports(kind: string): boolean {
return this.supported.includes(kind)
}
abstract deserialize(serializer: DeserializerBase, kind: string): object
next: CustomDeserializer | undefined = undefined
}
class DateDeserializer extends CustomDeserializer {
constructor() {
super("Date")
}
deserialize(serializer: DeserializerBase, kind: string): Date {
return new Date(serializer.readString())
}
}