#define _POSIX_C_SOURCE 200809L #include /* exit */ #include /* bool */ #include /* assert */ #include /* struct sockaddr, AF_UNIX, SOCK_DGRAM, socklen_t, socket, recv, send, connect */ #include /* struct sockaddr_un */ #include /* close, unlink, fork, alarm */ #include /* waitpid */ #include /* kill, SIGTERM */ #include /* errno, ENOENT, ESRCH */ #include /* memcmp, memcpy, memset, strncpy */ #include /* err, warn */ /* Naprogramujte proceduru ‹agree›, která obdrží: * * • ‹sock_fd›, popisovač datagramového socketu, * • ‹our_set›, ukazatel na pole, které popisuje podmnožinu ⟦S⟧ * množiny ⟦{ k ∈ ℤ | 0 ≤ k < 512 }⟧, a to tak, že číslo ⟦i⟧ je * přítomno právě když je v poli nastavený ⟦i⟧-tý bit,¹ * • ‹intersection› – ukazatel na neinicializované pole stejné * velikosti a stejného významu, do kterého uloží výsledek. * * Procedura (server) přijme od klienta datagram, který bude * obsahovat množinu čísel ve stejné podobě, jakou má parametr * ‹our_set›, provede průnik s ‹our_set› a tuto odešle jako odpověď * klientovi. Klient výslednou množinu potvrdí nebo zamítne zasláním * jednobajtového datagramu s hodnotou 1 (potvrzení) nebo 0 * (zamítnutí). Můžete předpokládat, že všechny příchozí datagramy * mají téhož odesílatele. * * Návratová hodnota ‹agree› bude: * * • ‹-3› v případě, že se domluva nezdařila – klient vybranou * množinu zamítl, * • ‹-2› při chybě protokolu (klient poslal data v nečekaném tvaru), * • ‹-1› v případě, že nastala systémová chyba na straně serveru, * • konečně ‹0› byla-li podmnožina úspěšně domluvena, pak zároveň * do ‹intersection› tuto uloží (v žádném jiném případě hodnoty * v ‹intersection› měnit nebude). * * ¹ Bity počítáme od nejméně významného bitu prvního bajtu až po * nejvýznamnější bit posledního bajtu v poli. */ int agree( int sock_fd, const char our_set[ 64 ], char intersection[ 64 ] ); /* ┄┄┄┄┄┄┄ %< ┄┄┄┄┄┄┄┄┄┄ následují testy ┄┄┄┄┄┄┄┄┄┄ %< ┄┄┄┄┄┄┄ */ static void unlink_if_exists( const char* file ) { if ( unlink( file ) == -1 && errno != ENOENT ) err( 2, "unlinking %s", file ); } static void close_or_warn( int fd, const char *name ) { if ( close( fd ) == -1 ) warn( "closing %s", name ); } static int prepare_socket( const char *addr ) { struct sockaddr_un sa = { .sun_family = AF_UNIX }; assert ( strlen( addr ) < sizeof sa.sun_path ); strncpy( sa.sun_path, addr, sizeof sa.sun_path ); unlink_if_exists( addr ); int sock_fd = socket( AF_UNIX, SOCK_DGRAM, 0 ); if ( sock_fd == -1 ) err( 1, "socket %s", addr ); if ( bind( sock_fd, ( struct sockaddr * ) &sa, sizeof sa ) ) err( 1, "binding to %s", addr ); return sock_fd; } static void cleanup_socket( int sock_fd, const char *addr ) { close_or_warn( sock_fd, addr ); unlink_if_exists( addr ); } static int run_client( const char *addr, const char set[ 64 ], unsigned char response, const char expected[ 64 ] ) { int sock_fd = prepare_socket( addr ); struct sockaddr_un srv_addr = { .sun_family = AF_UNIX, .sun_path = "zt.r1_server" }; if ( connect( sock_fd, ( struct sockaddr * ) &srv_addr, sizeof srv_addr ) ) err( 1, "connecting to %s", srv_addr.sun_path ); // response == 0xff je výhybka pro testování neúplné množiny if ( send( sock_fd, set, response == 0xff ? 32 : 64, 0 ) == -1 ) err( 1, "sending set to server" ); if ( response == 0xff ) return 0; char buf[ 64 ]; if ( recv( sock_fd, buf, 64, 0 ) == -1 ) err( 1, "receiving set from server" ); // response == 0xfe je výhybka pro testování prázdné odpovědi if ( send( sock_fd, &response, response == 0xfe ? 0 : 1, 0 ) == -1 ) err( 1, "sending reply to server" ); cleanup_socket( sock_fd, addr ); if ( expected ) return memcmp( buf, expected, 64 ); return 0; } static pid_t spawn_client( int serv_fd, const char *addr, const char set[ 64 ], unsigned char response, const char expected[ 64 ] ) { pid_t pid = fork(); if ( pid == -1 ) err( 1, "fork" ); // V potomkovi poběží klient s přednastavenými zprávami if ( pid == 0 ) { // Bezpodmínečně ukončit klient po 3 sekundách pro případ, že by se // vlivem chybného serveru zasekl v čekání na odpověď. alarm( 3 ); close_or_warn( serv_fd, "server socket in client" ); exit( run_client( addr, set, response, expected ) ); } return pid; } static int reap_client( pid_t pid ) { int status; if ( waitpid( pid, &status, 0 ) == -1 ) err( 1, "waitpid" ); if ( WIFEXITED( status ) ) return WEXITSTATUS( status ); return 1; } static int negotiate( const char server_set[ 64 ], const char client_set[ 64 ], unsigned char response, char result[ 64 ], const char expected[ 64 ] ) { // V době forku musí serverový socket existovat a být navázán. int sock_fd = prepare_socket( "zt.r1_server" ); pid_t pid = spawn_client( sock_fd, "zt.r1_client", client_set, response, expected ); int rv = agree( sock_fd, server_set, result ); cleanup_socket( sock_fd, "zt.r1_server" ); if ( reap_client( pid ) != 0 ) return 1; return rv; } static bool check_intersection( const char server_set[ 64 ], const char client_set[ 64 ], const char expected[ 64 ] ) { char received[ 64 ]; assert(( negotiate( server_set, client_set, 0x01, received, expected ) == 0 )); return memcmp( received, expected, 64 ) == 0; } int main( void ) { // Ukončit testy po 5 sekundách, pokud se zaseknou. alarm( 5 ); char set_empty[ 64 ]; memset( set_empty, 0x00, 64 ); char set_full[ 64 ]; memset( set_full, 0xff, 64 ); char set_even[ 64 ]; memset( set_even, 0x55, 64 ); char set_odd[ 64 ]; memset( set_odd, 0xaa, 64 ); char set_a[ 64 ]; for ( int i = 0; i < 64; ++i ) set_a[ i ] = i; // V následujících testech klient vždy přijme nabízený průnik assert( check_intersection( set_empty, set_empty, set_empty ) ); assert( check_intersection( set_empty, set_full, set_empty ) ); assert( check_intersection( set_full, set_empty, set_empty ) ); assert( check_intersection( set_full, set_full, set_full ) ); assert( check_intersection( set_odd, set_full, set_odd ) ); assert( check_intersection( set_full, set_even, set_even ) ); assert( check_intersection( set_even, set_odd, set_empty ) ); assert( check_intersection( set_full, set_a, set_a ) ); assert( check_intersection( set_a, set_full, set_a ) ); // Test na odmítnutí char received[ 64 ]; memcpy( received, set_a, 64 ); assert(( negotiate( set_full, set_full, 0x00, received, set_full ) == -3 )); assert(( memcmp( received, set_a, 64 ) == 0 )); assert(( negotiate( set_even, set_odd, 0x00, received, set_empty ) == -3 )); assert(( memcmp( received, set_a, 64 ) == 0 )); // Test na několikanásobné použití socketu za sebou int sock_fd = prepare_socket( "zt.r1_server" ); pid_t pid; pid = spawn_client( sock_fd, "zt.r1_client", set_even, 0x01, set_even ); assert(( agree( sock_fd, set_full, received ) == 0 )); assert(( memcmp( received, set_even, 64 ) == 0 )); assert( reap_client( pid ) == 0 ); pid = spawn_client( sock_fd, "zt.r1_client", set_odd, 0x01, set_odd ); assert(( agree( sock_fd, set_full, received ) == 0 )); assert(( memcmp( received, set_odd, 64 ) == 0 )); assert( reap_client( pid ) == 0 ); pid = spawn_client( sock_fd, "zt.r1_client", set_a, 0x00, set_a ); assert(( agree( sock_fd, set_full, received ) == -3 )); assert(( memcmp( received, set_odd, 64 ) == 0 )); assert( reap_client( pid ) == 0 ); // Test na nedodržení protokolu: klient odpoví něco jiného než 0/1 assert(( negotiate( set_full, set_a, 0x42, received, set_a ) == -2 )); assert(( memcmp( received, set_odd, 64 ) == 0 )); // Test na nedodržení protokolu: klient posílá neúplnou množinu assert(( negotiate( set_full, set_a, 0xff, received, NULL ) == -2 )); assert(( memcmp( received, set_odd, 64 ) == 0 )); // Test na nedodržení protokolu: klient pošle prázdnou odpověď assert(( negotiate( set_full, set_a, 0xfe, received, NULL ) == -2 )); assert(( memcmp( received, set_odd, 64 ) == 0 )); }