shlist

share and manage lists between multiple people
Log | Files | Refs

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:
Msl | 225+++++++++++++++++++++++++++++++++++++++++++------------------------------------
Mtests/add_friend/test.pl | 14++++++++------
Mtests/add_friend_bad_num/test.pl | 12++++++++++--
Mtests/bad_deviceid/server.log.good | 30++++++++++++++++--------------
Mtests/bad_deviceid/test.pl | 30++++++++++++++++++------------
Mtests/bad_msg/server.log.good | 3+--
Mtests/double_register/test.pl | 18+++++++++++-------
Mtests/leave_list_self/test.pl | 23++++++++++++++---------
Mtests/list_request_basic/test.pl | 16+++++++++-------
Mtests/msg_ok/test.pl | 11++++++-----
Mtests/mutual_friends_basic/test.pl | 27+++++++++++++++++----------
Mtests/new_device/server.log.good | 1+
Mtests/new_device/test.pl | 20++++++++++++++------
Mtests/new_list/test.pl | 17+++++++----------
Mtests/new_list_missing_name/server.log.good | 2+-
Mtests/new_list_missing_name/test.pl | 12+++++++++---
Mtests/payload_invalid/server.log.good | 20++++++++++----------
Mtests/payload_invalid/test.pl | 10++++++++--
Mtests/test.pm | 105++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------------------
Mtests/two_lists_same_name/test.pl | 28++++++++++++----------------
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');