#!/usr/bin/perl
################################################################
#
# A very simple (and stupid) dialer for the HSO driver
#
################################################################

use strict;
use File::Temp;

$SIG{INT} = $SIG{TERM} = 'IGNORE';

use constant WWAN_PATH => '/sys/devices/platform/sony-laptop/wwanpower';
use constant WWAN_ON   => 1;
use constant WWAN_OFF  => 0;

my $command = ( shift || 'connect' );

my $config = {
                 pin => '2204',
                 apn => 'gprs.swisscom.ch',
                 dev => 'hso0',
             };

$config->{_tty} = undef;
$config->{_log} = File::Temp->new(Template=>".hso-output-XXXXXXX", DIR=>'/tmp')->filename;

# are we root?

$< && xfail("$0 must be run with uid 0 (root)");


_cfset_app_port($config); # Guess 'application' tty
_set_wwan(WWAN_ON);       # Turn WWAN power on
_reset_modem($config);    # send ATZ command
_unlock_sim($config);     # (try to) unlock the SIM

exit if $command eq 'unlock';

_try_apn($config);        # send APN command
_connect($config);        # connect
_setip($config);          # ..and guess the local-ip

xl("==> CONNECTED! Hit CTRL+D to hangup <===\n");
while(<STDIN>) {}
_disconnect($config);     # Disconnect from modem
_set_wwan(WWAN_OFF);      # ...and turn the power OFF again
xl("Goodbye.\n");
exit(0);



########################################################################
# Terminates a connection
sub _disconnect {
	my($cf) = @_;
	
	xl("Terminating connection          ");
	
	my @chat = ();
	my $dev  = $cf->{dev} or xfail("No device specified?!");
	
	# turn hso0 down
	system("ifconfig $dev down") and xfail("ifconfig $dev down : failed!");
	
	push(@chat, qq/ABORT   ERROR/,
	            qq/TIMEOUT 100/,
	            qq/""      ATZ/,
	            qq/OK      AT_OWANCALL=1,0,0^m/,
	            qq/OK      ""/);
	_chat($cf,\@chat) and xfail("disconnect failed");
	xok();
}

########################################################################
# 'dial'
sub _connect {
	my($cf) = @_;
	xl("Connecting                      ");
	my @chat = ();
	push(@chat, qq/ABORT   ERROR/,
	            qq/TIMEOUT 30/,
	            qq/""      ATZ/,
	            qq/OK      AT_OWANCALL=1,1,0^m/,
	            qq/OK      \\d\\d\\d\\d\\dAT_OWANDATA=1^m/,
	            qq/OK      ""/,
	);
	my $fail = 1;
	for(1..5) {
		xl("..");
		_chat($cf,\@chat) and next;
		$fail = 0; last;
	}
	( $fail ? xfail("connect failed") : xok() );
}

########################################################################
# Guess remote IP from connect output
sub _setip {
	my($cf) = @_;
	
	xl("Configuring IP                  ");
	my $dev    = $cf->{dev} or xfail("No device specified?!");
	my $ourip = `grep '^_OWANDATA' $cf->{_log} | cut -d, -f2`;
	chomp($ourip);
	unless($ourip) { xfail("No IP received?! PANIC!"); }
	
	xl("IP=$ourip, Dev=$dev\n");
	system("ifconfig $dev $ourip netmask 255.255.255.255 up") and xfail("ifconfig failed");
	
	xl("Configuring default gateway     ");
	system("route add default dev $dev")                      and xfail("route add failed");
	xok();
}


########################################################################
# 
sub _try_apn {
	my($cf) = @_;
	xl("Configuring APN                 ");
	my $apn  = $cf->{apn} or xfail("No APN in configuration?");
	my @chat = ();
	push(@chat, qq/ABORT   ERROR/,
	            qq/TIMEOUT 100/,
	            qq/""      AT+COPS=0^m/,
	            qq/OK      AT+CGDCONT=1,\\"IP\\",\\"$apn\\"^m/,
	            qq/OK      ""/,
	            );
	_chat($cf,\@chat) and xfail("APN failed");
	xok();
}


########################################################################
# Try to unlock the SIM
sub _unlock_sim {
	my($cf) = @_;
	my @chat = ();
	xl("Unlocking SIM                   ");
	my $pin  = $cf->{pin} or xfail("No pin?");
	push(@chat, qq/TIMEOUT 5/,
	            qq/""  "AT+CPIN=\\"$pin\\""/,
	            qq/"" ""/,);
	_chat($cf,\@chat) and xfail("Init failed");
	xok();
}

########################################################################
# send ATZ
sub _reset_modem {
	my($cf) = @_;
	my @chat = ();
	xl("Resetting modem                 ");
	push(@chat, qq/ABORT   ERROR/,
	            qq/TIMEOUT 20/,
	            qq/""      ATZ/,
	            qq/OK      ""/,);
	_chat($cf,\@chat) and xfail("Reset failed!");
	xok();
}

########################################################################
# Kick chat binary
sub _chat {
	my($cf,$script) = @_;
	my $str  = join("\n",@$script)."\n";
	my $fh   = File::Temp->new(Template=>".hso-script-XXXXXXX", DIR=>'/tmp');
	my $fnam = $fh->filename;
	
	print $fh $str;
	$fh->close;
	
	return system("chat -E -s -V -f $fnam < $cf->{_tty} > $cf->{_tty} 2> $cf->{_log}");
}


########################################################################
# Change WWANs power state
sub _set_wwan {
	my($val) = @_;
	xl("Setting wwan power to '$val'       ");
	system("echo $val > ".WWAN_PATH) and xfail();
	xok();
}

sub _cfset_app_port {
	my($cf) = @_;
	my $tty = `grep '^Application\$' /sys/class/tty/ttyHS*/hsotype | awk -F/ '{print \$5}'`;
	chomp($tty);
	if(length($tty)) {
		$cf->{_tty} = "/dev/$tty";
		xl("Guessed HSO-TTY is at           $cf->{_tty}\n");
	}
	else {
		xfail("Failed to guess a valid TTY");
	}
}

sub xfail {
	my($msg) = @_;
	$msg ||= '';
	print "failed! ($msg)\n";
	exit(1);
}

sub xok {
	print "done\n";
	return 0;
}

sub xl {
	my($logmsg) = @_;
	print $logmsg;
}

