import Compression from './compression' ; import crc32 from './crc32' ; import { decode , encode } from './punycode' ; interface IDataViewOptions { enableCompression : boolean ; } export const EMPTY_UINT8_ARRAY = new Uint8Array ( 0 ) ; export const EMPTY_UINT32_ARRAY = new Uint32Array ( 0 ) ; const LITTLE_ENDIAN = new Int8Array ( new Int16Array ( [ 1 ] ) . buffer ) [ 0 ] === 1 ; let getCompressionSingleton = ( ) => { const COMPRESSION = new Compression ( ) ; getCompressionSingleton = ( ) => COMPRESSION ; return COMPRESSION ; } ; function align4 ( pos ) { return ( pos + 3 ) & ~ 0x03 ; } /**
 * Return size of of a serialized byte value.
 */ export function sizeOfByte ( ) { return 1 ; } /**
 * Return size of of a serialized boolean value.
 */ export function sizeOfBool ( ) { return 1 ; } /**
 * Return number of bytes needed to serialize `length`.
 */ export function sizeOfLength ( length ) { return length <= 127 ? 1 : 5 ; } /**
 * Return number of bytes needed to serialize `array` Uint8Array typed array.
 *
 * WARNING: this only returns the correct size if `align` is `false`.
 */ export function sizeOfBytes ( array , align ) { return sizeOfBytesWithLength ( array . length , align ) ; } /**
 * Return number of bytes needed to serialize `array` Uint8Array typed array.
 *
 * WARNING: this only returns the correct size if `align` is `false`.
 */ export function sizeOfBytesWithLength ( length , align ) { return ( align ? 3 : 0 ) + length + sizeOfLength ( length ) ; } /**
 * Return number of bytes needed to serialize `str` ASCII string.
 */ export function sizeOfASCII ( str ) { return str . length + sizeOfLength ( str . length ) ; } /**
 * Return number of bytes needed to serialize `str` UTF8 string.
 */ export function sizeOfUTF8 ( str ) { const encodedLength = encode ( str ) . length ; return encodedLength + sizeOfLength ( encodedLength ) ; } /**
 * Return number of bytes needed to serialize `array`.
 */ export function sizeOfUint32Array ( array ) { return array . byteLength + sizeOfLength ( array . length ) ; } export function sizeOfNetworkRedirect ( str , compression ) { return compression === true ? sizeOfBytesWithLength ( getCompressionSingleton ( ) . networkRedirect . getCompressedSize ( str ) , false ) : sizeOfASCII ( str ) ; } export function sizeOfNetworkHostname ( str , compression ) { return compression === true ? sizeOfBytesWithLength ( getCompressionSingleton ( ) . networkHostname . getCompressedSize ( str ) , false ) : sizeOfASCII ( str ) ; } export function sizeOfNetworkCSP ( str , compression ) { return compression === true ? sizeOfBytesWithLength ( getCompressionSingleton ( ) . networkCSP . getCompressedSize ( str ) , false ) : sizeOfASCII ( str ) ; } export function sizeOfNetworkFilter ( str , compression ) { return compression === true ? sizeOfBytesWithLength ( getCompressionSingleton ( ) . networkFilter . getCompressedSize ( str ) , false ) : sizeOfASCII ( str ) ; } export function sizeOfCosmeticSelector ( str , compression ) { return compression === true ? sizeOfBytesWithLength ( getCompressionSingleton ( ) . cosmeticSelector . getCompressedSize ( str ) , false ) : sizeOfASCII ( str ) ; } export function sizeOfRawNetwork ( str , compression ) { return compression === true ? sizeOfBytesWithLength ( getCompressionSingleton ( ) . networkRaw . getCompressedSize ( encode ( str ) ) , false ) : sizeOfUTF8 ( str ) ; } export function sizeOfRawCosmetic ( str , compression ) { return compression === true ? sizeOfBytesWithLength ( getCompressionSingleton ( ) . cosmeticRaw . getCompressedSize ( encode ( str ) ) , false ) : sizeOfUTF8 ( str ) ; } /**
 * This abstraction allows to serialize efficiently low-level values of types:
 * string, uint8, uint16, uint32, etc. while hiding the complexity of managing
 * the current offset and growing. It should always be instantiated with a
 * big-enough length because this will not allow for resizing. To allow
 * deciding the required total size, function estimating the size needed to
 * store different primitive values are exposes as static methods.
 *
 * This class is also more efficient than the built-in `DataView`.
 *
 * The way this is used in practice is that you write pairs of function to
 * serialize and deserialize a given structure/class (with code being pretty
 * symetrical). In the serializer you `pushX` values, and in the deserializer
 * you use `getX` functions to get back the values.
 */ export class StaticDataView { /**
     * Create an empty (i.e.: size = 0) StaticDataView.
     */ public static empty ( options ) { return StaticDataView . fromUint8Array ( EMPTY_UINT8_ARRAY , options ) ; } /**
     * Instantiate a StaticDataView instance from `array` of type Uint8Array.
     */ public static fromUint8Array ( array , options ) { return new StaticDataView ( array , options ) ; } /**
     * Instantiate a StaticDataView with given `capacity` number of bytes.
     */ public static allocate ( capacity , options ) { return new StaticDataView ( new Uint8Array ( capacity ) , options ) ; } public pos : number ; public buffer : Uint8Array ; public compression : Compression | undefined ; constructor ( buffer , { enableCompression } ) { if ( LITTLE_ENDIAN === false ) { throw new Error ( 'Adblocker currently does not support Big-endian systems' ) ; } if ( enableCompression === true ) { this . enableCompression ( ) ; } this . buffer = buffer ; this . pos = 0 ; } public enableCompression ( ) { this . compression = getCompressionSingleton ( ) ; } public checksum ( ) { return crc32 ( this . buffer , 0 , this . pos ) ; } public dataAvailable ( ) { return this . pos < this . buffer . byteLength ; } public setPos ( pos ) { this . pos = pos ; } public getPos ( ) { return this . pos ; } public seekZero ( ) { this . pos = 0 ; } public slice ( ) { this . checkSize ( ) ; return this . buffer . slice ( 0 , this . pos ) ; } public subarray ( ) { if ( this . pos === this . buffer . byteLength ) { return this . buffer ; } this . checkSize ( ) ; return this . buffer . subarray ( 0 , this . pos ) ; } /**
     * Make sure that `this.pos` is aligned on a multiple of 4.
     */ public align4 ( ) { this . pos = align4 ( this . pos ) ; } public set ( buffer ) { this . buffer = new Uint8Array ( buffer ) ; this . seekZero ( ) ; } public pushBool ( bool ) { this . pushByte ( Number ( bool ) ) ; } public getBool ( ) { return Boolean ( this . getByte ( ) ) ; } public setByte ( pos , byte ) { this . buffer [ pos ] = byte ; } public pushByte ( octet ) { this . pushUint8 ( octet ) ; } public getByte ( ) { return this . getUint8 ( ) ; } public pushBytes ( bytes , align = false ) { this . pushLength ( bytes . length ) ; if ( align === true ) { this . align4 ( ) ; } this . buffer . set ( bytes , this . pos ) ; this . pos += bytes . byteLength ; } public getBytes ( align = false ) { const numberOfBytes = this . getLength ( ) ; if ( align === true ) { this . align4 ( ) ; } const bytes = this . buffer . subarray ( this . pos , this . pos + numberOfBytes ) ; this . pos += numberOfBytes ; return bytes ; } /**
     * Allows row access to the internal buffer through a Uint32Array acting like
     * a view. This is used for super fast writing/reading of large chunks of
     * Uint32 numbers in the byte array.
     */ public getUint32ArrayView ( desiredSize ) { this . align4 ( ) ; if ( desiredSize === 0 ) { return EMPTY_UINT32_ARRAY ; } const view = new Uint32Array ( this . buffer . buffer , this . pos + this . buffer . byteOffset , desiredSize ) ; this . pos += desiredSize * 4 ; return view ; } public pushUint8 ( uint8 ) { this . buffer [ this . pos ++ ] = uint8 ; } public getUint8 ( ) { return this . buffer [ this . pos ++ ] ; } public pushUint16 ( uint16 ) { this . buffer [ this . pos ++ ] = uint16 >>> 8 ; this . buffer [ this . pos ++ ] = uint16 ; } public getUint16 ( ) { return ( ( this . buffer [ this . pos ++ ] << 8 ) | this . buffer [ this . pos ++ ] ) >>> 0 ; } public pushUint32 ( uint32 ) { this . buffer [ this . pos ++ ] = uint32 >>> 24 ; this . buffer [ this . pos ++ ] = uint32 >>> 16 ; this . buffer [ this . pos ++ ] = uint32 >>> 8 ; this . buffer [ this . pos ++ ] = uint32 ; } public getUint32 ( ) { return ( ( ( ( this . buffer [ this . pos ++ ] << 24 ) >>> 0 ) + ( ( this . buffer [ this . pos ++ ] << 16 ) | ( this . buffer [ this . pos ++ ] << 8 ) | this . buffer [ this . pos ++ ] ) ) >>> 0 ) ; } public pushUint32Array ( arr ) { this . pushLength ( arr . length ) ; for ( const n of arr ) { this . pushUint32 ( n ) ; } } public getUint32Array ( ) { const length = this . getLength ( ) ; const arr = new Uint32Array ( length ) ; for ( let i = 0 ; i < length ; i += 1 ) { arr [ i ] = this . getUint32 ( ) ; } return arr ; } public pushUTF8 ( raw ) { const str = encode ( raw ) ; this . pushLength ( str . length ) ; for ( let i = 0 ; i < str . length ; i += 1 ) { this . buffer [ this . pos ++ ] = str . charCodeAt ( i ) ; } } public getUTF8 ( ) { const byteLength = this . getLength ( ) ; this . pos += byteLength ; return decode ( String . fromCharCode . apply ( null , this . buffer . subarray ( this . pos - byteLength , this . pos ) ) ) ; } public pushASCII ( str ) { this . pushLength ( str . length ) ; for ( let i = 0 ; i < str . length ; i += 1 ) { this . buffer [ this . pos ++ ] = str . charCodeAt ( i ) ; } } public getASCII ( ) { const byteLength = this . getLength ( ) ; this . pos += byteLength ; return String . fromCharCode . apply ( null , this . buffer . subarray ( this . pos - byteLength , this . pos ) ) ; } public pushNetworkRedirect ( str ) { if ( this . compression !== undefined ) { this . pushBytes ( this . compression . networkRedirect . compress ( str ) ) ; } else { this . pushASCII ( str ) ; } } public getNetworkRedirect ( ) { if ( this . compression !== undefined ) { return this . compression . networkRedirect . decompress ( this . getBytes ( ) ) ; } return this . getASCII ( ) ; } public pushNetworkHostname ( str ) { if ( this . compression !== undefined ) { this . pushBytes ( this . compression . networkHostname . compress ( str ) ) ; } else { this . pushASCII ( str ) ; } } public getNetworkHostname ( ) { if ( this . compression !== undefined ) { return this . compression . networkHostname . decompress ( this . getBytes ( ) ) ; } return this . getASCII ( ) ; } public pushNetworkCSP ( str ) { if ( this . compression !== undefined ) { this . pushBytes ( this . compression . networkCSP . compress ( str ) ) ; } else { this . pushASCII ( str ) ; } } public getNetworkCSP ( ) { if ( this . compression !== undefined ) { return this . compression . networkCSP . decompress ( this . getBytes ( ) ) ; } return this . getASCII ( ) ; } public pushNetworkFilter ( str ) { if ( this . compression !== undefined ) { this . pushBytes ( this . compression . networkFilter . compress ( str ) ) ; } else { this . pushASCII ( str ) ; } } public getNetworkFilter ( ) { if ( this . compression !== undefined ) { return this . compression . networkFilter . decompress ( this . getBytes ( ) ) ; } return this . getASCII ( ) ; } public pushCosmeticSelector ( str ) { if ( this . compression !== undefined ) { this . pushBytes ( this . compression . cosmeticSelector . compress ( str ) ) ; } else { this . pushASCII ( str ) ; } } public getCosmeticSelector ( ) { if ( this . compression !== undefined ) { return this . compression . cosmeticSelector . decompress ( this . getBytes ( ) ) ; } return this . getASCII ( ) ; } public pushRawCosmetic ( str ) { if ( this . compression !== undefined ) { this . pushBytes ( this . compression . cosmeticRaw . compress ( encode ( str ) ) ) ; } else { this . pushUTF8 ( str ) ; } } public getRawCosmetic ( ) { if ( this . compression !== undefined ) { return decode ( this . compression . cosmeticRaw . decompress ( this . getBytes ( ) ) ) ; } return this . getUTF8 ( ) ; } public pushRawNetwork ( str ) { if ( this . compression !== undefined ) { this . pushBytes ( this . compression . networkRaw . compress ( encode ( str ) ) ) ; } else { this . pushUTF8 ( str ) ; } } public getRawNetwork ( ) { if ( this . compression !== undefined ) { return decode ( this . compression . networkRaw . decompress ( this . getBytes ( ) ) ) ; } return this . getUTF8 ( ) ; } private checkSize ( ) { if ( this . pos !== 0 && this . pos > this . buffer . byteLength ) { throw new Error ( `StaticDataView too small: ${ this . buffer . byteLength }, but required ${ this . pos } bytes` ) ; } } private pushLength ( length ) { if ( length <= 127 ) { this . pushUint8 ( length ) ; } else { this . pushUint8 ( 128 ) ; this . pushUint32 ( length ) ; } } private getLength ( ) { const lengthShort = this . getUint8 ( ) ; return lengthShort === 128 ? this . getUint32 ( ) : lengthShort ; } }