#!/usr/bin/perl

# Andrew Daviel, April 2005
# convert NetStumbler NS1 binary data to Fig
# e.g. ns12fig  -v blah.ns1  710000 928000  49.2499 -123.2322 50000 > blah.fig

# Usage: $0 <file.ns1> <xscale> [<yscale>] [<latoff>] [<longoff>] [<radius factor>]
# radius factor makes points bigger; min. size is 1

# options: n - add text legend
#          e - show WEP encrypted/unencrypted
#          d - show all APs at same depth
#          v - verbose (cf. summary data)

use Math::BigInt;

# remember depths used - for merging files
dbmopen(%depth,"ns1depthdb",0644) ;

$verb = 0 ;
$verb2 = 0 ; # show time

# http://www.stumbler.net/ns1files.html
# C uint8 S uint16 L uint32 l int32 Q uint64 d double

# Flags  (HEx)
# 0001 ESS extended service set
# 0002 IBSS  ad hod/P2P
# 0004 CF Pollable
# 0008 CF-Poll request
# 0010 WEP
# 0020 Short Preamble
# 0040 PBCC
# 0080 Channel Agility
# 0400 Short slot time
# 2000 DSSS-OFDM

$quiet = 0 ;
                                                                                
$line_style = 0 ;
$thickness = 1 ;
$pen_color = 4 ; # red
$red = 4 ; $gold = 31 ; $green = 14 ; $black = 0 ;
$fill_color = 7 ;
$pdepth = 40 ;
                                                                                
$pen_style = -1 ;
$area_fill = 20 ;
$style_val = '0.000' ;
$angle = '0.000' ;
$textangle = '0.700' ;
$pt = 2 ; # text point
$pt = 18 ; # text point
$join_style = 0 ;
$cap_style = 0 ;
$radius0 = 400000 ;
$radius0 = 2000 ;
$forward_arrow = 0 ;
$backward_arrow = 0 ;
$maxcol = 5 ;
$legend2 = 0 ;


while ($ARGV[0] =~ /-([nedv])/) {
  if ($1 eq 'n') { shift ; $legend = 1 ; }
  if ($1 eq 'e') { shift ; $doe = 1 ; }
  if ($1 eq 'd') { shift ; $dp1 = 1 ; }
  if ($1 eq 'v') { shift ; $verb = 1 ; }
}
$file = shift ;
open (IN,$file) or die "Usage: $0 <file.ns1> <xscale> [<yscale>] [<latoff>] [<longoff>] [<radius factor>]\n" ;

$xscale = shift ;
unless ($xscale) {
  die "Usage: $0 <xscale> [<yscale>] [<xoff>] [<yoff>] [<radius factor>]\n" ;
# e.g. 180000
}
$yscale = shift ; unless ($yscale) { $yscale = $xscale ; }
$yoff = shift ;
$xoff = shift  ;
$radius0 = shift ;  unless ($radius0) { $radius0 = 4000 ; }
                                                                                
$radius = int ($xscale / $radius0) ;
unless ($radius>0) { $radius = 1 ; }
                                                                                
$scale2 = 1200/100 ;
$scale2 = 1200 ;
                                                                                
print<<EOT;
#FIG 3.2
Landscape
Flush left
Inches
C
100.00
Single
-2
# generated by ns12fig  with scale $xscale,$yscale offset $yoff,$xoff
1200 2
EOT


read (IN,$in,4) ;

unless ($in eq 'NetS') {
  die  "$file has no NetStumbler magic\n" ;
}
read (IN,$in,4) ;
$fv = unpack("L",$in) ;

#print "File version $fv\n" ;

read (IN,$in,4) ;
$nap = unpack("L",$in) ;

