~aritra1911/mc_client

c96ab3f5ce8ff952b04683c99cd75c6c9e04b474 — Aritra Sarkar 1 year, 7 months ago e028859
Defer command recognition until server confirms
6 files changed, 203 insertions(+), 137 deletions(-)

M commands.c
M commands.h
M common.h
M main.c
M message.c
M message.h
M commands.c => commands.c +44 -21
@@ 19,6 19,7 @@
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <assert.h>


@@ 29,7 30,8 @@
#define DELIM " "

static int
add_send_code(SendCode snd_code, char *buf, char *rem, int *len)
add_send_code(SendCode snd_code, char *buf, char *rem, int *len,
              Message *pending)
{
    if ( buf < rem ) {
        /* Overwrite `buf` with remaining message content thereby


@@ 47,11 49,15 @@ add_send_code(SendCode snd_code, char *buf, char *rem, int *len)
    buf[0] = (char) snd_code;
    (*len)++;

    memcpy(pending->content, buf + 1, *len - 1);
    pending->content[*len - 1] = '\0';

    return *len;
}

static int
set_nick(char *nick, char *buf, char *rem, int *len, syntax_error_t *why)
set_nick(const char *nick, char *buf, char *rem, int *len, syntax_error_t *why,
         Message *pending)
{
    char new_nick[NICKLEN];



@@ 63,24 69,19 @@ set_nick(char *nick, char *buf, char *rem, int *len, syntax_error_t *why)
        return -1;
    }

    /* Set the new nick for the client */
    strcpy(pending->new_nick, new_nick);

    /*
     * Depending on whether current nick exists, decide if it's a join
     * or a nick change.
     */
    *buf = *nick ? SND_SETNICK : SND_JOIN;
    *buf = pending->type = *nick ? RES_SETNICK : RES_JOIN;

    /* Modify rest of `buf` for the announcement of join */
    strcpy(buf + 1, new_nick);

    *len = strlen(new_nick) + 1;    /* + 1 for the send code */

    /*
     * Set the new nick for the client
     * TODO: Defer doing this until we hear a confirmation from the
     *       server
     */
    strcpy(nick, new_nick);

    return *len;
}



@@ 89,7 90,7 @@ desc_se(syntax_error_t err_code, char *desc)
{
    switch ( err_code ) {
    case REQ_ARG:
        strcpy(desc, "No argument specified!!");
        strcpy(desc, "No arguments specified!!");
        break;

    case EXTRA_ARGS:


@@ 108,20 109,30 @@ desc_se(syntax_error_t err_code, char *desc)
}

int
parse_command(char *nick, char *buf, int *len, syntax_error_t *why, char *help)
parse_command(char *nick, char *buf, int *len, Message **pending,
              syntax_error_t *why, char *help)
{
    char *rem, *command;

    /*
     * Transforms a user typed message / command
     * into something the server can understand
     * into something the server can understand.
     */

    *help = '\0';

    if ( buf[0] != '/' )
    if ( pending ) {
        *pending = calloc(1, sizeof(Message));
        strcpy((*pending)->nick, nick);
    }

    if ( buf[0] != '/' ) {
        /* We've got a normal message */
        return add_send_code(SND_NORMAL, buf, buf, len);
        if ( pending )
            (*pending)->type = RES_NORMAL;

        return add_send_code(SND_NORMAL, buf, buf, len, *pending);
    }

    if ( buf[1] == '/' ) {
        /*


@@ 129,7 140,12 @@ parse_command(char *nick, char *buf, int *len, syntax_error_t *why, char *help)
         * and treated as a normal message.  We can achieve that by
         * just replacing the starting '/' with the send code
         */
        buf[0] = (char) SND_NORMAL;
        *buf = (char) SND_NORMAL;
        if ( pending ) {
            (*pending)->type = RES_NORMAL;
            memcpy((*pending)->content, buf + 1, *len - 1);
            (*pending)->content[*len - 1] = '\0';
        }
        return *len;
    }



@@ 152,7 168,7 @@ parse_command(char *nick, char *buf, int *len, syntax_error_t *why, char *help)
            return -1;
        }

        return set_nick(nick, buf, rem, len, why);
        return set_nick(nick, buf, rem, len, why, *pending);
    }

    if ( !strcmp(command, "/me") ) {


@@ 165,11 181,18 @@ parse_command(char *nick, char *buf, int *len, syntax_error_t *why, char *help)
            return -1;
        }

        return add_send_code(SND_ACTION, buf, rem, len);
        if ( pending )
            (*pending)->type = RES_ACTION;

        return add_send_code(SND_ACTION, buf, rem, len, *pending);
    }

    if ( !strcmp(command, "/quit") )
        return add_send_code(SND_QUIT, buf, rem, len);
    if ( !strcmp(command, "/quit") ) {
        if ( pending )
            (*pending)->type = RES_QUIT;

        return add_send_code(SND_QUIT, buf, rem, len, *pending);
    }

    if ( why ) *why = BAD_CMD;
    return -1;

