aboutsummaryrefslogtreecommitdiff
path: root/client.c
blob: 07e413fb2896cfdddc6773343f3b4577bcdc9acc (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
/*
    author: sotech117
    date: 9/25/2023
    course: csci1680
    description: client for snowcast, a music streaming service

    enjoy :)
*/

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <netdb.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <ctype.h>
#include <pthread.h>
#include <arpa/inet.h>
#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,"<server IP> <server port> <listener port>\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: %s", 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
}