• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/*
2 * Copyright (c) 2025 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16import { float32, int32, int64, float32FromBits } from "@koalaui/common"
17import { pointer, KUint8ArrayPtr } from "./InteropTypes"
18import { KBuffer } from "./buffer"
19import { NativeBuffer } from "./NativeBuffer"
20import { InteropNativeModule } from "./InteropNativeModule"
21import { Tags, CallbackResource } from "./SerializerBase";
22
23export class DeserializerBase {
24    private position = 0
25    private readonly buffer: KBuffer
26    private readonly length: int32
27    private static customDeserializers: CustomDeserializer | undefined = new DateDeserializer()
28
29    static registerCustomDeserializer(deserializer: CustomDeserializer) {
30        let current = DeserializerBase.customDeserializers
31        if (current == undefined) {
32            DeserializerBase.customDeserializers = deserializer
33        } else {
34            while (current!.next != undefined) {
35                current = current!.next
36            }
37            current!.next = deserializer
38        }
39    }
40
41    constructor(buffer: KUint8ArrayPtr, length: int32) {
42        this.buffer = new KBuffer(buffer)
43        this.length = length
44    }
45
46    static get<T extends DeserializerBase>(
47        factory: (args: Uint8Array, length: int32) => T,
48        args: Uint8Array, length: int32): T {
49
50        // TBD: Use cache
51        return factory(args, length);
52    }
53
54    asArray(): KUint8ArrayPtr {
55        return this.buffer.buffer
56    }
57
58    currentPosition(): int32 {
59        return this.position
60    }
61
62    resetCurrentPosition(): void {
63        this.position = 0
64    }
65
66    private checkCapacity(value: int32) {
67        if (value > this.length) {
68            throw new Error(`${value} is less than remaining buffer length`)
69        }
70    }
71
72    readInt8(): int32 {
73        this.checkCapacity(1)
74        const value = this.buffer.get(this.position)
75        this.position += 1
76        return value
77    }
78
79    readInt32(): int32 {
80        this.checkCapacity(4)
81        let res: int32 = 0;
82        for (let i = 0; i < 4; i++) {
83            let byteVal = this.buffer.get(this.position + i) as int32;
84            byteVal &= 0xff
85            res = (res | byteVal << (8 * i)) as int32;
86        }
87        this.position += 4
88        return res
89    }
90
91    readPointer(): pointer {
92        this.checkCapacity(8)
93        let res: int64 = 0;
94        for (let i = 0; i < 8; i++) {
95            let byteVal = this.buffer.get(this.position + i) as int64;
96            byteVal &= 0xff
97            res = (res | byteVal << (8 * i)) as int64;
98        }
99        this.position += 8
100        return res
101    }
102
103    readInt64(): int64 {
104        this.checkCapacity(8)
105        let res: int64 = 0;
106        for (let i = 0; i < 8; i++) {
107            let byteVal = this.buffer.get(this.position + i) as int64;
108            byteVal &= 0xff
109            res = (res | byteVal << (8 * i)) as int64;
110        }
111        this.position += 8
112        return res
113    }
114
115    readFloat32(): float32 {
116        this.checkCapacity(4)
117        let res: int32 = 0;
118        for (let i = 0; i < 4; i++) {
119            let byteVal = this.buffer.get(this.position + i) as int32;
120            byteVal &= 0xff
121            res = (res | byteVal << (8 * i)) as int32;
122        }
123        this.position += 4
124        return float32FromBits(res)
125    }
126
127    readBoolean(): boolean {
128        this.checkCapacity(1)
129        const value = this.buffer.get(this.position)
130        this.position += 1
131        return value == 1
132    }
133
134    readFunction(): int32 {
135        // TODO: not exactly correct.
136        const id = this.readInt32()
137        return id
138    }
139
140    // readMaterialized(): object {
141    //     const ptr = this.readPointer()
142    //     return { ptr: ptr }
143    // }
144
145    readCallbackResource(): CallbackResource {
146        return ({
147            resourceId: this.readInt32(),
148            hold: this.readPointer(),
149            release: this.readPointer(),
150        } as CallbackResource)
151    }
152
153    readString(): string {
154        const length = this.readInt32()
155        this.checkCapacity(length)
156        // read without null-terminated byte
157        const value = InteropNativeModule._Utf8ToString(this.buffer.buffer, this.position, length)
158        this.position += length
159        return value
160    }
161
162    readCustomObject(kind: string): object {
163        let current = DeserializerBase.customDeserializers
164        while (current) {
165            if (current!.supports(kind)) {
166                return current!.deserialize(this, kind)
167            }
168            current = current!.next
169        }
170        // consume tag
171        const tag = this.readInt8()
172        throw Error(`${kind} is not supported`)
173    }
174
175    readNumber(): number | undefined {
176        const tag = this.readInt8()
177        if (tag == Tags.UNDEFINED) {
178            return undefined
179        } else if (tag == Tags.INT32) {
180            return this.readInt32()
181        } else if (tag == Tags.FLOAT32) {
182            return this.readFloat32()
183        } else {
184            throw new Error(`Unknown number tag: ${tag}`)
185        }
186    }
187
188    static lengthUnitFromInt(unit: int32): string {
189        let suffix: string
190        switch (unit) {
191            case 0:
192                suffix = "px"
193                break
194            case 1:
195                suffix = "vp"
196                break
197            case 3:
198                suffix = "%"
199                break
200            case 4:
201                suffix = "lpx"
202                break
203            default:
204                suffix = "<unknown>"
205        }
206        return suffix
207    }
208
209    readBuffer(): NativeBuffer {
210        /* not implemented */
211        const resource = this.readCallbackResource()
212        const data = this.readPointer()
213        const length = this.readInt64()
214        return NativeBuffer.wrap(data, length, resource.resourceId, resource.hold, resource.release)
215    }
216
217    readUint8ClampedArray(): Uint8ClampedArray {
218        throw new Error("Not implemented")
219    }
220}
221
222export abstract class CustomDeserializer {
223    protected supported: string
224    protected constructor(supported_: string) {
225        this.supported = supported_
226    }
227
228    supports(kind: string): boolean {
229        return this.supported.includes(kind)
230    }
231
232    abstract deserialize(serializer: DeserializerBase, kind: string): object
233
234    next: CustomDeserializer | undefined = undefined
235}
236
237class DateDeserializer extends CustomDeserializer {
238    constructor() {
239        super("Date")
240    }
241
242    deserialize(serializer: DeserializerBase, kind: string): Date {
243        return new Date(serializer.readString())
244    }
245}