M commands.h => commands.h +4 -2
@@ 1,6 1,8 @@
#ifndef _COMMANDS_H
# define _COMMANDS_H

# include "message.h"

/* TODO: We need command syntax errors */
typedef enum {
    REQ_ARG=1,


@@ 10,8 12,8 @@ typedef enum {

char *desc_se(syntax_error_t, char *desc);

int parse_command(char *nick, char *buf, int *len, syntax_error_t *,
                  char *help);
int parse_command(char *nick, char *buf, int *len, Message **pending,
                  syntax_error_t *, char *help);


#endif  /* _COMMANDS_H */

M common.h => common.h +21 -12
@@ 25,21 25,30 @@

# define PORT "4200"

/* TODO: Can we get rid of SND_JOIN and RES_JOIN ?? */

typedef enum {
    SND_NORMAL=0,   /* A normal message */
    SND_JOIN,       /* Join */
    SND_SETNICK,    /* Change nick */
    SND_ACTION,     /* Perform an action */
    SND_QUIT        /* Quit */
    SND_NORMAL=0,       /* A normal message */
    SND_JOIN,           /* Join */
    SND_SETNICK,        /* Change nick */
    SND_ACTION,         /* Perform an action */
    SND_QUIT,           /* Quit */
    SND_KEY             /* Sending session key to server */
} SendCode;

typedef enum _ResponseCode {
    RES_NORMAL=0,   /* Normal group messages */
    RES_JOIN,       /* User joins */
    RES_SETNICK,    /* Nick changes */
    RES_ACTION,     /* Actions */
    RES_QUIT,       /* User quits or disconnects */
    RES_ERROR       /* User did something wrong */
/*
 * TODO: We only need RES_ERROR and RES_CONFIRM in ResponseCode;
 *       everything else are redundant
 */

typedef enum {
    RES_NORMAL=0,       /* Normal group messages */
    RES_JOIN,           /* User joins */
    RES_SETNICK,        /* Nick changes */
    RES_ACTION,         /* Actions */
    RES_QUIT,           /* User quits or disconnects */
    RES_ERROR,          /* User did something wrong */
    RES_CONFIRM         /* Server announces to all */
} ResponseCode;

typedef enum {

M main.c => main.c +65 -55
@@ 20,6 20,7 @@

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>


@@ 361,7 362,7 @@ int main(int argc, char **argv)
    char buf[BUFLEN], desc[256], help[256], nick[NICKLEN]="";
    pref_t prefs;
    syntax_error_t why;
    Message msg;
    Message msg, *pending=NULL;

    /*
     * We'll have two `pfds`, one for `stdin` and another for the


@@ 409,34 410,33 @@ int main(int argc, char **argv)
            break;
        }

        if ( !num_events ) {
            /*
             * WARNING : COLD PATH !!
             * This can't ever happen since timeout is infinte.
             */
            fprintf(stderr, "`poll()` timed out.\n");
            continue;   /* Go back to `poll()`ing */
        }
        /*
         * If num_events == 0, this means that `poll()` has timed out.
         * This can't ever happen since timeout is infinte.
         */
        assert(num_events);

        /* Now we want to know who is ready to read */
        if ( pfds[0].revents & POLLIN ) {
            /* `stdin` is ready to read. */
            int items;

            /* `scanf()` should now return immediately. */
            if ( scanf("%[^\n]", buf) == EOF ) {
            if ( (items = scanf("%[^\n]", buf)) == EOF ) {
                printf("EOF\n");
                break;
            }
            getchar();  /* Eat trailing LF ('\n') character */

            numbytes = strlen(buf);
            numbytes = items ? strlen(buf) : 0;
            buf[numbytes] = '\0';

            /*
             * WARNING: This funtion mutates contents of `nick`, `buf`
             *          and `numbytes`.
             */
            if ( parse_command(nick, buf, &numbytes, &why, help) == -1 ) {
            if ( parse_command(nick, buf, &numbytes, &pending,
                               &why, help) == -1 ) {

                printf("\x1b[A\x1b[2K\r");  /* Move up and clear line */
                fflush(stdout);


@@ 444,7 444,7 @@ int main(int argc, char **argv)
                fprintf(stderr, "[x] Syntax Error: `%s`: %s\n",
                        buf, desc_se(why, desc));

                if ( *help ) fprintf(stderr, "[?] %s\n", help);
                if ( *help ) printf("[?] %s\n", help);

                continue;
            }


@@ 481,55 481,65 @@ int main(int argc, char **argv)
                break;
            }

            parse_response(buf, numbytes, &msg);
            parse_response(buf, numbytes, nick, &msg, &pending);

            if ( pending ) continue;

            if ( overwrite ) {
                overwrite = 0;

                /* Alter above line with server's echo */
                printf("\x1b[A\x1b[2K\r");  /* Move up and clear line */
                overwrite = 0;

                /*
                 * We need to explicitly flush stdout for the overwrite
                 * to take effect.  Since it doesn't end with a line
                 * feed ('\n') character, it doesn't get flushed
                 * automatically.
                 */
                fflush(stdout);
            }

            switch( msg.type ) {
                case RES_NORMAL:
                    /* Decorate nick and print */
                    printf("<%s> %s\n", msg.nick, msg.content);
                    break;

                case RES_JOIN:
                    /* Announce join */
                    printf("[!] %s has joined\n", msg.nick);
                    break;

                case RES_SETNICK:
                    /* Announce nick change */
                    printf("[!] %s is now known as %s\n",
                            msg.nick, msg.new_nick);
                    break;

                case RES_ACTION:
                    printf(" * %s %s\n", msg.nick, msg.content);
                    break;

                case RES_QUIT:
                    printf("[!] %s has quit [%s]\n", msg.nick, msg.content);
                    break;

                case RES_ERROR:

                    /* Ignore these errors */
                    if ( msg.err_code != EMPTY_NICK &&
                         msg.err_code != NO_NICK_CHANGE ) {

                        char desc[128];
                        describe_error(msg.err_code, desc);
                        printf("[x] %s\n", desc);

                    } else {
                        /* Yes, it wasn't flushing and the overwrite
                         * wasn't taking effect with GNU libc 2.33 and
                         * with musl libc 1.2.2 */
                        fflush(stdout);
                    }
            case RES_NORMAL:
                /* Decorate nick and print */
                printf("<%s> %s\n", msg.nick, msg.content);
                break;

            case RES_JOIN:
                /* Announce join */
                printf("[!] %s has joined\n", msg.new_nick);
                break;

            case RES_SETNICK:
                /* Announce nick change */
                printf("[!] %s is now known as %s\n",
                       msg.nick, msg.new_nick);
                break;

            case RES_ACTION:
                printf(" * %s %s\n", msg.nick, msg.content);
                break;

            case RES_QUIT:
                printf("[!] %s has quit [%s]\n", msg.nick, msg.content);
                break;

            case RES_ERROR:
                /* Ignore these errors */
                if ( msg.err_code != EMPTY_NICK &&
                     msg.err_code != NO_NICK_CHANGE ) {

                    char desc[128];
                    describe_error(msg.err_code, desc);
                    printf("[x] %s\n", desc);
                }
                break;

            case RES_CONFIRM:
                fprintf(stderr, " ERR : %s:%i:"
                                      " Unreachable code has been reached!",
                        __FILE__, __LINE__ - 2);
            }
        }
    }

M message.c => message.c +66 -45
@@ 19,6 19,8 @@
 */

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <string.h>
#include <ctype.h>



@@ 62,61 64,80 @@ int describe_error(ResponseError err_code, char *description)
    return -1;
}

int parse_response(const char *buf, int len, Message *msg)
int
parse_response(const char *buf, int len, char *nick, Message *msg,
               Message **pending)
{
    /* The first byte of `buf` is the response code */
    msg->type = (ResponseCode) buf[0];
    msg->type = *buf;

    /* Skip the response code */
    buf++;
    len--;

    /* Initialize error code */
    msg->err_code = 0;

    /* The response code tells us what kind of message to expect */
    switch (msg->type) {
        case RES_NORMAL:
        case RES_ACTION:
        case RES_QUIT:
            /* buf = "<nick> <content>" */

            /* Extract the first word as nick */
            sscanf(buf, "%s", msg->nick);
            buf += strlen(msg->nick);
            len -= strlen(msg->nick);

            /* Get `buf` to the first byte of content */
            while ( len && buf[0] != '\0' && isspace(buf[0]) ) {
                buf++;
                len--;
            }

            strncpy(msg->content, buf, len);
            msg->content[len] = '\0';
            break;

        case RES_JOIN:
            /* buf = "<nick>" */

            /* Extract the first word as nick */
            sscanf(buf, "%s", msg->nick);
            msg->nick[len] = '\0';
            break;

        case RES_SETNICK:
            /* buf = "<nick> <new_nick>" */
            sscanf(buf, "%s %s", msg->nick, msg->new_nick);
            msg->new_nick[len - strlen(msg->nick) - 1] = '\0';
            break;

        case RES_ERROR:
            /* buf = <error_code> */
            msg->err_code = (ResponseError) buf[0];
            break;

        default:
            return -1;
    switch ( msg->type ) {
    case RES_NORMAL:
    case RES_ACTION:
    case RES_QUIT:
        /* buf = "<nick> <content>" */

        /* Extract the first word as nick */
        sscanf(buf, "%s", msg->nick);
        buf += strlen(msg->nick);
        len -= strlen(msg->nick);

        /* Get `buf` to the first byte of content */
        while ( len && buf[0] != '\0' && isspace(buf[0]) ) {
            buf++;
            len--;
        }

        memcpy(msg->content, buf, len);
        msg->content[len] = '\0';
        break;

    case RES_JOIN:
        /* buf = "<nick>" */

        /* Extract the first word as nick */
        sscanf(buf, "%s", msg->nick);
        msg->nick[len] = '\0';
        break;

    case RES_SETNICK:
        /* buf = "<nick> <new_nick>" */
        sscanf(buf, "%s %s", msg->nick, msg->new_nick);
        msg->new_nick[len - strlen(msg->nick) - 1] = '\0';
        break;

    case RES_ERROR:
        /* buf = <error_code> */
        msg->err_code = (ResponseError) buf[0];
        if ( pending && *pending ) {
            free(*pending);
            *pending = NULL;
        }
        break;

    case RES_CONFIRM:
        if ( pending ) {
            assert(*pending);

            memcpy(msg, *pending, sizeof(Message));

            free(*pending);
            *pending = NULL;

            if ( msg->type == RES_JOIN || msg->type == RES_SETNICK )
                strcpy(nick, msg->new_nick);
        }
        break;

    default:
        return -1;
    }

    return 0;

M message.h => message.h +3 -2
@@ 12,6 12,7 @@ typedef struct _Message {
} Message;

int describe_error(ResponseError, char *);
int parse_response(const char *, int, Message *);
int parse_response(const char *buf, int len, char *nick, Message *msg,
                   Message **pending);

#endif
#endif  /* _MESSAGE_H */