commit d688035222641b7d8a72c89639e9e58435893bae
parent 9f158eea326bef6786324074efdcd4689a09a776
Author: kyle <kyle@0x30.net>
Date: Sun, 27 Dec 2015 20:32:30 -0700
sl: start sending back errors in replies
- start passing error strings back from message handlers
- these error strings are meant to be used in real sentences client side
- while here, improve the read/write message functions
- exit 0 in header parsing code when errors occur instead of passing error up
- also start testing the errors in responses
- create new check_status() test library function
- this function verifies message status and unwraps a reply from the server
- no new tests, but additional checks added to existing tests
Diffstat:
20 files changed, 367 insertions(+), 257 deletions(-)
diff --git a/sl b/sl
@@ -38,96 +38,83 @@ die "Could not create socket: $!\n" unless $listen_sock;
my ($laddr, $lport) = ($listen_sock->sockhost(), $listen_sock->sockport());
log_print_bare("accepting connections on $laddr:$lport (pid = '$$')\n");
-while (my $new_sock = $listen_sock->accept()) {
+# every time accept() returns we have a new client trying to connect
+while (my $client_sock = $listen_sock->accept()) {
# create a child process to handle this client
my $pid = fork;
if (!defined $pid) {
die "error: can't fork: $!\n";
} elsif ($pid) {
- # in parent: go back to listening for more connections
- close $new_sock;
+ # in parent: close our copy of $client_sock and listen again
+ close $client_sock;
next;
}
+ # in child: close the listening socket and add ip/port logging prefix
close $listen_sock;
- log_set_peer_host_port($new_sock);
+ log_set_peer_host_port($client_sock);
log_print("new connection (pid = '$$')\n");
- # upgrade connection to SSL
- IO::Socket::SSL->start_SSL($new_sock,
+ # unconditionally upgrade connection to SSL
+ IO::Socket::SSL->start_SSL($client_sock,
SSL_server => 1,
SSL_cert_file => 'ssl/cert_chain.pem',
SSL_key_file => 'ssl/privkey.pem'
- ) or die "failed to ssl handshake: $SSL_ERROR";
- my $ssl_ver = $new_sock->get_sslversion();
- my $ssl_cipher = $new_sock->get_cipher();
+ ) or die "failed ssl handshake: $SSL_ERROR";
+ my $ssl_ver = $client_sock->get_sslversion();
+ my $ssl_cipher = $client_sock->get_cipher();
log_print("ssl started, ver = '$ssl_ver' cipher = '$ssl_cipher'\n");
- # each child opens their own database connection
+ # open a new database connection
my $dbh = DBI->connect(
"dbi:SQLite:dbname=$db_file",
"", "",
{ RaiseError => 1 }
) or die $DBI::errstr;
+ # foreign keys are off by default, autocommit is needed for transactions
$dbh->do("PRAGMA foreign_keys = ON");
$dbh->{AutoCommit} = 1;
- my $stmt_handles = prepare_stmt_handles($dbh);
+ my $sths = prepare_stmt_handles($dbh);
- # main message receiving loop
while (1) {
- my ($msg_type, $msg) = recv_msg($new_sock);
- last unless (defined $msg_type && defined $msg);
+ my ($msg_type, $msg) = recv_msg($client_sock);
$dbh->begin_work;
- my $reply = $msg_func[$msg_type]->($dbh, $stmt_handles, $msg);
+ my $reply = $msg_func[$msg_type]->($dbh, $sths, $msg);
$dbh->commit;
if ($@) {
- warn "Transaction aborted because $@";
# now rollback to undo the incomplete changes
# but do it in an eval{} as it may also fail
eval { $dbh->rollback };
- # XXX: are database errors fatal to this connection?
- next;
+
+ log_print("$msg_str[$msg_type]: discarding reply '$reply'\n");
+ log_print("$msg_str[$msg_type]: db transaction aborted: $@\n");
+ $reply = "err\0database transaction aborted";
}
- # when message handlers have errors, don't send a reply
- next unless defined $reply;
- send_msg($new_sock, $msg_type, $reply);
+ send_msg($client_sock, $msg_type, $reply);
}
-
- $stmt_handles->{$_} = undef for (keys %$stmt_handles);
- $dbh->disconnect();
-
- log_print("disconnected!\n");
- exit 0;
}
-print "got here\n";
+print ">>>>>> got here\n";
-# any header parsing errors or message read errors are fatal in this function
+# any header parsing errors or message read errors are fatal
sub recv_msg {
- my ($sock) = (@_);
+ my ($sock) = @_;
my $header = read_all($sock, 4);
- return undef unless defined $header;
-
my ($msg_type, $msg_size) = unpack("nn", $header);
- unless (defined $msg_type && defined $msg_size) {
- log_print("error: unpacking message type or size\n");
- return undef;
- }
if ($msg_type >= @msg_str) {
- my $bad_msg = sprintf "0x%x", $msg_type;
- log_print("error: unknown message type $bad_msg\n");
- return undef;
+ log_print("error: unknown message type $msg_type\n");
+ exit 0;
}
if ($msg_size > 4096) {
log_print("error: $msg_size byte message too large\n");
- return undef;
+ exit 0;
}
elsif ($msg_size == 0) {
# don't try and do another read, as a read of size 0 is EOF
@@ -135,8 +122,6 @@ sub recv_msg {
}
my $msg = read_all($sock, $msg_size);
- return undef unless defined $msg;
-
return ($msg_type, $msg);
}
@@ -144,41 +129,47 @@ sub read_all {
my ($sock, $bytes_total) = @_;
my $bytes_read = $sock->sysread(my $data, $bytes_total);
+
if (!defined $bytes_read) {
log_print("error: read failed: $!\n");
- return undef;
+ exit 0;
} elsif ($bytes_read == 0) {
- # log_print("error: read EOF\n");
- return undef;
+ log_print("disconnected!\n");
+ exit 0;
} elsif ($bytes_read != $bytes_total) {
log_print("error: read $bytes_read instead of $bytes_total bytes\n");
- return undef;
+ exit 0;
}
return $data;
}
sub send_msg {
- my ($socket, $msg_type, $msg) = (@_);
+ my ($sock, $msg_type, $msg) = @_;
+
+ my $hdr_length = 4;
+ my $msg_length = length($msg);
- my $n = $socket->syswrite(pack("nn", $msg_type, length($msg)));
- $n += $socket->syswrite($msg);
- return $n;
+ send_all($sock, pack("nn", $msg_type, $msg_length), $hdr_length);
+ send_all($sock, $msg, $msg_length);
+
+ return $hdr_length + $msg_length;
}
-sub get_phone_number
-{
- my ($dbh, $sth, $device_id) = @_;
+sub send_all {
+ my ($socket, $bytes, $bytes_total) = @_;
- #print "info: get_phone_number() unimplemented, returning device id!\n";
- #return $device_id;
- my (undef, $ph_num) = $dbh->selectrow_array($sth->{device_id_exists}, undef, $device_id);
- unless (defined $ph_num && looks_like_number($ph_num)) {
- log_print("phone number lookup for $device_id failed!\n");
- return "000";
+ my $bytes_written = $socket->syswrite($bytes);
+
+ if (!defined $bytes_written) {
+ log_print("error: write failed: $!\n");
+ exit 0;
+ } elsif ($bytes_written != $bytes_total) {
+ log_print("error: wrote $bytes_written instead of $bytes_total bytes\n");
+ exit 0;
}
- return $ph_num;
+ return;
}
sub msg_new_device
@@ -189,13 +180,13 @@ sub msg_new_device
# single field
my $ph_num = $msg;
- if (!looks_like_number($ph_num)) {
- log_print("new_device: received phone number '$ph_num' invalid\n");
- return;
+ unless (looks_like_number($ph_num)) {
+ log_print("new_device: phone number '$ph_num' invalid\n");
+ return "err\0the sent phone number is not a number";
}
if ($dbh->selectrow_array($sth{ph_num_exists}, undef, $ph_num)) {
log_print("new_device: phone number '$ph_num' already exists\n");
- return;
+ return "err\0the sent phone number already exists";
}
# make a new device id, the client will supply this on all
@@ -206,7 +197,7 @@ sub msg_new_device
$sth{new_device}->execute($token, $ph_num, time);
log_print("new_device: success '$ph_num' '" .fingerprint($token). "'\n");
- return $token;
+ return "ok\0$token";
}
sub msg_new_list
@@ -217,14 +208,16 @@ sub msg_new_list
# expecting two fields delimited by null
my ($device_id, $list_name) = split("\0", $msg);
- # validate input
- return if (device_id_invalid($dbh, $sth_ref, $device_id));
+ if (my $err = device_id_invalid($dbh, $sth_ref, $device_id)) {
+ return "err\0$err";
+ }
+
unless ($list_name) {
- log_print("new_list: name field missing\n");
- return;
+ log_print("new_list: error, no name\n");
+ return "err\0no list name was given";
}
- my $devid_fp = fingerprint($device_id);
+ my $devid_fp = fingerprint($device_id);
log_print("new_list: '$list_name'\n");
log_print("new_list: adding first member devid = '$devid_fp'\n");
@@ -238,15 +231,19 @@ sub msg_new_list
# XXX: also send back the date and all that stuff
my $phone_number = get_phone_number($dbh, $sth_ref, $device_id);
- my $out = $list_id . "\0" . $list_name . "\0" . $phone_number;
+ my $response = "$list_id\0$list_name\0$phone_number";
- return $out;
+ return "ok\0$response";
}
sub msg_new_list_item
{
- my ($dbh, $sth_ref, $new_sock, $msg) = @_;
- return undef;
+ my ($dbh, $sth_ref, $msg) = @_;
+
+ if (my $err = device_id_invalid($dbh, $sth_ref, $msg)) {
+ return "err\0$err";
+ }
+ return "err\0unimplemented";
# my ($list_id, $position, $text) = split ("\0", $msg);
@@ -268,7 +265,9 @@ sub msg_join_list
my %sth = %$sth_ref;
my ($device_id, $list_id) = split("\0", $msg);
- return if (device_id_invalid($dbh, $sth_ref, $device_id));
+ if (my $err = device_id_invalid($dbh, $sth_ref, $device_id)) {
+ return "err\0$err";
+ }
log_print("join_list: device '$device_id'\n");
log_print("join_list: list '$list_id'\n");
@@ -283,7 +282,7 @@ sub msg_join_list
log_print("join_list: tried to create a duplicate list member entry for device $device_id and list $list_id\n");
}
- return $list_id;
+ return "ok\0$list_id";
}
sub msg_leave_list
@@ -293,8 +292,10 @@ sub msg_leave_list
my ($device_id, $list_id) = split("\0", $msg);
- return if (device_id_invalid($dbh, $sth_ref, $device_id));
-
+ if (my $err = device_id_invalid($dbh, $sth_ref, $device_id)) {
+ return "err\0$err";
+ }
+
log_print("leave_list: device '$device_id'\n");
log_print("leave_list: list '$list_id'\n");
@@ -320,7 +321,7 @@ sub msg_leave_list
}
my $out = "$list_id\0$alive";
- return $out;
+ return "ok\0$out";
}
# update friend map
@@ -331,14 +332,16 @@ sub msg_add_friend
# device id followed by 1 friends number
my ($device_id, $friend) = split("\0", $msg);
+ if (my $err = device_id_invalid($dbh, $sth_ref, $device_id)) {
+ return "err\0$err";
+ }
- return if (device_id_invalid($dbh, $sth_ref, $device_id));
my $devid_fp = fingerprint($device_id);
log_print("add_friend: '$devid_fp' adding '$friend'\n");
unless (looks_like_number($friend)) {
log_print("add_friend: bad friends number '$friend'\n");
- return;
+ return "err\0friends phone number is not a valid phone number";
}
# XXX: check they're not already a friend before doing this
@@ -361,12 +364,13 @@ sub msg_add_friend
}
}
- return $friend;
+ return "ok\0$friend";
}
sub msg_delete_friend
{
- my ($dbh, $sth_ref, $new_sock, $msg) = @_;
+ my ($dbh, $sth_ref, $msg) = @_;
+ return "err\0unimplemented";
# delete all friends, remove mutual friend references
# $friends_map_delete_sth->execute($device_id);
@@ -379,7 +383,9 @@ sub msg_list_request
my ($dbh, $sth_ref, $msg) = @_;
my %sth = %$sth_ref;
- return if (device_id_invalid($dbh, $sth_ref, $msg));
+ if (my $err = device_id_invalid($dbh, $sth_ref, $msg)) {
+ return "err\0$err";
+ }
my $devid_fp = fingerprint($msg);
log_print("list_request: gathering lists for '$devid_fp'\n");
@@ -430,7 +436,7 @@ sub msg_list_request
}
$out .= join("\0", @indirect_lists);
- return $out;
+ return "ok\0$out";
# XXX: add time of last request to list (rate throttling)?
}
@@ -441,17 +447,18 @@ sub msg_list_items
my %sth = %$sth_ref;
my ($device_id, $list_id) = split("\0", $msg);
-
- return if (device_id_invalid($dbh, $sth_ref, $device_id));
+ if (my $err = device_id_invalid($dbh, $sth_ref, $device_id)) {
+ return "err\0$err";
+ }
if (!$list_id) {
log_print("list_items: received null list id");
- return;
+ return "err\0the sent list id was empty";
}
unless ($dbh->selectrow_array($sth{check_list_member}, undef, $list_id, $device_id)) {
# XXX: table list_members list_id's should always exist in table lists
log_print("list_items: $device_id not a member of $list_id\n");
- return;
+ return "err\0the sent device id is not a member of the list";
}
log_print("list_items: $device_id request items for $list_id\n");
@@ -466,19 +473,18 @@ sub msg_list_items
}
my $out = join("\0", @items);
- return $out;
+ return "ok\0$out";
}
sub msg_ok
{
my ($dbh, $sth_ref, $msg) = @_;
-
- return if (device_id_invalid($dbh, $sth_ref, $msg));
+ if (my $err = device_id_invalid($dbh, $sth_ref, $msg)) {
+ return "err\0$err";
+ }
log_print("ok: device '" . fingerprint($msg) . "' checking in\n");
-
- # send empty payload back
- return "";
+ return "ok\0";
}
sub fingerprint
@@ -486,28 +492,43 @@ sub fingerprint
return substr shift, 0, 8;
}
+sub get_phone_number
+{
+ my ($dbh, $sth, $device_id) = @_;
+
+ #print "info: get_phone_number() unimplemented, returning device id!\n";
+ #return $device_id;
+ my (undef, $ph_num) = $dbh->selectrow_array($sth->{device_id_exists}, undef, $device_id);
+ unless (defined $ph_num && looks_like_number($ph_num)) {
+ log_print("phone number lookup for $device_id failed!\n");
+ return "000";
+ }
+
+ return $ph_num;
+}
+
sub device_id_invalid
{
my ($dbh, $sth_ref, $device_id) = @_;
unless ($device_id) {
- log_print("device id '' invalid\n");
- return 1;
+ log_print("device_id: got empty device id\n");
+ return "the client sent an empty device id";
}
# validate this at least looks like base64
unless ($device_id =~ m/^[a-zA-Z0-9+\/=]+$/) {
- log_print("device id '$device_id' not valid base64\n");
- return 1;
+ log_print("device_id: '$device_id' not base64\n");
+ return "the client sent a device id that wasn't base64";
}
# make sure we know about this device id
unless ($dbh->selectrow_array($sth_ref->{device_id_exists}, undef, $device_id)) {
- log_print("unknown device '$device_id'\n");
- return 1;
+ log_print("device_id: unknown device '$device_id'\n");
+ return "the client sent an unknown device id";
}
- return 0;
+ return;
}
sub create_tables {
diff --git a/tests/add_friend/test.pl b/tests/add_friend/test.pl
@@ -8,13 +8,15 @@ use test;
# - adds a new friend
my $sock = new_socket();
+
send_msg($sock, 'new_device', "4038675309");
-my (undef, $device_id, undef) = recv_msg($sock);
+my ($msg_data) = recv_msg($sock, 'new_device');
+my $device_id = check_status($msg_data, 'ok');
my $friend_phnum = "4033217654";
-my $send_t = 'add_friend';
-send_msg($sock, $send_t, "$device_id\0$friend_phnum");
-my ($recv_t, $resp_data, $length) = recv_msg($sock);
-fail "got response type '$recv_t', expected '$send_t'" if ($recv_t ne $send_t);
-fail "got response ph num '$resp_data' expected '$friend_phnum'" if ($resp_data ne $friend_phnum);
+send_msg($sock, 'add_friend', "$device_id\0$friend_phnum");
+($msg_data) = recv_msg($sock, 'add_friend');
+
+my $msg = check_status($msg_data, 'ok');
+fail "got response ph num '$msg' expected '$friend_phnum'" if ($msg ne $friend_phnum);
diff --git a/tests/add_friend_bad_num/test.pl b/tests/add_friend_bad_num/test.pl
@@ -5,11 +5,19 @@ use test;
# this test:
# - gets a new device id
-# - tries adding a new friend with a bad phone number
+# - tries adding a new friend with a non numeric phone number
my $sock = new_socket();
+
send_msg($sock, 'new_device', "4038675309");
-my (undef, $device_id, undef) = recv_msg($sock);
+my ($msg_data) = recv_msg($sock, 'new_device');
+my $device_id = check_status($msg_data, 'ok');
my $friend_phnum = "4033217654bad";
+
send_msg($sock, 'add_friend', "$device_id\0$friend_phnum");
+($msg_data) = recv_msg($sock, 'add_friend');
+
+my $msg = check_status($msg_data, 'err');
+my $msg_good = "friends phone number is not a valid phone number";
+fail "unexpected error message '$msg', was expecting '$msg_good'" if ($msg ne $msg_good);
diff --git a/tests/bad_deviceid/server.log.good b/tests/bad_deviceid/server.log.good
@@ -1,18 +1,20 @@
accepting connections on <ip>:<port> (pid = <digits>)
new connection (pid = <digits>)
ssl started, ver = 'TLSv1_2' cipher = 'ECDHE-RSA-AES128-SHA256'
-unknown device <base64>
-unknown device <base64>
-unknown device <base64>
-unknown device <base64>
-unknown device <base64>
-unknown device <base64>
-unknown device <base64>
-device id '&^%_invalid_base64' not valid base64
-device id '&^%_invalid_base64' not valid base64
-device id '&^%_invalid_base64' not valid base64
-device id '&^%_invalid_base64' not valid base64
-device id '&^%_invalid_base64' not valid base64
-device id '&^%_invalid_base64' not valid base64
-device id '&^%_invalid_base64' not valid base64
+device_id: unknown device <base64>
+device_id: '&^%_invalid_base64' not base64
+device_id: unknown device <base64>
+device_id: '&^%_invalid_base64' not base64
+device_id: unknown device <base64>
+device_id: '&^%_invalid_base64' not base64
+device_id: unknown device <base64>
+device_id: '&^%_invalid_base64' not base64
+device_id: unknown device <base64>
+device_id: '&^%_invalid_base64' not base64
+device_id: unknown device <base64>
+device_id: '&^%_invalid_base64' not base64
+device_id: unknown device <base64>
+device_id: '&^%_invalid_base64' not base64
+device_id: unknown device <base64>
+device_id: '&^%_invalid_base64' not base64
disconnected!
diff --git a/tests/bad_deviceid/test.pl b/tests/bad_deviceid/test.pl
@@ -3,19 +3,25 @@ use strict;
use warnings;
use test;
-# this test:
-# - tries to send every message type with an invalid id
-# - except for new_device message type
-
my $sock = new_socket();
-for my $msg (sort @msg_str) {
- # new device doesn't take device id as a first parameter
- next if ($msg eq "new_device");
- send_msg($sock, $msg, "notvaliddeviceid");
-}
-for my $msg (sort @msg_str) {
+for my $msg_type (sort @msg_str) {
# new device doesn't take device id as a first parameter
- next if ($msg eq "new_device");
- send_msg($sock, $msg, "&^%_invalid_base64");
+ next if ($msg_type eq "new_device");
+
+ # send a valid base64 but not yet registered device id
+ send_msg($sock, $msg_type, "notvaliddeviceid");
+ my ($msg_data) = recv_msg($sock, $msg_type);
+
+ my $msg = check_status($msg_data, 'err');
+ my $msg_good = 'the client sent an unknown device id';
+ fail "got unexpected error message '$msg', expected '$msg_good'" if ($msg ne $msg_good);
+
+ # send an invalid base64 id
+ send_msg($sock, $msg_type, "&^%_invalid_base64");
+ ($msg_data) = recv_msg($sock, $msg_type);
+
+ $msg = check_status($msg_data, 'err');
+ $msg_good = 'the client sent a device id that wasn\'t base64';
+ fail "got unexpected error message '$msg', expected '$msg_good'" if ($msg ne $msg_good);
}
diff --git a/tests/bad_msg/server.log.good b/tests/bad_msg/server.log.good
@@ -1,5 +1,4 @@
accepting connections on <ip>:<port> (pid = <digits>)
new connection (pid = <digits>)
ssl started, ver = 'TLSv1_2' cipher = 'ECDHE-RSA-AES128-SHA256'
-error: unknown message type 0xbadd
-disconnected!
+error: unknown message type 47837
diff --git a/tests/double_register/test.pl b/tests/double_register/test.pl
@@ -4,12 +4,16 @@ use warnings;
use test;
my $sock = new_socket();
-send_msg($sock, 'new_device', "4038675309");
-my ($type, $response, $length) = recv_msg($sock);
-# verify response length is 32 random bytes encoded with base64
-if ($length != 43) {
- fail "expected response length of 43, got $length";
-}
+my $phnum = '4038675309';
+send_msg($sock, 'new_device', $phnum);
+my ($msg_data) = recv_msg($sock, 'new_device');
-send_msg($sock, 'new_device', "4038675309");
+check_status($msg_data, 'ok');
+
+send_msg($sock, 'new_device', $phnum);
+($msg_data) = recv_msg($sock, 'new_device');
+
+my $msg = check_status($msg_data, 'err');
+my $msg_good = 'the sent phone number already exists';
+fail "unexpected error message '$msg', was expecting '$msg_good'" if ($msg ne $msg_good);
diff --git a/tests/leave_list_self/test.pl b/tests/leave_list_self/test.pl
@@ -10,25 +10,30 @@ use test;
# - verifies list is gone
my $sock = new_socket();
-my $send_t = 'new_device';
-send_msg($sock, $send_t, "4038675309");
-my (undef, $device_id, undef) = recv_msg($sock);
+send_msg($sock, 'new_device', "4038675309");
+my ($msg_data) = recv_msg($sock, 'new_device');
+
+my $device_id = check_status($msg_data, 'ok');
my $list_name = "this is a new list";
+
send_msg($sock, 'new_list', "$device_id\0$list_name");
-my (undef, $list_data, undef) = recv_msg($sock);
-my ($list_id) = split("\0", $list_data);
+($msg_data) = recv_msg($sock, 'new_list');
+
+my $msg = check_status($msg_data, 'ok');
+my ($list_id) = split("\0", $msg);
send_msg($sock, 'leave_list', "$device_id\0$list_id");
-my ($recv_t, $leave_data, $length) = recv_msg($sock);
+($msg_data) = recv_msg($sock, 'leave_list');
-my ($leave_id) = split("\0", $leave_data);
-fail "message type mismatch, '$recv_t' != 'leave_list'" if ($recv_t ne 'leave_list');
+$msg = check_status($msg_data, 'ok');
+my ($leave_id) = split("\0", $msg);
fail "got leave data '$leave_id', expected $list_id" if ($leave_id ne $list_id);
# verify we don't get this list back when requesting all lists
send_msg($sock, 'list_request', $device_id);
-my (undef, $request_data, $length2) = recv_msg($sock);
+($msg_data) = recv_msg($sock, 'list_request');
+my $request_data = check_status($msg_data, 'ok');
my ($direct, $other) = split("\0\0", $request_data);
fail "expected empty, got other" if ($direct ne "" || $other ne "");
diff --git a/tests/list_request_basic/test.pl b/tests/list_request_basic/test.pl
@@ -11,25 +11,27 @@ use test;
my $phone_num = "4038675309";
my $sock = new_socket();
+
send_msg($sock, 'new_device', $phone_num);
-my (undef, $device_id, undef) = recv_msg($sock);
+my ($msg_data) = recv_msg($sock, 'new_device');
+
+my $device_id = check_status($msg_data, 'ok');
my %list_id_map;
for my $name ("new list 1", "new list 2", "new list 3") {
send_msg($sock, 'new_list', "$device_id\0$name");
- my (undef, $data, undef) = recv_msg($sock);
+ my ($msg_data) = recv_msg($sock, 'new_list');
+
+ my $data = check_status($msg_data, 'ok');
my ($id, $name, $member) = split("\0", $data);
# save this for verification later
$list_id_map{$name} = $id;
}
send_msg($sock, 'list_request', $device_id);
-my ($type, $list_data, $length) = recv_msg($sock);
-
-if ($type ne 'list_request') {
- fail "got response type '$type', expected 'list_request'";
-}
+($msg_data) = recv_msg($sock, 'list_request');
+my $list_data = check_status($msg_data, 'ok');
my ($direct, $indirect) = split("\0\0", $list_data);
fail "got indirect lists, expected none" if (length($indirect) != 0);
diff --git a/tests/msg_ok/test.pl b/tests/msg_ok/test.pl
@@ -6,11 +6,12 @@ use test;
my $sock = new_socket();
send_msg($sock, 'new_device', "4038675309");
-my (undef, $device_id) = recv_msg($sock);
+my ($msg_data, $length) = recv_msg($sock, 'new_device');
+
+my $device_id = check_status($msg_data, 'ok');
send_msg($sock, 'ok', $device_id);
-my ($type, $response, $length) = recv_msg($sock);
+($msg_data, $length) = recv_msg($sock, 'ok');
-fail "expected msg type 'ok', got '$type'" if ($type ne 'ok');
-fail "expected response to be undefined, it wasn't" if (defined $response);
-fail "expected response size 0, got $length" if ($length != 0);
+check_status($msg_data, 'ok');
+fail "expected response size 3, got $length" if ($length != 3);
diff --git a/tests/mutual_friends_basic/test.pl b/tests/mutual_friends_basic/test.pl
@@ -9,30 +9,37 @@ use test;
# - device 1 creates a new list
# - then verify that device 2 can see it
-my $sock_1 = new_socket();
-my $phnum_1 ="4038675309";
+my ($sock_1, $sock_2) = (new_socket(), new_socket());
+my ($phnum_1, $phnum_2) = ("4038675309", "4037082094");
+
send_msg($sock_1, 'new_device', $phnum_1);
-my (undef, $device_id1, undef) = recv_msg($sock_1);
+my ($msg_1) = recv_msg($sock_1, 'new_device');
-my $sock_2 = new_socket();
-my $phnum_2 = "4037082094";
send_msg($sock_2, 'new_device', $phnum_2);
-my (undef, $device_id2, undef) = recv_msg($sock_2);
+my ($msg_2) = recv_msg($sock_2, 'new_device');
+
+my $device_id1 = check_status($msg_1, 'ok');
+my $device_id2 = check_status($msg_2, 'ok');
# the mutual friend relationship, computer style
send_msg($sock_1, 'add_friend', "$device_id1\0$phnum_2");
-recv_msg($sock_1);
+recv_msg($sock_1, 'add_friend');
send_msg($sock_2, 'add_friend', "$device_id2\0$phnum_1");
-recv_msg($sock_2);
+recv_msg($sock_2, 'add_friend');
my $list_name = "this is a new list";
+
send_msg($sock_1, 'new_list', "$device_id1\0$list_name");
-my (undef, $list_data, undef) = recv_msg($sock_1);
+my ($msg_data) = recv_msg($sock_1, 'new_list');
+
+my $list_data = check_status($msg_data, 'ok');
my ($list_id) = split("\0", $list_data);
# make sure socket 2 can see socket 1's list
send_msg($sock_2, 'list_request', $device_id2);
-my (undef, $request_data, $request_len) = recv_msg($sock_2);
+($msg_data) = recv_msg($sock_2, 'list_request');
+
+my $request_data = check_status($msg_data, 'ok');
my (undef, $other) = split("\0\0", $request_data);
my $num_lists = 0;
diff --git a/tests/new_device/server.log.good b/tests/new_device/server.log.good
@@ -2,4 +2,5 @@ accepting connections on <ip>:<port> (pid = <digits>)
new connection (pid = <digits>)
ssl started, ver = 'TLSv1_2' cipher = 'ECDHE-RSA-AES128-SHA256'
new_device: success <digits> <base64>
+new_device: phone number '403867530&' invalid
disconnected!
diff --git a/tests/new_device/test.pl b/tests/new_device/test.pl
@@ -3,12 +3,20 @@ use strict;
use warnings;
use test;
+# send a valid number and verify response is ok
my $sock = new_socket();
+
send_msg($sock, 'new_device', "4038675309");
-my ($type, $response, $length) = recv_msg($sock);
-close $sock;
+my ($msg_data, $length) = recv_msg($sock, 'new_device');
+
+my $msg = check_status($msg_data, 'ok');
+fail "expected response length of 46, got $length" if ($length != 46);
+fail "response '$msg' not base64" unless ($msg =~ m/^[a-zA-Z0-9+\/=]+$/);
+
+# send a bad phone number and verify error response
+send_msg($sock, 'new_device', "403867530&");
+($msg_data) = recv_msg($sock, 'new_device');
-# verify response length is 32 random bytes encoded with base64
-if ($length != 43) {
- fail "expected response length of 43, got $length";
-}
+$msg = check_status($msg_data, 'err');
+my $fail_msg = 'the sent phone number is not a number';
+fail "expected failure message '$fail_msg' but got '$msg'" if ($msg ne $fail_msg);
diff --git a/tests/new_list/test.pl b/tests/new_list/test.pl
@@ -10,21 +10,18 @@ use test;
# - verifies received information is congruent with what was sent
my $sock = new_socket();
-my $send_t = 'new_device';
my $phnum = "4038675309";
-send_msg($sock, $send_t, $phnum);
-my ($recv_t, $device_id, $length) = recv_msg($sock);
-
-fail "got response type '$recv_t', expected '$send_t'" if ($recv_t ne $send_t);
-fail "expected response length of 43, got $length" if ($length != 43);
+send_msg($sock, 'new_device', $phnum);
+my ($payload, $length) = recv_msg($sock, 'new_device');
+my $device_id = check_status($payload, 'ok');
+fail "expected response length of 46, got $length" if ($length != 46);
my $list_name = "this is a new list";
-$send_t = 'new_list';
-send_msg($sock, $send_t, "$device_id\0$list_name");
-my ($recv_t2, $list_data, $length2) = recv_msg($sock);
-fail "got response type '$recv_t2', expected '$send_t'" if ($recv_t2 ne $send_t);
+send_msg($sock, 'new_list', "$device_id\0$list_name");
+($payload, $length) = recv_msg($sock, 'new_list');
+my $list_data = check_status($payload, 'ok');
my ($id, $name, @members) = split("\0", $list_data);
my $id_length = length($id);
diff --git a/tests/new_list_missing_name/server.log.good b/tests/new_list_missing_name/server.log.good
@@ -2,5 +2,5 @@ accepting connections on <ip>:<port> (pid = <digits>)
new connection (pid = <digits>)
ssl started, ver = 'TLSv1_2' cipher = 'ECDHE-RSA-AES128-SHA256'
new_device: success <digits> <base64>
-new_list: name field missing
+new_list: error, no name
disconnected!
diff --git a/tests/new_list_missing_name/test.pl b/tests/new_list_missing_name/test.pl
@@ -8,10 +8,16 @@ use test;
# - tries to create a new list without a name
my $sock = new_socket();
+
send_msg($sock, 'new_device', "4038675309");
-my ($type, $device_id, $length) = recv_msg($sock);
+my ($msg_data, $length) = recv_msg($sock, 'new_device');
-fail "got response type '$type', expected 'new_device'" if ($type ne 'new_device');
-fail "expected response length of 43, got $length" if ($length != 43);
+my $device_id = check_status($msg_data, 'ok');
+fail "expected response length of 46, got $length" if ($length != 46);
send_msg($sock, 'new_list', "$device_id\0");
+($msg_data, $length) = recv_msg($sock, 'new_list');
+
+my $msg = check_status($msg_data, 'err');
+my $msg_good = 'no list name was given';
+fail "unexpected error response '$msg', expecting '$msg_good'" if ($msg ne $msg_good);
diff --git a/tests/payload_invalid/server.log.good b/tests/payload_invalid/server.log.good
@@ -2,15 +2,15 @@ accepting connections on <ip>:<port> (pid = <digits>)
new connection (pid = <digits>)
ssl started, ver = 'TLSv1_2' cipher = 'ECDHE-RSA-AES128-SHA256'
error: 7000 byte message too large
-disconnected!
new connection (pid = <digits>)
-ssl started, ver = 'TLSv1_2' cipher = 'ECDHE-RSA-AES128-SHA256'
-new_device: received phone number <digits> invalid
-device id <digits> invalid
-device id <digits> invalid
-device id <digits> invalid
-device id <digits> invalid
-device id <digits> invalid
-device id <digits> invalid
-device id <digits> invalid
+ssl ok, ver = 'TLSv1_2' cipher = 'ECDHE-RSA-AES128-SHA256'
+new_device: phone number <digits> invalid
+device_id: got empty device id
+device_id: got empty device id
+device_id: got empty device id
+device_id: got empty device id
+device_id: got empty device id
+device_id: got empty device id
+device_id: got empty device id
+device_id: got empty device id
disconnected!
diff --git a/tests/payload_invalid/test.pl b/tests/payload_invalid/test.pl
@@ -3,10 +3,16 @@ use strict;
use warnings;
use test;
-# send way too long message
+# send message that's too long
send_msg(new_socket(), 'new_device', "longstr" x 1000);
# send message size 0 to all message types
# reuse a socket because we shouldn't get disconnected for this
my $sock = new_socket();
-send_msg($sock, $_, "") for (@msg_str);
+for (@msg_str) {
+ send_msg($sock, $_, "");
+ my ($msg_data) = recv_msg($sock, $_);
+
+ my $msg = check_status($msg_data, 'err');
+ # print "$msg\n";
+}
diff --git a/tests/test.pm b/tests/test.pm
@@ -10,7 +10,7 @@ use Time::HiRes qw(usleep);
require "msgs.pl";
our (%msg_num, @msg_str);
-our @EXPORT = qw(new_socket fail send_msg recv_msg %msg_num @msg_str SHUT_RDWR);
+our @EXPORT = qw(new_socket fail send_msg recv_msg %msg_num @msg_str check_status);
sub fail {
my (undef, $file, $line) = caller;
@@ -54,55 +54,94 @@ sub new_socket
return $sock;
}
-sub send_msg
-{
- my ($sock, $type_str, $msg) = @_;
+sub send_msg {
+ my ($sock, $msg_type, $msg) = @_;
- if (! exists $msg_num{$type_str}) {
- fail "$0: send_msg: invalid msg type '$type_str'";
+ if (! exists $msg_num{$msg_type}) {
+ fail "send_msg: invalid message type '$msg_type'";
}
- # send away
- my ($n, $msg_len) = (0, length($msg));
- $n += $sock->syswrite(pack("nn", $msg_num{$type_str}, $msg_len));
- $n += $sock->syswrite($msg);
+ my $hdr_length = 4;
+ my $msg_length = length($msg);
+
+ send_all($sock, pack("nn", $msg_num{$msg_type}, $msg_length), $hdr_length);
+ send_all($sock, $msg, $msg_length);
+
+ return $hdr_length + $msg_length;
+}
+
+sub send_all {
+ my ($socket, $bytes, $bytes_total) = @_;
- if ($n != ($msg_len + 4)) {
- fail "$0: send_msg: tried to send $msg_len bytes, but sent $n\n";
+ my $bytes_written = $socket->syswrite($bytes);
+
+ if (!defined $bytes_written) {
+ fail "send_all: write failed: $!";
+ } elsif ($bytes_written != $bytes_total) {
+ fail "send_all: wrote $bytes_written instead of $bytes_total bytes";
}
+
+ return;
}
-sub recv_msg
-{
- my ($sock) = @_;
+sub recv_msg {
+ my ($sock, $expected_type) = @_;
+
+ my $header = read_all($sock, 4);
+ my ($msg_type, $msg_size) = unpack("nn", $header);
+
+ if ($msg_type >= @msg_str) {
+ fail "recv_msg: unknown message type $msg_type";
+ }
+ if ($msg_str[$msg_type] ne $expected_type) {
+ fail "recv_msg: response type mismatch '$msg_str[$msg_type]'" .
+ " != '$expected_type'";
+ }
- # wait for response
- my ($metadata, $type, $size);
- my $bread = $sock->sysread($metadata, 4);
- unless (defined $bread) {
- fail "read(): $!\n";
+ if ($msg_size > 4096) {
+ fail "recv_msg: $msg_size byte message too large";
}
- if ($bread != 4) {
- fail "read() returned $bread instead of 4!";
+ elsif ($msg_size == 0) {
+ # don't try and do another read, as a read of size 0 is EOF
+ return ("", 0);
}
- unless (($type, $size) = unpack("nn", $metadata)) {
- fail "error unpacking metadata";
+
+ my $msg = read_all($sock, $msg_size);
+ return ($msg, $msg_size);
+}
+
+sub read_all {
+ my ($sock, $bytes_total) = @_;
+
+ my $bytes_read = $sock->sysread(my $data, $bytes_total);
+
+ if (!defined $bytes_read) {
+ fail "recv_msg: read failed: $!";
+ } elsif ($bytes_read == 0) {
+ fail "recv_msg: read EOF on socket";
+ } elsif ($bytes_read != $bytes_total) {
+ fail "recv_msg: read $bytes_read instead of $bytes_total bytes";
}
- if ($type >= @msg_str) {
- fail "$0: recv_msg: invalid msg num '$type'";
+ return $data;
+}
+
+sub check_status {
+ my ($msg, $expected_status) = @_;
+
+ my $first_null = index($msg, "\0");
+ if ($first_null == -1) {
+ fail "check_status: no null byte found in response";
}
- fail "bad message size not $size < 1024" if ($size > 1023);
- return ($msg_str[$type], undef, 0) if ($size == 0);
+ my $msg_status = substr($msg, 0, $first_null);
+ my $msg_rest = substr($msg, $first_null + 1);
- my $data;
- if ((my $bread = $sock->sysread($data, $size)) != $size) {
- fail "read() returned $bread instead of $size!";
+ if ($msg_status ne $expected_status) {
+ fail "unexpected receive status '$msg_status': '$msg_rest'";
}
- # caller should validate this is the expected type
- return ($msg_str[$type], $data, $size);
+ return $msg_rest;
}
1;
diff --git a/tests/two_lists_same_name/test.pl b/tests/two_lists_same_name/test.pl
@@ -4,29 +4,25 @@ use warnings;
use test;
# this test:
-# - gets a new device id
-# - creates a new list
+# - creates new device id
+# - creates new list
+# - creates another new list with identical name
my $sock = new_socket();
-send_msg($sock, 'new_device', "4038675309");
-my ($type, $device_id, $length) = recv_msg($sock);
-fail "got response type '$type', expected 'new_device" if ($type ne 'new_device');
-fail "expected response length of 43, got $length" if ($length != 43);
+send_msg($sock, 'new_device', "4038675309");
+my ($msg_data, $length) = recv_msg($sock, 'new_device');
+my $device_id = check_status($msg_data, 'ok');
my $list_name = "this is a new list";
-send_msg($sock, 'new_list', "$device_id\0$list_name");
-my ($type2, $list_data, $length2) = recv_msg($sock);
-fail "got response type '$type', expected 'new_list'" if ($type2 ne 'new_list');
-
-my ($id, $name, @members) = split("\0", $list_data);
-my $id_length = length($id);
+send_msg($sock, 'new_list', "$device_id\0$list_name");
+($msg_data) = recv_msg($sock, 'new_list');
-fail "bad id length $id_length != 43" if ($id_length != 43);
-fail "recv'd name '$name' not equal to '$list_name'" if ($name ne $list_name);
-fail "list does not have exactly 1 member" if (@members != 1);
+check_status($msg_data, 'ok');
# add the same list again
send_msg($sock, 'new_list', "$device_id\0$list_name");
-($type2, $list_data, $length2) = recv_msg($sock);
+($msg_data, $length) = recv_msg($sock, 'new_list');
+
+my $msg = check_status($msg_data, 'ok');