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}