print STDERR "$nap APINFO entries\n" ;
for ($ap=1;$ap<=$nap;$ap++) {
#  print "\nAP $ap\n" ;
#  print "\n" ;
  read (IN,$in,1) ;
  $ssidlen = unpack("C",$in) ;
  read (IN,$in,$ssidlen) ;

  $ssid = $in ;
  #print "SSID $ssid\t" ;

  read (IN,$in,6) ; @bssid = unpack("C6",$in) ;

  $bssid = sprintf("%2.2x:%2.2x:%2.2x:%2.2x:%2.2x:%2.2x", $bssid[0],$bssid[1],$bssid[2],$bssid[3],$bssid[4],$bssid[5]) ;
  $ssid{$bssid} = $ssid ;
  read (IN,$in,4) ; $maxsig = unpack("l",$in) ; $maxsig{$bssid} = $maxsig ;
  read (IN,$in,4) ; $minns  = unpack("l",$in) ; $minns{$bssid} = $minns ;
  read (IN,$in,4) ; $maxsnr = unpack("l",$in) ; $maxsnr{$bssid} =  $maxsnr;

  #print "maxsig $maxsig min noise $minns max SNR $maxsnr\n" ;
  #print "\t$maxsig $minns $maxsnr" ;

  read (IN,$in,4) ; $flags = unpack("L",$in) ;
  $wep = $flags & 0x10 ;
  $fast = $flags & 0x2400  ;
  $hflags = sprintf("%4.4x", $flags) ;
  #printf ("\tflags %4.4x", $flags) ;
  read (IN,$in,4) ; $bint = unpack("L",$in) ;
  #print "\tb.int $bint" ;

  read (IN,$in,8) ; 
  $first = filetotime($in) ;
  $ff = localtime($first) ;
  #print "\tfirst $ff\t" ;
  
  read (IN,$in,8) ; 
  $last = filetotime($in) ; $dt = $last - $first ;
  $ff = localtime($last) ;
  #print "\tfor $dt sec\n" ;

  # http://blogs.msdn.com/oldnewthing/archive/2003/09/05/54806.aspx
  # http://maillist.perforce.com/pipermail/jamming/1997-August/000294.html

  read (IN,$in,8) ; $lat =  unpack("d",$in) ;
  read (IN,$in,8) ; $long =  unpack("d",$in) ;

  #printf("\tLL %.6f %.6f\n",$lat,$long) ;

  unless (!$lat and !$long or $lat == 0.0 and $long == 0.0 or $verb) {
    $x = $xscale * ($long - $xoff) / 180 ; $y = $yscale * ($lat - $yoff) / 180 ; # from worldmap.c
    if ($xmax eq '') { $xmax = $xmin = $x ; }
    if ($ymax eq '') { $ymax = $ymin = $y ; }
    if ($x>$xmax) { $xmax = $x ; }
    if ($y>$ymax) { $ymax = $y ; }
    if ($x<$xmin) { $xmin = $x ; }
    if ($y<$ymin) { $ymin = $y ; }
    $x = int($x * $scale2 + 0.5 ) ; $y = int($y * -$scale2 + 0.5 ) ; # from ps2fig
    $lat5 = sprintf("%.5f",$lat) ;
    $long5 = sprintf("%.5f",$long) ;
    print<<EOT;
# Time $ff
# Pos $lat5 $long5
# SSID $ssid $bssid
# flags $hflags
# sig $maxsig noise $minns SNR $maxsnr
EOT
  }


  read (IN,$in,4) ; $ndata = unpack("L",$in) ;

  #print "$ndata data entries\n" ;

  for ($i=0;$i<$ndata;$i++) {
    read (IN,$in,8) ;
    if ($verb and $verb2) { $time = filetotime($in) ; $ltime = localtime($time) ; }
    read (IN,$in,4) ; $sig  = unpack("l",$in) ;
    read (IN,$in,4) ; $noise  = unpack("l",$in) ;
    read (IN,$in,4) ; $fix  = unpack("l",$in) ;
    #if ($verb) { print "\t$ltime\t$sig\t$noise\t" ; }
    if ($fix) {
      read (IN,$in,8) ; $lat =  unpack("d",$in) ;
      read (IN,$in,8) ; $long =  unpack("d",$in) ;
      read (IN,$in,8) ; $alt =  unpack("d",$in) ;
      read (IN,$in,4) ; $nsat = unpack("L",$in) ;
      read (IN,$in,8) ; $speed =  unpack("d",$in) ;
      read (IN,$in,8) ; $track  =  unpack("d",$in) ;
      read (IN,$in,8) ; $mvar =  unpack("d",$in) ;
      read (IN,$in,8) ; $hdop =  unpack("d",$in) ;
      #print "  LL $lat $long alt $alt Nsat $nsat speed $speed track $track Mvar $mvar hdop $hdop\n" ;
	$x = $xscale * ($long - $xoff) / 180 ; $y = $yscale * ($lat - $yoff) / 180 ; # from worldmap.c
	if ($xmax eq '') { $xmax = $xmin = $x ; }
	if ($ymax eq '') { $ymax = $ymin = $y ; }
	if ($x>$xmax) { $xmax = $x ; }
	if ($y>$ymax) { $ymax = $y ; }
	if ($x<$xmin) { $xmin = $x ; }
	if ($y<$ymin) { $ymin = $y ; }
	$x = int($x * $scale2 + 0.5 ) ; $y = int($y * -$scale2 + 0.5 ) ; # from ps2fig
        $x{$bssid} = $x ; $y{$bssid} = $y ;
      if ($verb) { 
        #printf ("LL %.5f %.5f alt %.3f Nsat %i speed %.1f track %.1f mvar %.1f HDOP %i\n",$lat,$long, $alt, $nsat, $speed, $track, $mvar, $hdop) ;

	unless (!$lat and !$long or $lat == 0.0 and $long == 0.0) {
          $lat5 = sprintf("%.5f",$lat) ;
          $long5 = sprintf("%.5f",$long) ;
	  print<<EOT;
# Time $ff
# Pos $lat $long
# SSID $ssid $bssid
# flags $hflags
# sig $sig noise $noise
EOT
	  unless ($depth{$bssid}) {
            $depth{'MAX'}++ ;
	    $depth{$bssid} = $depth{'MAX'} ;
	  }
	  unless ($dp1) { $pdepth = $depth{$bssid} ; }
	  unless ($seen{$bssid}) {
	    $seen{$bssid} = 1 ;
	    if ($legend2) { print "4 0 $black $pdepth 0 0 $pt $textangle  0  210 750 $x $y $ssid\\001\n" ; }
	  }
          $snr = $sig - $noise ;
	  $pen_color = $red ;
          if ($sig == -32767) {  $pen_color = $black ; }
	  if ($snr > 5) { $pen_color = $gold ; }
	  if ($snr > 10) { $pen_color = $green ; }
	  $fill_color = $pen_color ;
	  if ($doe) {
	    $pen_color = $gold ; if ($wep) { $pen_color = $red ; }
	  }
	  $fill_color = $pen_color ;
	  if (!$wep and !$doe or $doe and !$fast) {
	# circle
	    $x2 = $x + $radius ;
	    print "1 3 $line_style $thickness $pen_color" ;
	    print " $fill_color $pdepth $pen_style $area_fill $angle 1 0.000 " ;
	    print "$x $y $radius $radius $x $y $x2 $y \n" ;
	  } else {
	# square
	    $x1 = $x - $radius ; $x2 = $x + $radius ;
	    $y1 = $y - $radius ; $y2 = $y + $radius ;
	    #print "2 2 0 1 0 0 $depth 0 20 0.000 0 0 -1 0 0 5\n" ;
	    print "2 2 $line_style $thickness $pen_color $fill_color $pdepth $pen_style $area_fill $angle 0 0 -1 0 0 5\n" ;
	    print "\t$x1 $y1 $x2 $y1 $x2 $y2 $x1 $y2 $x1 $y1\n" ;
	  }
																       
	}
      }
    } else {
      #if ($verb) { print "\n" ; }
    }
  }

  read (IN,$in,1) ; $len = unpack("C",$in) ;
  read (IN,$in,$len) ; $name = $in ; $name{$bssid} = $name ;
  if ($fv >= 8) {
    read (IN,$in,8) ; @chan = unpack("L2",$in) ;
    read (IN,$in,4) ; $lchan = unpack("L",$in) ; $lchan{$bssid} = $lchan ;
    read (IN,$in,4) ; $ip = unpack("L",$in) ; @ip = unpack("C4",$in) ;
    #printf ("channelbits %4.4x%4.4x",$chan[1], $chan[0]) ;
    #printf ("\tip %i.%i.%i.%i lastchan %i\n",$ip[0],$ip[1],$ip[2],$ip[3],$lchan) ;
  }
  if ($fv >= 11 ) {
    read (IN,$in,4) ; $minsig = unpack("l",$in) ; $minsig{$bssid} = $minsig ;
    read (IN,$in,4) ; $maxns = unpack("l",$in) ; $maxns{$bssid} = $maxns ;
    read (IN,$in,4) ; $maxrate = unpack("L",$in)/10  ; $maxrate{$bssid} = $maxrate ;
    read (IN,$in,4) ; $subnet  = unpack("L",$in) ; @subnet = unpack("C4",$in) ;
    read (IN,$in,4) ; $mask  = unpack("L",$in) ; @mask = unpack("C4",$in) ;
    #printf ("min sig %i max noise %i max rate %i subnet %i.%i.%i.%i mask %i.%i.%i.%i\n",$minsig,$maxns,$maxrate,$subnet[0],$subnet[1],$subnet[2],$subnet[3], $mask[0],$mask[1],$mask[2],$mask[3]) ;
  }
  if ($fv >= 12) {
    read (IN,$in,4) ; $miscflags  = unpack("L",$in) ;
    #if ($miscflags) { printf ("misc flags %4.4x\n", $miscflags) ; }
    read (IN,$in,4) ; $ielength  = unpack("L",$in) ;
    read (IN,$in,$ielength) ; @ie = unpack("C*",$in) ;
    #if ($ielength) { print "ie length $ielength\n" ; }
  }

  unless (!$lat and !$long or $lat == 0.0 and $long == 0.0 or $verb) {
    print<<EOT;
# rate $maxrate
# channel $lchan
EOT
    unless ($depth{$bssid}) {
      $depth{'MAX'}++ ;
      $depth{$bssid} = $depth{'MAX'} ;
    }
    unless ($dp1) { $pdepth = $depth{$bssid} ; }
    unless ($seen{$bssid}) {
      $seen{$bssid} = 1 ;
      if ($legend2) { print "4 0 $black $pdepth 0 0 $pt $textangle  0  210 750 $x $y $ssid\\001\n" ; }
    }
												       
    $pen_color = $red ;
    if ($snr > 5) { $pen_color = $gold ; }
    if ($snr > 10) { $pen_color = $green ; }
    $fill_color = $pen_color ;
    if ($doe) {
      $pen_color = $gold ; if ($wep) { $pen_color = $red ; }
    }
    $fill_color = $pen_color ;
    if (!$wep and !$doe or $doe and !$fast) {
  # circle
      $x2 = $x + $radius ;
      print "1 3 $line_style $thickness $pen_color" ;
      print " $fill_color $pdepth $pen_style $area_fill $angle 1 0.000 " ;
      print "$x $y $radius $radius $x $y $x2 $y \n" ;
    } else {
  # square
      $x1 = $x - $radius ; $x2 = $x + $radius ;
      $y1 = $y - $radius ; $y2 = $y + $radius ;
      #print "2 2 0 1 0 0 $depth 0 20 0.000 0 0 -1 0 0 5\n" ;
      print "2 2 $line_style $thickness $pen_color $fill_color $pdepth $pen_style $area_fill $angle 0 0 -1 0 0 5\n" ;
      print "\t$x1 $y1 $x2 $y1 $x2 $y2 $x1 $y2 $x1 $y1\n" ;
    }

  }


}
unless ($dp1) { 
  print STDERR "Using ns1depthdb file for depth\n" ;
  foreach $bssid ( sort { $depth{$a} <=> $depth{$b} } keys %ssid) {
    print STDERR "Depth $depth{$bssid}\t$ssid{$bssid} $bssid $name{$bssid}\tCH $lchan{$bssid} maxsig $maxsig{$bssid} rate $maxrate{$bssid}\n" ;
    if ($legend) {
      print "4 0 $black $depth{$bssid} 0 0 $pt $textangle  0  210 750 $x{$bssid} $y{$bssid} $ssid{$bssid} $bssid $name{$bssid} CH $lchan{$bssid}\\001\n" ; 
    }
  }
}


exit ;

sub filetotime {
  my ($h,$l,$t,$r) ;
  ($l,$h) = unpack("L2",$_[0]) ;

  my $t = Math::BigInt->new($h);
  $t->blsft(32) ;
  $t->badd($l) ;

  $t->bdiv(10000000) ; # convert 100ns ticks to seconds
  $t->bsub("11644473600") ; # 134774 days = 369 years 1601 - 1970
  return $t ;
}