/* * Copyright 2021 Juan Cruz Viotti * * 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 { strict as assert } from 'assert' import ResizableBuffer from '../resizable-buffer' import { BitsetResult, bitsetDecode } from './bitset' import { IntegerListResult, integerListDecode } from './integer-list' import { JSONObject } from '../../json' import { IntegerResult, FLOOR_MULTIPLE_ENUM_VARINT } from '../integer/decode' import { BoundedTypedOptions, RequiredUnboundedTypedOptions, OptionalUnboundedTypedOptions, UnboundedTypedOptions, TypedKeysOptions, OptionalBoundedTypedOptions, RequiredBoundedTypedOptions, PackedUnboundedOptions, PackedRequiredBoundedOptions, SizeTypedKeysOptions } from './options' import { decode, DecodeResult } from '../index' import { Encoding } from '../../mapper' export interface ObjectResult extends DecodeResult { readonly value: JSONObject; readonly bytes: number; } export const REQUIRED_ONLY_BOUNDED_TYPED_OBJECT = ( buffer: ResizableBuffer, offset: number, options: RequiredBoundedTypedOptions ): ObjectResult => { const result: JSONObject = {} const booleanResult: BitsetResult = bitsetDecode(buffer, offset, options.booleanRequiredProperties.length) for (const [ index, bit ] of booleanResult.value.entries()) { Reflect.set(result, options.booleanRequiredProperties[index], bit) } let cursor: number = offset + booleanResult.bytes for (const key of options.requiredProperties) { const encoding: Encoding | undefined = options.propertyEncodings[key] assert(typeof encoding !== 'undefined') const propertyResult: DecodeResult = decode(buffer, cursor, encoding) assert(propertyResult.bytes >= 0) Reflect.set(result, key, propertyResult.value) cursor += propertyResult.bytes } return { value: result, bytes: cursor - offset } } export const NON_REQUIRED_BOUNDED_TYPED_OBJECT = ( buffer: ResizableBuffer, offset: number, options: OptionalBoundedTypedOptions ): ObjectResult => { const bitsetLength: IntegerResult = FLOOR_MULTIPLE_ENUM_VARINT(buffer, offset, { minimum: 0, multiplier: 1 }) assert(bitsetLength.value >= 0) const bitsetResult: BitsetResult = bitsetDecode( buffer, offset + bitsetLength.bytes, bitsetLength.value) assert(bitsetResult.value.length === bitsetLength.value) const result: JSONObject = {} let cursor: number = offset + bitsetLength.bytes + bitsetResult.bytes for (const [ index, value ] of bitsetResult.value.entries()) { if (!value) { continue } const key: string = options.optionalProperties[index] const encoding: Encoding | undefined = options.propertyEncodings[key] assert(typeof encoding !== 'undefined') const propertyResult: DecodeResult = decode(buffer, cursor, encoding) assert(propertyResult.bytes >= 0) Reflect.set(result, key, propertyResult.value) cursor += propertyResult.bytes } return { value: result, bytes: cursor - offset } } export const MIXED_BOUNDED_TYPED_OBJECT = ( buffer: ResizableBuffer, offset: number, options: BoundedTypedOptions ): ObjectResult => { const requiredResult: ObjectResult = REQUIRED_ONLY_BOUNDED_TYPED_OBJECT( buffer, offset, { propertyEncodings: options.propertyEncodings, requiredProperties: options.requiredProperties, booleanRequiredProperties: options.booleanRequiredProperties }) const optionalResult: ObjectResult = NON_REQUIRED_BOUNDED_TYPED_OBJECT( buffer, offset + requiredResult.bytes, { propertyEncodings: options.propertyEncodings, optionalProperties: options.optionalProperties }) return { bytes: requiredResult.bytes + optionalResult.bytes, value: Object.assign(requiredResult.value, optionalResult.value) } } export const ARBITRARY_TYPED_KEYS_OBJECT_WITHOUT_LENGTH = ( buffer: ResizableBuffer, offset: number, options: SizeTypedKeysOptions ): ObjectResult => { assert(options.size >= 0) let count: number = 0 let cursor: number = offset const value: JSONObject = {} while (count < options.size) { const keyResult: DecodeResult = decode(buffer, cursor, options.keyEncoding) assert(typeof keyResult.value === 'string') assert(keyResult.bytes >= 0) cursor += keyResult.bytes const valueResult: DecodeResult = decode(buffer, cursor, options.encoding) assert(valueResult.bytes >= 0) cursor += valueResult.bytes Reflect.set(value, keyResult.value, valueResult.value) count += 1 } return { value, bytes: cursor - offset } } export const ARBITRARY_TYPED_KEYS_OBJECT = ( buffer: ResizableBuffer, offset: number, options: TypedKeysOptions ): ObjectResult => { const sizeResult: IntegerResult = FLOOR_MULTIPLE_ENUM_VARINT(buffer, offset, { minimum: 0, multiplier: 1 }) const result: ObjectResult = ARBITRARY_TYPED_KEYS_OBJECT_WITHOUT_LENGTH( buffer, offset + sizeResult.bytes, { encoding: options.encoding, keyEncoding: options.keyEncoding, size: sizeResult.value }) return { bytes: sizeResult.bytes + result.bytes, value: result.value } } export const REQUIRED_UNBOUNDED_TYPED_OBJECT = ( buffer: ResizableBuffer, offset: number, options: RequiredUnboundedTypedOptions ): ObjectResult => { const requiredResult: ObjectResult = REQUIRED_ONLY_BOUNDED_TYPED_OBJECT( buffer, offset, { propertyEncodings: options.propertyEncodings, requiredProperties: options.requiredProperties, booleanRequiredProperties: options.booleanRequiredProperties }) const arbitraryResult: ObjectResult = ARBITRARY_TYPED_KEYS_OBJECT( buffer, offset + requiredResult.bytes, { keyEncoding: options.keyEncoding, encoding: options.encoding }) return { bytes: requiredResult.bytes + arbitraryResult.bytes, value: Object.assign(requiredResult.value, arbitraryResult.value) } } export const OPTIONAL_UNBOUNDED_TYPED_OBJECT = ( buffer: ResizableBuffer, offset: number, options: OptionalUnboundedTypedOptions ): ObjectResult => { const optionalResult: ObjectResult = NON_REQUIRED_BOUNDED_TYPED_OBJECT( buffer, offset, { propertyEncodings: options.propertyEncodings, optionalProperties: options.optionalProperties }) const arbitraryResult: ObjectResult = ARBITRARY_TYPED_KEYS_OBJECT( buffer, offset + optionalResult.bytes, { keyEncoding: options.keyEncoding, encoding: options.encoding }) return { bytes: optionalResult.bytes + arbitraryResult.bytes, value: Object.assign(optionalResult.value, arbitraryResult.value) } } export const MIXED_UNBOUNDED_TYPED_OBJECT = ( buffer: ResizableBuffer, offset: number, options: UnboundedTypedOptions ): ObjectResult => { const requiredResult: ObjectResult = REQUIRED_ONLY_BOUNDED_TYPED_OBJECT( buffer, offset, { propertyEncodings: options.propertyEncodings, requiredProperties: options.requiredProperties, booleanRequiredProperties: options.booleanRequiredProperties }) const optionalResult: ObjectResult = NON_REQUIRED_BOUNDED_TYPED_OBJECT( buffer, offset + requiredResult.bytes, { propertyEncodings: options.propertyEncodings, optionalProperties: options.optionalProperties }) const arbitraryResult: ObjectResult = ARBITRARY_TYPED_KEYS_OBJECT( buffer, offset + requiredResult.bytes + optionalResult.bytes, { keyEncoding: options.keyEncoding, encoding: options.encoding }) return { bytes: requiredResult.bytes + optionalResult.bytes + arbitraryResult.bytes, value: Object.assign(requiredResult.value, optionalResult.value, arbitraryResult.value) } } export const PACKED_UNBOUNDED_OBJECT = ( buffer: ResizableBuffer, offset: number, options: PackedUnboundedOptions ): ObjectResult => { const packedLength: IntegerResult = FLOOR_MULTIPLE_ENUM_VARINT(buffer, offset, { minimum: 0, multiplier: 1 }) const packedResult: IntegerListResult = integerListDecode( buffer, offset + packedLength.bytes, packedLength.value, { minimum: options.packedEncoding.options.minimum, maximum: options.packedEncoding.options.maximum }) const result: JSONObject = {} for (const [ index, key ] of options.packedRequiredProperties.entries()) { Reflect.set(result, key, packedResult.value[index]) } const rest: ObjectResult = MIXED_UNBOUNDED_TYPED_OBJECT( buffer, offset + packedLength.bytes + packedResult.bytes, { requiredProperties: options.requiredProperties, booleanRequiredProperties: options.booleanRequiredProperties, optionalProperties: options.optionalProperties, keyEncoding: options.keyEncoding, encoding: options.encoding, propertyEncodings: options.propertyEncodings }) return { value: Object.assign(result, rest.value), bytes: packedLength.bytes + packedResult.bytes + rest.bytes } } export const PACKED_BOUNDED_REQUIRED_OBJECT = ( buffer: ResizableBuffer, offset: number, options: PackedRequiredBoundedOptions ): ObjectResult => { const packedResult: IntegerListResult = integerListDecode( buffer, offset, options.packedRequiredProperties.length, { minimum: options.packedEncoding.options.minimum, maximum: options.packedEncoding.options.maximum }) const result: JSONObject = {} for (const [ index, key ] of options.packedRequiredProperties.entries()) { Reflect.set(result, key, packedResult.value[index]) } const rest: ObjectResult = REQUIRED_ONLY_BOUNDED_TYPED_OBJECT( buffer, offset + packedResult.bytes, { propertyEncodings: options.propertyEncodings, requiredProperties: options.requiredProperties, booleanRequiredProperties: options.booleanRequiredProperties }) return { value: Object.assign(result, rest.value), bytes: packedResult.bytes + rest.bytes } }