/**
 * Options for generating a fingerprint graphic
 */ export interface Options { /** target height (number of cells) */ height : number ; /** target width (number of cells) */ width : number ; /** symbols to be mapped against calculated values */ symbols : string [ ] ; } /**
 * Represents a "fingerprint" visualization of a certain [[height]] and [[width]],
 * the [[values]] property contains the raw values generated using the [drunken
 * bishop](http://www.dirk-loss.de/sshvis/drunken_bishop.pdf) while [[symbols]]
 * represents the same values by mapping the on the symbols defined in [[Options.symbols]].
 */ export interface Fingerprint { width : number ; height : number ; values : number [ ] [ ] ; symbols : string [ ] [ ] ; } type Move = "00" | "01" | "10" | "11" ; type Position = [ number , number ] ; /** Default options */ export const defaults = { height : 9 , width : 17 , symbols : [ " " , "." , "o" , "+" , "=" , "*" , "B" , "O" , "X" , "@" , "%" , "&" , "#" , "/" , "^" , "S" , "E" ] } ; function getMoves ( bytes ) { const moves = [ ] ; for ( const b of bytes ) { let str = b . toString ( 2 ) ; while ( str . length < 8 ) { str = "0" + str ; } moves . push ( str . slice ( 6 , 8 ) , str . slice ( 4 , 6 ) , str . slice ( 2 , 4 ) , str . slice ( 0 , 2 ) ) ; } return moves as Move [ ] ; } function getSteps ( moves , width , height ) { let pos = [ ( width - 1 ) / 2 , ( height - 1 ) / 2 ] ; const steps = [ pos ] ; for ( const move of moves ) { let [ x , y ] = pos ; switch ( move ) { case "00" : x -- , y -- ; break ; case "01" : x ++ , y -- ; break ; case "10" : x -- , y ++ ; break ; case "11" : x ++ , y ++ ; break ; } pos = [ Math . max ( 0 , Math . min ( width - 1 , x ) ) , Math . max ( 0 , Math . min ( height - 1 , y ) ) ] ; steps . push ( pos ) ; } return steps ; } export function getValues ( fingerprint , opts = { } ) { const { width , height } = Object . assign ( { } , defaults , opts ) ; if ( height % 2 !== 1 || width % 2 !== 1 ) { throw "The height and width options must be odd numbers" ; } if ( fingerprint . length % 2 !== 0 ) { throw "The fingerprint length must be an even number" ; } const moves = getMoves ( fingerprint ) ; const steps = getSteps ( moves , width , height ) ; const grid = [ ] ; for ( let i = 0 ; i < height ; i ++ ) { grid . push ( new Array ( width ) . fill ( 0 ) ) ; } for ( const [ x , y ] of steps ) { grid [ y ] [ x ] ++ ; } const [ x0 , y0 ] = steps [ 0 ] ; const [ x1 , y1 ] = steps [ steps . length - 1 ] ; grid [ y0 ] [ x0 ] = 15 ; grid [ y1 ] [ x1 ] = 16 ; return grid ; } /**
 * Generates a visualisation of a "fingerprint" similar to the ascii-based
 * visualization method used by OpenSSH. Generates a "heat map" of values on a
 * grid of a given width and height using the "drunken bishop" algorithm
 * desribed in [this paper](http://www.dirk-loss.de/sshvis/drunken_bishop.pdf).
 */ export function randomArt ( fingerprint , opts = { } ) { const options = Object . assign ( { } , defaults , opts ) ; const vals = getValues ( fingerprint , options ) ; const symbols = opts . symbols || defaults . symbols ; return { width : options . width , height : options . height , values : vals , symbols : vals . map ( line => line . map ( val => symbols [ val ] ) ) } ; }