/* author: sotech117 date: 9/25/2023 course: csci1680 description: client for snowcast, a music streaming service enjoy :) */ #include #include #include #include #include #include #include #include #include #include #include #include #include "protocol.c" #define COMMAND_LINE_MAX 1024 // handles the handshake and returns the number of stations u_int16_t handle_handshake(int sockfd, const char* udp_port); // routine thread for the command line void *command_line_routine(void *args); int l = 0; // logging int station_is_set = 0; // if we have a station set int waiting = 0; // for command line "snowcast_control>" prompt to be a new lone int exiting = 0; // to not have error messages when the program exits "gracefully" main(int argc, char *argv[]) // no int here for good luck :) { // CHECK AND USE ARGUMENTS // ------------------------------------------------------------------------------------------------- if (argc != 4) { fprintf(stderr," \n"); exit(1); } const char* tcpPort = argv[2]; // port we use to connect to server's tcp stream const char* udpPort = argv[3]; // port we use to connect to server's udp info and command // SETUP TCP CONNECTION (getaaadrinfo->socket->connect) // ------------------------------------------------------------------------------------------------- struct addrinfo hints, *servinfo, *p; memset(&hints, 0, sizeof hints); hints.ai_family = AF_INET; // only IPv4 hints.ai_socktype = SOCK_STREAM; // resolve host & make socket int sockfd, err; if ((err = getaddrinfo(argv[1], tcpPort, &hints, &servinfo)) != 0) { fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(err)); return 1; } // loop through all the results and connect to the first we can for(p = servinfo; p != NULL; p = p->ai_next) { if ((sockfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) == -1) { perror("client: socket"); continue; } if (connect(sockfd, p->ai_addr, p->ai_addrlen) == -1) { close(sockfd); perror("client: connect"); continue; } break; } if (p == NULL) { fprintf(stderr, "client: failed to connect\n"); return 2; } // char s[INET_ADDRSTRLEN]; // for printing the ip address // inet_ntop(p->ai_family, get_in_addr((struct sockaddr *)p->ai_addr), s, sizeof s); freeaddrinfo(servinfo); // all done with this structure // DO HANDSHAKE & PRINT STATION NUMBER // ------------------------------------------------------------------------------------------------- // now that we're connectioned, let's do the handshake uint16_t num_stations = handle_handshake(sockfd, udpPort); // handle_handshake will end the program on fail // so if we're here, num_stations is correct fflush(stdout); printf("Welcome to Snowcast! The server has %u stations.\n", num_stations); fflush(stdout); // START COMMAND LINE THREAD // ------------------------------------------------------------------------------------------------- pthread_t command_line_thread; pthread_create(&command_line_thread, NULL, command_line_routine, (void*)&sockfd); // START WHILE LOOP THAT HANDLES ALL REPLIES // ------------------------------------------------------------------------------------------------- while (69) { // get the type of the incoming reply uint8_t reply_type = -1; // print size of utin8 if (recv(sockfd, &reply_type, 1, 0) == -1) { if (exiting) { // just to remove that pesky error message break; } perror("recv in first byte"); exit(1); } if (reply_type == WELCOME) { fprintf(stderr, "WECLOME reply received twice. Exiting.\n"); close(sockfd); exit(1); } if (reply_type == ANNOUNCE) { if (!station_is_set) { fprintf(stderr, "ANNOUNCE reply received before SETSTATION command. Exiting.\n"); close(sockfd); exit(1); } // get the string size for the songname u_int8_t string_size = -1; if (recv(sockfd, &string_size, 1, 0) == -1) { perror("recv in announce"); exit(1); } // read the songname char *song_name = malloc(string_size); if(song_name == NULL) { perror("malloc in song name"); } int bytes_to_read = string_size; if (recv_all(sockfd, song_name, &bytes_to_read) == -1) { perror("recv_all in announce"); exit(1); } remove_timeout(sockfd); // have received all, so can remove timeout if (l) printf("received ANNOUNCE reply.\n"); // print the songname if (!waiting) printf("\n"); // note: this is worth the lines for a clean cmd prompt waiting = 0; fflush(stdout); printf("New song announced: %s\n", song_name); printf("snowcast_control> "); fflush(stdout); free(song_name); continue; } if (reply_type == INVALID) { // we have an invalid command message // get the string size u_int8_t string_size = -1; if (recv(sockfd, &string_size, 1, 0) == -1) { perror("recv in invalid"); exit(1); } char *message = malloc(string_size); if(message == NULL) { perror("malloc in message"); } int bytes_to_read = string_size; if (recv_all(sockfd, message, &bytes_to_read) == -1) { perror("recv_all in invalid"); exit(1); } fflush(stdout); printf("received INVALID reply: %s.\nExiting.\n", message); fflush(stdout); free(message); close(sockfd); // close the program on all INVALID COMMANDS exit(1); } if (reply_type == STATIONINFO) { // we are getting STATIONINFO if (l) printf("received STATIONINFO reply.\n"); // get the string size, which can be farily long (uint32_t) uint32_t buf_string_size = -1; int bytes_to_read = sizeof(uint32_t); if (recv_all(sockfd, (char *) &buf_string_size, &bytes_to_read) == -1) { perror("recv_all 1 in stationinfo"); exit(1); } uint32_t string_size = ntohl(buf_string_size); // recieve the message char *info = malloc(string_size); if(info == NULL) { perror("malloc in info"); } bytes_to_read = string_size; if (recv_all(sockfd, info, &bytes_to_read) == -1) { perror("recv_all 2 in stationinfo"); exit(1); } remove_timeout(sockfd); // remove the timeout, now that we have the data // print the info fflush(stdout); printf("Station Information:\n%s\n", info); printf("snowcast_control> "); fflush(stdout); free(info); continue; } if (reply_type == STATIONSHUTDOWN) { // we are getting StationShutdown if (l) printf("received STATIONSHUTDOWN reply.\n"); // remove timeout, in case we were waiting for an announce remove_timeout(sockfd); if (!waiting) printf("\n"); // note: this is worth the lines for a clean cmd prompt :) waiting = 0; // station no longer set station_is_set = 0; fflush(stdout); printf("This station has shut down. Please select a different station.\n"); printf("snowcast_control> "); fflush(stdout); continue; } if (reply_type == NEWSTATION) { // we are getting NewStation if (l) printf("received NEWSTATION reply.\n"); // get station number uint16_t station_number = -1; if (recv(sockfd, &station_number, 2, 0) == -1) { perror("recv in new station"); exit(1); } station_number = ntohs(station_number); // print fflush(stdout); printf("\nThere is now a new station @ index %u.\n", station_number); printf("snowcast_control> "); fflush(stdout); continue; } // if we're here, lost conneciton to the server -> end the program printf("\nsocket to server HUNGUP. Exiting.\n"); close(sockfd); exit(1); } return 0; } /* thread for managing the command line */ void *command_line_routine(void* args) { // unpack sockfd as arg int sockfd = *(int *) args; // buffer for input char input[COMMAND_LINE_MAX]; printf("Enter a number to change to it's station. Enter q to end stream.\n"); printf("snowcast_control> "); fflush(stdout); while (420) { memset(input, 0, COMMAND_LINE_MAX); char *line = fgets(input, COMMAND_LINE_MAX, stdin); // nothing was typed if (line == NULL) { continue; } // "q" command: exit the program if (strncmp("q\n", input, COMMAND_LINE_MAX) == 0) { // end code if type in q exiting = 1; printf("Exiting.\n"); close(sockfd); exit(0); } // "l" command: STATIONINFO command (EXTRA CREDIT) if (strncmp("l\n", input, COMMAND_LINE_MAX) == 0) { // send the command to list stations if (l) printf("sending LISTSTATIONS command. waiting for STATIONINFO reply.\n"); apply_timeout(sockfd); // apply a timeout, will be released when we get the reply int list_station_reply_type = 5; if (send(sockfd, &list_station_reply_type, 1, 0) == -1) { perror("send"); exit(1); } continue; } // "log" command: toggle logging if (strncmp("log\n", input, COMMAND_LINE_MAX) == 0) { l = !l; printf("LOGGING is now %s!\n", l ? "on" : "off"); printf("snowcast_control> "); fflush(stdout); continue; } // check if this could be a station number int inputInt = atoi(input); if (input[0] != '0' && inputInt == 0) { // if we're in here, it's ~likely~ not a number (ik it's not perfect, sorry :/) printf("unknown command: %si", input); printf("snowcast_control> "); fflush(stdout); continue; } // if we're here, it was a valid number, // & we need to send SETSTATION command. if (l) printf("sending SETSTATION command.\n"); waiting = 1; // just for clean cmd // setup struct struct SetStation setStation; setStation.commandType = SETSTATION; setStation.stationNumber = htons(inputInt); // send it apply_timeout(sockfd); // apply timeout, will be released when we get ANNOUNCE reply int bytes_to_send = sizeof(struct SetStation); if (send_all(sockfd, (char *) &setStation, &bytes_to_send) == -1) { perror("send_all"); exit(1); } if (!station_is_set) station_is_set = 1; // update, if needed if (l) printf("waiting for ANNOUNCE reply...\n"); } return (NULL); } /* handles the handshake, given the new socket fd and the udp port of this client returns the number of stations note: will close the program on failure! */ uint16_t handle_handshake(int sockfd, const char* udp_port) { if (l) printf("found server, sending HELLO command.\n"); apply_timeout(sockfd); // apply timeout for handshake // after we find connection, we need to send HELLO command struct Hello hello; hello.commandType = HELLO; // convert updPort to an int int udp_port_int = atoi(udp_port); hello.udpPort = htons(udp_port_int); int hello_len = sizeof(struct Hello); if (send_all(sockfd, (char *) &hello, &hello_len) == -1) { perror("send"); exit(1); } if (l) printf("waiting for WELCOME reply...\n"); // read WELCOME reply type uint8_t reply_type = -1; // print size of utin8 if (recv(sockfd, (void *) &reply_type, 1, 0) == -1) { // only one byte, so can use recv perror("recv in handshake"); exit(1); } if (reply_type != WELCOME) { fprintf(stderr, "first reply is not WELCOME. Exiting.\n"); close(sockfd); exit(1); } // get the number of stations int16_t num_stations = -1; int bytes_to_read = sizeof(uint16_t); if (recv_all(sockfd, (char *) &num_stations, &bytes_to_read) == -1) { perror("recv_all in handshake"); exit(1); } if (l) printf("received ANNOUNCE reply.\n"); remove_timeout(sockfd); // remove timeout since we no longer are "waiting" for an immediate reply return ntohs(num_stations); // return the num_stations to be printed }