| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you 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 TypeResolver from "./typeResolver"; |
| import { BinaryWriter } from "./writer"; |
| import { BinaryReader } from "./reader"; |
| import { ReferenceResolver } from "./referenceResolver"; |
| import { ConfigFlags, Serializer, Config, ForyTypeInfoSymbol, WithForyClsInfo, TypeId, CustomSerializer } from "./type"; |
| import { OwnershipError } from "./error"; |
| import { InputType, ResultType, TypeInfo } from "./typeInfo"; |
| import { Gen } from "./gen"; |
| import { TypeMeta } from "./meta/TypeMeta"; |
| import { PlatformBuffer } from "./platformBuffer"; |
| import { TypeMetaResolver } from "./typeMetaResolver"; |
| import { MetaStringResolver } from "./metaStringResolver"; |
| |
| const DEFAULT_DEPTH_LIMIT = 50 as const; |
| const MIN_DEPTH_LIMIT = 2 as const; |
| |
| export default class { |
| binaryReader: BinaryReader; |
| binaryWriter: BinaryWriter; |
| typeResolver: TypeResolver; |
| typeMetaResolver: TypeMetaResolver; |
| metaStringResolver: MetaStringResolver; |
| referenceResolver: ReferenceResolver; |
| anySerializer: Serializer; |
| typeMeta = TypeMeta; |
| config: Config; |
| depth = 0; |
| maxDepth: number; |
| |
| constructor(config?: Partial<Config>) { |
| this.config = this.initConfig(config); |
| const maxDepth = config?.maxDepth ?? DEFAULT_DEPTH_LIMIT; |
| if (!Number.isInteger(maxDepth) || maxDepth < MIN_DEPTH_LIMIT) { |
| throw new Error(`maxDepth must be an integer >= ${MIN_DEPTH_LIMIT} but got ${maxDepth}`); |
| } |
| this.maxDepth = maxDepth; |
| this.binaryReader = new BinaryReader(this.config); |
| this.binaryWriter = new BinaryWriter(this.config); |
| this.referenceResolver = new ReferenceResolver(this.binaryReader); |
| this.typeMetaResolver = new TypeMetaResolver(this); |
| this.typeResolver = new TypeResolver(this); |
| this.metaStringResolver = new MetaStringResolver(this); |
| this.typeResolver.init(); |
| this.anySerializer = this.typeResolver.getSerializerById(TypeId.UNKNOWN); |
| } |
| |
| private initConfig(config: Partial<Config> | undefined) { |
| return { |
| refTracking: config?.refTracking !== null ? Boolean(config?.refTracking) : null, |
| useSliceString: Boolean(config?.useSliceString), |
| maxDepth: config?.maxDepth, |
| hooks: config?.hooks || {}, |
| compatible: Boolean(config?.compatible), |
| }; |
| } |
| |
| isCompatible() { |
| return this.config.compatible === true; |
| } |
| |
| incReadDepth(): void { |
| this.depth++; |
| if (this.depth > this.maxDepth) { |
| throw new Error( |
| `Deserialization depth limit exceeded: ${this.depth} > ${this.maxDepth}. ` |
| + "The data may be malicious, or increase maxDepth if needed." |
| ); |
| } |
| } |
| |
| decReadDepth(): void { |
| this.depth--; |
| } |
| |
| private resetRead(): void { |
| this.referenceResolver.resetRead(); |
| this.typeMetaResolver.resetRead(); |
| this.metaStringResolver.resetRead(); |
| this.depth = 0; |
| } |
| |
| private resetWrite(): void { |
| this.binaryWriter.reset(); |
| this.referenceResolver.resetWrite(); |
| this.metaStringResolver.resetWrite(); |
| this.typeMetaResolver.resetWrite(); |
| } |
| |
| registerSerializer<T>(constructor: new () => T, customSerializer: CustomSerializer<T>): { |
| serializer: Serializer; |
| serialize(data: InputType<T> | null): PlatformBuffer; |
| serializeVolatile(data: InputType<T>): { |
| get: () => Uint8Array; |
| dispose: () => void; |
| }; |
| deserialize(bytes: Uint8Array): ResultType<T>; |
| }; |
| registerSerializer<T extends TypeInfo>(typeInfo: T): { |
| serializer: Serializer; |
| serialize(data: InputType<T> | null): PlatformBuffer; |
| serializeVolatile(data: InputType<T>): { |
| get: () => Uint8Array; |
| dispose: () => void; |
| }; |
| deserialize(bytes: Uint8Array): ResultType<T>; |
| }; |
| registerSerializer<T extends new () => any>(constructor: T): { |
| serializer: Serializer; |
| serialize(data: Partial<InstanceType<T>> | null): PlatformBuffer; |
| serializeVolatile(data: Partial<InstanceType<T>>): { |
| get: () => Uint8Array; |
| dispose: () => void; |
| }; |
| deserialize(bytes: Uint8Array): InstanceType<T> | null; |
| }; |
| registerSerializer(constructor: any, customSerializer?: CustomSerializer<any>) { |
| let serializer: Serializer; |
| TypeInfo.attach(this); |
| if (constructor.prototype?.[ForyTypeInfoSymbol]) { |
| const typeInfo: TypeInfo = (<WithForyClsInfo>(constructor.prototype[ForyTypeInfoSymbol])).structTypeInfo; |
| typeInfo.freeze(); |
| serializer = new Gen(this, { creator: constructor, customSerializer }).generateSerializer(typeInfo); |
| this.typeResolver.registerSerializer(typeInfo, serializer); |
| } else { |
| const typeInfo = constructor; |
| typeInfo.freeze(); |
| serializer = new Gen(this).generateSerializer(typeInfo); |
| this.typeResolver.registerSerializer(typeInfo, serializer); |
| } |
| TypeInfo.detach(); |
| return { |
| serializer, |
| serialize: (data: any) => { |
| return this.serialize(data, serializer); |
| }, |
| serializeVolatile: (data: any) => { |
| return this.serializeVolatile(data, serializer); |
| }, |
| deserialize: (bytes: Uint8Array) => { |
| if (TypeId.polymorphicType(serializer.getTypeId())) { |
| return this.deserialize(bytes, serializer); |
| } |
| return this.deserialize(bytes); |
| }, |
| }; |
| } |
| |
| replaceSerializerReader(typeInfo: TypeInfo) { |
| TypeInfo.attach(this); |
| const serializer = new Gen(this, { creator: (typeInfo).options!.creator! }).reGenerateSerializer(typeInfo); |
| const result = this.typeResolver.registerSerializer(typeInfo, { |
| getHash: serializer.getHash, |
| read: serializer.read, |
| readNoRef: serializer.readNoRef, |
| readRef: serializer.readRef, |
| readTypeInfo: serializer.readTypeInfo, |
| readRefWithoutTypeInfo: serializer.readRefWithoutTypeInfo, |
| } as any)!; |
| TypeInfo.detach(); |
| return result; |
| } |
| |
| deserialize<T = any>(bytes: Uint8Array, serializer: Serializer = this.anySerializer): T | null { |
| this.resetRead(); |
| this.binaryReader.reset(bytes); |
| const bitmap = this.binaryReader.readUint8(); |
| if ((bitmap & ConfigFlags.isNullFlag) === ConfigFlags.isNullFlag) { |
| return null; |
| } |
| const isCrossLanguage = (bitmap & ConfigFlags.isCrossLanguageFlag) == ConfigFlags.isCrossLanguageFlag; |
| if (!isCrossLanguage) { |
| throw new Error("support crosslanguage mode only"); |
| } |
| const isOutOfBandEnabled = (bitmap & ConfigFlags.isOutOfBandFlag) === ConfigFlags.isOutOfBandFlag; |
| if (isOutOfBandEnabled) { |
| throw new Error("outofband mode is not supported now"); |
| } |
| return serializer.readRef(); |
| } |
| |
| private serializeInternal<T = any>(data: T, serializer: Serializer) { |
| try { |
| this.resetWrite(); |
| } catch (e) { |
| if (e instanceof OwnershipError) { |
| throw new Error("Permission denied. To release the serialization ownership, you must call the dispose function returned by serializeVolatile."); |
| } |
| throw e; |
| } |
| let bitmap = 0; |
| if (data === null) { |
| bitmap |= ConfigFlags.isNullFlag; |
| } |
| bitmap |= ConfigFlags.isCrossLanguageFlag; |
| this.binaryWriter.writeUint8(bitmap); |
| // reserve fixed size |
| this.binaryWriter.reserve(serializer.fixedSize); |
| // start write |
| serializer.writeRef(data); |
| return this.binaryWriter; |
| } |
| |
| serialize<T = any>(data: T, serializer: Serializer = this.anySerializer) { |
| return this.serializeInternal(data, serializer).dump(); |
| } |
| |
| serializeVolatile<T = any>(data: T, serializer: Serializer = this.anySerializer) { |
| return this.serializeInternal(data, serializer).dumpAndOwn(); |
| } |
| } |