module import { debuglog } from "util" ; import { assert } from "node-opcua-assert" ; import { decodeString , encodeString , UAString , UInt8 } from "node-opcua-basic-types" ; import { BinaryStream , OutputBinaryStream } from "node-opcua-binary-stream" ; import { registerBasicType } from "node-opcua-factory" ; import { StatusCode , StatusCodes } from "node-opcua-status-code" ; const NUMERIC_RANGE_EMPTY_STRING = "NumericRange:<Empty>" ; export const schemaNumericRange = { name : "NumericRange" , subType : "UAString" , defaultValue : ( ) : NumericRange => { return new NumericRange ( ) ; } , encode : encodeNumericRange , decode : decodeNumericRange , random : ( ) : NumericRange => { function r ( ) { return Math . ceil ( Math . random ( ) * 100 ) ; } const start = r ( ) ; const end = start + r ( ) ; return new NumericRange ( start , end ) ; } , coerce : coerceNumericRange } ; registerBasicType ( schemaNumericRange ) ; export enum NumericRangeType { Empty = 0 , SingleValue = 1 , ArrayRange = 2 , MatrixRange = 3 , InvalidRange = 4 } const regexNumericRange = /^[0-9:,]*$/ ; function _valid_range ( low , high ) { return ! ( low >= high || low < 0 || high < 0 ) ; } type NumericalRangeValueType = null | number | string | number [ ] | number [ ] [ ] ; export interface NumericalRangeSingleValue { type : NumericRangeType . SingleValue ; value : number ; } export interface NumericalRangeArrayRange { type : NumericRangeType . ArrayRange ; value : number [ ] ; } export interface NumericalRangeMatrixRange { type : NumericRangeType . MatrixRange ; value : number [ ] [ ] ; } export interface NumericalRangeEmpty { type : NumericRangeType . Empty ; value : null ; } export interface NumericalRangeInvalid { type : NumericRangeType . InvalidRange ; value : string ; } export type NumericalRange0 = NumericalRangeSingleValue | NumericalRangeArrayRange | NumericalRangeMatrixRange | NumericalRangeEmpty | NumericalRangeInvalid ; export interface NumericalRange1 { type : NumericRangeType ; value : NumericalRangeValueType ; } function construct_numeric_range_bit_from_string ( str ) { const values = str . split ( ":" ) ; if ( values . length === 1 ) { return { type : NumericRangeType . SingleValue , value : parseInt ( values [ 0 ] , 10 ) } ; } else if ( values . length === 2 ) { const array = values . map ( ( a ) => parseInt ( a , 10 ) ) ; if ( ! _valid_range ( array [ 0 ] , array [ 1 ] ) ) { return { type : NumericRangeType . InvalidRange , value : str } ; } return { type : NumericRangeType . ArrayRange , value : array } ; } else { return { type : NumericRangeType . InvalidRange , value : str } ; } } function _normalize ( e ) { if ( e . type === NumericRangeType . SingleValue ) { const ee = e as NumericalRangeSingleValue ; return [ ee . value , ee . value ] ; } return e . value as number ; } function construct_numeric_range_from_string ( str ) { if ( ! regexNumericRange . test ( str ) ) { return { type : NumericRangeType . InvalidRange , value : str } ; } const values = str . split ( "," ) ; if ( values . length === 1 ) { return construct_numeric_range_bit_from_string ( values [ 0 ] ) ; } else if ( values . length === 2 ) { const elements = values . map ( construct_numeric_range_bit_from_string ) ; let rowRange = elements [ 0 ] ; let colRange = elements [ 1 ] ; if ( rowRange . type === NumericRangeType . InvalidRange || colRange . type === NumericRangeType . InvalidRange ) { return { type : NumericRangeType . InvalidRange , value : str } ; } rowRange = _normalize ( rowRange ) ; colRange = _normalize ( colRange ) ; return { type : NumericRangeType . MatrixRange , value : [ rowRange , colRange ] } ; } else { return { type : NumericRangeType . InvalidRange , value : str } ; } } function construct_from_string ( value ) { return construct_numeric_range_from_string ( value ) ; } function _set_single_value ( value ) { if ( value === null || value < 0 || ! isFinite ( value ) ) { return { type : NumericRangeType . InvalidRange , value : "" + value ?. toString ( ) } ; } else { return { type : NumericRangeType . SingleValue , value : value } ; } } function _check_range ( numericalRange ) { switch ( numericalRange . type ) { case NumericRangeType . ArrayRange : return _valid_range ( numericalRange . value [ 0 ] , numericalRange . value [ 1 ] ) ; } throw new Error ( "unsupported case" ) ; } function _set_range_value ( low , high ) { if ( low === high ) { return { type : NumericRangeType . SingleValue , value : low } ; } const numericalRange = { type : NumericRangeType . ArrayRange , value : [ low , high ] } ; if ( ! _check_range ( numericalRange as NumericalRangeArrayRange ) ) { return { type : NumericRangeType . InvalidRange , value : "" } ; } return numericalRange ; } function construct_from_values ( value , secondValue ? ) { if ( secondValue === undefined ) { return _set_single_value ( value ) ; } else { if ( ! isFinite ( secondValue ) ) { throw new Error ( " invalid second argument, expecting a number" ) ; } return _set_range_value ( value , secondValue ) ; } } function _construct_from_array ( value , value2 ? ) { assert ( value . length === 2 ) ; if ( ! isFinite ( value [ 0 ] ) || ! isFinite ( value [ 1 ] ) ) { return { type : NumericRangeType . InvalidRange , value : "" + value } ; } let range1 = _set_range_value ( value [ 0 ] , value [ 1 ] ) ; if ( ! value2 ) { return range1 ; } const nr2 = new NumericRange ( value2 ) ; if ( nr2 . type === NumericRangeType . InvalidRange || nr2 . type === NumericRangeType . MatrixRange || nr2 . type === NumericRangeType . Empty ) { return { type : NumericRangeType . InvalidRange , value : "" + value } ; } if ( range1 . type === NumericRangeType . SingleValue ) { range1 = { type : NumericRangeType . ArrayRange , value : [ range1 . value , range1 . value ] } ; } if ( nr2 . type === NumericRangeType . SingleValue ) { nr2 . type = NumericRangeType . ArrayRange ; nr2 . value = [ nr2 . value as number , nr2 . value as number ] ; } return { type : NumericRangeType . MatrixRange , value : [ range1 . value as number [ ] , nr2 . value as number [ ] ] } ; } export class NumericRange implements NumericalRange1 { public static coerce = coerceNumericRange ; public static schema = schemaNumericRange ; public static NumericRangeType = NumericRangeType ; public static readonly empty = new NumericRange ( ) as NumericalRange0 ; public static overlap ( nr1 ? , nr2 ? ) { nr1 = nr1 || NumericRange . empty ; nr2 = nr2 || NumericRange . empty ; if ( NumericRangeType . Empty === nr1 . type || NumericRangeType . Empty === nr2 . type ) { return true ; } if ( NumericRangeType . SingleValue === nr1 . type && NumericRangeType . SingleValue === nr2 . type ) { return nr1 . value === nr2 . value ; } if ( NumericRangeType . ArrayRange === nr1 . type && NumericRangeType . ArrayRange === nr2 . type ) { const l1 = nr1 . value [ 0 ] ; const h1 = nr1 . value [ 1 ] ; const l2 = nr2 . value [ 0 ] ; const h2 = nr2 . value [ 1 ] ; return _overlap ( l1 , h1 , l2 , h2 ) ; } assert ( false , "NumericalRange#overlap : case not implemented yet " ) ; return false ; } public type : NumericRangeType ; public value : NumericalRangeValueType ; constructor ( ) ; constructor ( value ) ; constructor ( value , secondValue ? ) ; constructor ( value ) ; constructor ( value , secondValue ) ; constructor ( value ? , secondValue ? ) { this . type = NumericRangeType . InvalidRange ; this . value = null ; assert ( ! value || ! ( value instanceof NumericRange ) , "use coerce to create a NumericRange" ) ; assert ( ! secondValue || typeof secondValue === "number" || Array . isArray ( secondValue ) ) ; if ( typeof value === "string" ) { const a = construct_from_string ( value as string ) ; this . type = a . type ; this . value = a . value ; } else if ( typeof value === "number" && isFinite ( value ) && ( secondValue === undefined || ( typeof secondValue === "number" && isFinite ( secondValue ) ) ) ) { const a = construct_from_values ( value , secondValue ) ; this . type = a . type ; this . value = a . value ; } else if ( Array . isArray ( value ) ) { const a = _construct_from_array ( value , secondValue ) ; this . type = a . type ; this . value = a . value ; } else { this . value = "<invalid>" ; this . type = NumericRangeType . Empty ; } } public isValid ( ) { if ( this . type === NumericRangeType . ArrayRange ) { const value = this . value as number [ ] ; if ( value [ 0 ] < 0 || value [ 1 ] < 0 ) { return false ; } } if ( this . type === NumericRangeType . SingleValue ) { const value = this . value as number ; if ( value < 0 ) { return false ; } } return this . type !== NumericRangeType . InvalidRange ; } public isEmpty ( ) { return this . type === NumericRangeType . Empty ; } public isDefined ( ) { return this . type !== NumericRangeType . Empty && this . type !== NumericRangeType . InvalidRange ; } public toString ( ) { function array_range_to_string ( values ) { assert ( Array . isArray ( values ) ) ; if ( values . length === 2 && values [ 0 ] === values [ 1 ] ) { return values [ 0 ] . toString ( ) ; } return values . map ( ( value ) => value . toString ( 10 ) ) . join ( ":" ) ; } function matrix_range_to_string ( values ) { return values . map ( ( value ) => { return Array . isArray ( value ) ? array_range_to_string ( value ) : value . toString ( 10 ) ; } ) . join ( "," ) ; } switch ( this . type ) { case NumericRangeType . SingleValue : return ( this . value as any ) . toString ( 10 ) ; case NumericRangeType . ArrayRange : return array_range_to_string ( this . value as number [ ] ) ; case NumericRangeType . Empty : return NUMERIC_RANGE_EMPTY_STRING ; case NumericRangeType . MatrixRange : return matrix_range_to_string ( this . value ) ; default : assert ( this . type === NumericRangeType . InvalidRange ) ; return "NumericRange:<Invalid>" ; } } public toJSON ( ) { return this . toString ( ) ; } public toEncodeableString ( ) { switch ( this . type ) { case NumericRangeType . SingleValue : case NumericRangeType . ArrayRange : case NumericRangeType . MatrixRange : return this . toString ( ) ; case NumericRangeType . InvalidRange : if ( ! ( typeof this . value === "string" ) ) { throw new Error ( "Internal Error" ) ; } return this . value ; default : return null ; } } method param array param dimensions return { * } public extract_values < U , T extends ArrayLike < U > > ( array , dimensions ? ) { const self = this as NumericalRange0 ; if ( ! array ) { return { array , statusCode : this . type === NumericRangeType . Empty ? StatusCodes . Good : StatusCodes . BadIndexRangeNoData } ; } let index ; let low_index ; let high_index ; let rowRange ; let colRange ; switch ( self . type ) { case NumericRangeType . Empty : return extract_empty ( array , dimensions ) ; case NumericRangeType . SingleValue : index = self . value ; return extract_single_value ( array , index ) ; case NumericRangeType . ArrayRange : low_index = self . value [ 0 ] ; high_index = self . value [ 1 ] ; return extract_array_range ( array , low_index , high_index ) ; case NumericRangeType . MatrixRange : rowRange = self . value [ 0 ] ; colRange = self . value [ 1 ] ; return extract_matrix_range ( array , rowRange , colRange , dimensions ) ; default : return { statusCode : StatusCodes . BadIndexRangeInvalid } ; } } public set_values_matrix ( sourceToAlter , newMatrix ) { const { matrix , dimensions } = sourceToAlter ; const self = this as NumericalRange0 ; assert ( dimensions , "expecting valid dimensions here" ) ; if ( self . type !== NumericRangeType . MatrixRange ) { return { matrix , statusCode : StatusCodes . BadTypeMismatch } ; } assert ( dimensions . length === 2 ) ; const nbRows = dimensions [ 0 ] ; const nbCols = dimensions [ 1 ] ; assert ( sourceToAlter . matrix . length === nbRows * nbCols ) ; const [ rowStart , rowEnd ] = self . value [ 0 ] ; const [ colStart , colEnd ] = self . value [ 1 ] ; const nbRowInNew = rowEnd - rowStart + 1 ; const nbColInNew = colEnd - colStart + 1 ; if ( nbRowInNew * nbColInNew !== newMatrix . length ) { return { matrix , statusCode : StatusCodes . BadTypeMismatch } ; } if ( rowEnd >= nbRows || colEnd >= nbCols ) { return { matrix , statusCode : StatusCodes . BadTypeMismatch } ; } for ( let row = rowStart ; row <= rowEnd ; row ++ ) { const ri = row - rowStart ; for ( let col = colStart ; col <= colEnd ; col ++ ) { const ci = col - colStart ; matrix [ row * nbCols + col ] = newMatrix [ ri * nbColInNew + ci ] ; } } return { matrix , statusCode : StatusCodes . Good } ; } public set_values ( arrayToAlter , newValues ) { assert_array_or_buffer ( arrayToAlter ) ; assert_array_or_buffer ( newValues ) ; const self = this as NumericalRange0 ; let low_index ; let high_index ; switch ( self . type ) { case NumericRangeType . Empty : low_index = 0 ; high_index = arrayToAlter . length - 1 ; break ; case NumericRangeType . SingleValue : low_index = self . value ; high_index = self . value ; break ; case NumericRangeType . ArrayRange : low_index = self . value [ 0 ] ; high_index = self . value [ 1 ] ; break ; case NumericRangeType . MatrixRange : return { array : arrayToAlter , statusCode : StatusCodes . BadIndexRangeNoData } ; default : return { array : [ ] , statusCode : StatusCodes . BadIndexRangeInvalid } ; } if ( high_index >= arrayToAlter . length || low_index >= arrayToAlter . length ) { return { array : [ ] , statusCode : StatusCodes . BadIndexRangeNoData } ; } if ( this . type !== NumericRangeType . Empty && newValues . length !== high_index - low_index + 1 ) { return { array : [ ] , statusCode : StatusCodes . BadIndexRangeInvalid } ; } const insertInPlace = Array . isArray ( arrayToAlter ) ? insertInPlaceStandardArray : arrayToAlter instanceof Buffer ? insertInPlaceBuffer : insertInPlaceTypedArray ; return { array : insertInPlace ( arrayToAlter , low_index , high_index , newValues ) , statusCode : StatusCodes . Good } ; } } function slice < U , T extends ArrayLike < U > > ( arr , start , end ) { if ( start === 0 && end === arr . length ) { return arr ; } let res ; if ( ( arr as any ) . buffer instanceof ArrayBuffer ) { res = ( arr as any ) . subarray ( start , end ) ; } else { assert ( typeof ( arr as any ) . slice === "function" ) ; assert ( arr instanceof Buffer || arr instanceof Array || typeof arr === "string" ) ; res = ( arr as any ) . slice ( start , end ) ; } if ( res instanceof Uint8Array && arr instanceof Buffer ) { res = Buffer . from ( res ) ; } return res ; } export interface ExtractResult < T > { array ? : T ; statusCode : StatusCode ; dimensions ? : number [ ] ; } function extract_empty < U , T extends ArrayLike < U > > ( array , dimensions ) { return { array : slice ( array , 0 , array . length ) , dimensions , statusCode : StatusCodes . Good } ; } function extract_single_value < U , T extends ArrayLike < U > > ( array , index ) { if ( index >= array . length ) { if ( typeof array === "string" ) { return { array : "" as any as T , statusCode : StatusCodes . BadIndexRangeNoData } ; } return { array : [ ] as any as T , statusCode : StatusCodes . BadIndexRangeNoData } ; } return { array : slice ( array , index , index + 1 ) , statusCode : StatusCodes . Good } ; } function extract_array_range < U , T extends ArrayLike < U > > ( array , low_index , high_index ) { assert ( isFinite ( low_index ) && isFinite ( high_index ) ) ; assert ( low_index >= 0 ) ; assert ( low_index <= high_index ) ; if ( low_index >= array . length ) { if ( typeof array === "string" ) { return { array : "" as any as T , statusCode : StatusCodes . BadIndexRangeNoData } ; } return { array : [ ] as any as T , statusCode : StatusCodes . BadIndexRangeNoData } ; } high_index = Math . min ( high_index , array . length - 1 ) ; return { array : slice ( array , low_index , high_index + 1 ) , statusCode : StatusCodes . Good } ; } function isArrayLike ( value ) { return typeof value . length === "number" || Object . prototype . hasOwnProperty . call ( value , "length" ) ; } function extract_matrix_range < U , T extends ArrayLike < U > > ( array , rowRange , colRange , dimension ? ) { assert ( Array . isArray ( rowRange ) && Array . isArray ( colRange ) ) ; if ( array . length === 0 ) { return { array : [ ] as any as T , statusCode : StatusCodes . BadIndexRangeNoData } ; } if ( isArrayLike ( ( array as any ) [ 0 ] ) && ! dimension ) { const result = extract_array_range ( array , rowRange [ 0 ] , rowRange [ 1 ] ) ; for ( let i = 0 ; i < result . array ! . length ; i ++ ) { const e = ( result . array ! as any ) [ i ] ; ( result . array as any ) [ i ] = extract_array_range ( e , colRange [ 0 ] , colRange [ 1 ] ) . array ; } return result ; } if ( ! dimension ) { return { array : [ ] as any as T , statusCode : StatusCodes . BadIndexRangeNoData } ; } assert ( dimension , "expecting dimension to know the shape of the matrix represented by the flat array" ) ; const rowLow = rowRange [ 0 ] ; const rowHigh = rowRange [ 1 ] ; const colLow = colRange [ 0 ] ; const colHigh = colRange [ 1 ] ; const nbRow = dimension [ 0 ] ; const nbCol = dimension [ 1 ] ; const nbRowDest = rowHigh - rowLow + 1 ; const nbColDest = colHigh - colLow + 1 ; const tmp = new ( array as any ) . constructor ( nbColDest * nbRowDest ) ; let row ; let col ; let r ; let c ; r = 0 ; for ( row = rowLow ; row <= rowHigh ; row ++ ) { c = 0 ; for ( col = colLow ; col <= colHigh ; col ++ ) { const srcIndex = row * nbCol + col ; const destIndex = r * nbColDest + c ; tmp [ destIndex ] = ( array as any ) [ srcIndex ] ; c ++ ; } r += 1 ; } return { array : tmp , dimensions : [ nbRowDest , nbColDest ] , statusCode : StatusCodes . Good } ; } function assert_array_or_buffer ( array ) { assert ( Array . isArray ( array ) || array . buffer instanceof ArrayBuffer || array instanceof Buffer ) ; } function insertInPlaceStandardArray ( arrayToAlter , low , high , newValues ) { const args = [ low , high - low + 1 ] . concat ( newValues ) ; arrayToAlter . splice ( ... args ) ; return arrayToAlter ; } function insertInPlaceTypedArray ( arrayToAlter , low , high , newValues ) { if ( low === 0 && high === arrayToAlter . length - 1 ) { return new arrayToAlter . constructor ( newValues ) ; } assert ( newValues . length === high - low + 1 ) ; arrayToAlter . subarray ( low , high + 1 ) . set ( newValues ) ; return arrayToAlter ; } function insertInPlaceBuffer ( bufferToAlter , low , high , newValues ) { if ( low === 0 && high === bufferToAlter . length - 1 ) { return Buffer . from ( newValues ) ; } assert ( newValues . length === high - low + 1 ) ; for ( let i = 0 ; i < newValues . length ; i ++ ) { bufferToAlter [ i + low ] = newValues [ i ] ; } return bufferToAlter ; } function _overlap ( l1 , h1 , l2 , h2 ) { return Math . max ( l1 , l2 ) <= Math . min ( h1 , h2 ) ; } export function encodeNumericRange ( numericRange , stream ) { assert ( numericRange instanceof NumericRange ) ; encodeString ( numericRange . toEncodeableString ( ) , stream ) ; } export function decodeNumericRange ( stream , _value ? ) { const str = decodeString ( stream ) ! ; return new NumericRange ( str ) ; } function coerceNumericRange ( value ) { if ( value instanceof NumericRange ) { return value ; } if ( value === null || value === undefined ) { return new NumericRange ( ) ; } if ( value === NUMERIC_RANGE_EMPTY_STRING ) { return new NumericRange ( ) ; } assert ( typeof value === "string" || Array . isArray ( value ) ) ; return new NumericRange ( value ) ; }