/* Test for a closed network connection and try to reopen it.
 * Not all errors can be detected without sending data first.
 * 
 * Possible network errors:
 * - the client handling process on the server timed out or the server was
 *   restarted. Result: closed socket; will be fixed here
 * - the connection may become invalid due to outside interference
 *   (a reconnect + IP address change triggered by the ISP, for example)
 *   This case is especially evil, as it will only become detectable after a
 *   write (which will get us an RST packet from the server)
 * - network down, bad wifi signal, etc. - nothing we can do about these
 */
static int
test_restore_connection(void)
{
    int ret;
    fd_set rfds;
    struct timeval tv = { 0, 0 };
    char testbuf[1];

    if (sockfd < 0)
        return restart_connection();

    FD_ZERO(&rfds);
    FD_SET(sockfd, &rfds);

    ret = select(1, &rfds, NULL, NULL, &tv);
    /* ret < 0: select error. Lets hope for the best and continue */
    /* ret == 0: select timeout, no events on sockfd. The desired result. */
    if (ret > 0) {      /* sockfd is "ready"; check whether recv returns EOF as
                           we're not expecting any data */
        ret = recv(sockfd, testbuf, 1, MSG_PEEK);
        if (ret <= 0)
            return restart_connection();
        return TRUE;
    }
    return TRUE;
}
Example #2
0
/* an error ocurred */
static json_t *cmd_server_error(json_t *params, int display_only)
{
    int is_error;
    const char *msg;
    char errmsg[BUFSZ];
    
    if (json_unpack(params, "{sb,ss!}", "error", &is_error, "message", &msg) == -1)
	return NULL;
    
    /* the error field in the response indicates the server's view of the client
     * communication state. If error == FALSE, the problem is internal to the
     * server and the client presumably did nothing wrong.
     * In that case retrying the last command is OK
     */
    error_retry_ok = !is_error;
    
    if (is_error) {
	snprintf(errmsg, BUFSZ, "Server reports error: %s", msg);
	print_error(errmsg);
    }
    
    if (!restart_connection()) {
	print_error("Connection to server could not be re-established.");
	nhnet_disconnect();
	if (ex_jmp_buf_valid)
	    longjmp(ex_jmp_buf, 1);
    }
    
    return NULL;
}
/* Should only be called when the return value of nhnet_get_socket_fd refers to
   a readable file descriptor (otherwise it may well hang); and should be called
   in that situation. The client library will check for server cancels, and call
   win_server_cancel if it finds one; it will error out if it finds any other
   sort of message (because the server shouldn't be sending asynchronously
   otherwise).

   If the connection has dropped, this function will attempt to restart the
   connection; this may involve longjmping back to play_game in the process.

   This function is not async-signal-safe. (Most functions aren't, but it's less
   obvious with this one because its main purpose is to call win_server_cancel,
   which is signal-safe, and it's not unreasonable to want to call it directly
   from a SIGIO handler. You should handle the signal in the normal way instead,
   via bouncing it off your event loop.) */
void 
nhnet_check_socket_fd(void)
{
    json_t *j = receive_json_msg();
    void *iter;

    if (j == NULL) {
        /* connection failure */
        if (restart_connection()) /* might longjmp or return normally */
            return;

        /* we couldn't restart it */
        close(sockfd);
        sockfd = -1;
        conn_err = TRUE;
        playgame_jmp_buf_valid = 0;
        if (ex_jmp_buf_valid)
            longjmp(ex_jmp_buf, 1);
        /* otherwise we can't do anything about the error */
        return;
    }

    iter = json_object_iter(j);
    if (!iter || strcmp(json_object_iter_key(iter), "server_cancel") != 0) {
        /* Misbehaving server, it shouldn't be sending out of sequence */
        print_error("Server sent a JSON object out of sequence");
        if (ex_jmp_buf_valid) {
            playgame_jmp_buf_valid = 0;
            longjmp(ex_jmp_buf, 1);
        }
        /* otherwise we can't do anything about the error */
        return;
    }

    json_decref(j);
    client_windowprocs.win_server_cancel();
}
/* send a command to the server, handle callbacks (menus, getline, etc) and
   display data (map data, pline, player status, ...) and finally return the
   response to the command.

   A play_game response will longjmp() to the matching play_game request, so
   beware! */
json_t *
send_receive_msg(const char *const volatile msgtype, json_t *volatile jmsg)
{
    const char *volatile sendkey;
    char key[BUFSZ];
    char oldkey[BUFSZ];
    void *iter;
    volatile int retry_count = 3;

    if (conn_err && ex_jmp_buf_valid)
        longjmp(ex_jmp_buf, 1);

    /* quick connection sanity check and restoration attempt */
    if (!test_restore_connection())
        return NULL;

    if (!jmsg)
        return NULL;

    sendkey = msgtype;
    while (1) {
        /* send the message; keep the reference to jmsg */
        json_t *send_msg = json_pack("{sO}", sendkey, jmsg);
        if (!send_json_msg(send_msg)) {
            json_decref(send_msg);
            if (retry_count-- > 0 && restart_connection())
                continue;
            goto error;
        }
        json_decref(send_msg);

    receive_without_sending:
        ;
        /* receive the response */
        json_t *recv_msg = receive_json_msg();
        if (!recv_msg) {
            /* If no data is received, there must have been a network error.
               Presumably the send didn't succeed either and the sent data
               vanished, so reconnect. restart_connection() can longjmp back to
               play_game; if it doesn't, retry both send and receive. */
            if (retry_count-- > 0 && restart_connection())
                continue;
            goto error;
        }

        json_t *jdisplay = json_object_get(recv_msg, "display");
        if (jdisplay) {
            if (json_is_array(jdisplay))
                handle_display_list(jdisplay);
            else
                print_error
                    ("New display list doesn't have the right data type.");
            json_object_del(recv_msg, "display");
        }

        iter = json_object_iter(recv_msg);
        if (!iter) {
            print_error("Empty return object.");
            json_decref(recv_msg);
            json_decref(jmsg);
            return json_object();
        }

        /* The string returned by json_object_iter_key is only valid while
           recv_msg exists. Since we still want the value afterwards, it must
           be copied. */
        strncpy(key, json_object_iter_key(iter), BUFSZ - 1);

        if (!strcmp(key, "server_cancel")) {
            /* This message is special in that it can be called out of
               sequence, and has no response. */
            json_decref(recv_msg); /* free it */
            client_windowprocs.win_server_cancel();
            goto receive_without_sending;
        }
        if (!strcmp(key, "load_progress")) {
            /* This message is only called in-sequence, but it still has no
               response. */
            int progress;
            if (json_unpack(json_object_iter_value(iter), "{si!}",
                            "progress", &progress) != -1)
                client_windowprocs.win_load_progress(progress);
            json_decref(recv_msg); /* free it */
            goto receive_without_sending;
        }

        send_receive_recent_response = json_object_iter_value(iter);

        if (json_object_iter_next(recv_msg, iter))
            print_error("Too many JSON objects in response data.");

        /* keep only the core of the response and throw away the wrapper */
        json_incref(send_receive_recent_response);
        json_decref(recv_msg);

        if (strcmp(sendkey, "play_game") == 0) {
            /* We might need to longjmp back here. */
            if (setjmp(playgame_jmp_buf) == 0) {
                playgame_jmp_buf_valid = 1;
            } else {
                playgame_jmp_buf_valid = 0;
                /* key, sendkey might have any value right now, but we know what
                   they should be from the position in the control flow */
                sendkey = "play_game";
                memset(key, 0, sizeof key);
                strcpy(key, "play_game");
            }
        }

        /* If the response type doesn't match the request type then either:
           - this is a callback that needs to be handled first;
           - this is a request to longjmp() back to nhnet_play_game.

           To simplify the control flow, our longjmp back upon receiving a
           play_game response is unconditional, and ends up cancelling itself
           out if a play_game message gets a play_game response. This also
           guarantees that playgame_jmp_buf_valid is only set while
           playgame_jmp_buf is actually on the call stack. */
        if (strcmp(key, "play_game") == 0 && playgame_jmp_buf_valid)
            longjmp(playgame_jmp_buf, 1);

        if (strcmp(key, msgtype)) {
            json_t *srvmsg = send_receive_recent_response;
            /* The next line is unneccessary, but makes the control flow easier
               to follow in a debugger. */
            send_receive_recent_response = 0;
            json_t *newmsg = handle_netcmd(key, msgtype, srvmsg);
            if (!newmsg) {     /* server error */
                if (error_retry_ok && retry_count-- > 0 && restart_connection())
                    continue;  /* jmsg is still alive, use it again */
                goto error;
            }

            json_decref(jmsg);
            jmsg = newmsg;
            strcpy(oldkey, key);
            sendkey = oldkey;

            /* send the callback data to the server and get a new response */
            continue;
        }

        json_decref(jmsg);
        break;  /* only loop via continue */
    }

    json_t *response = send_receive_recent_response;
    send_receive_recent_response = 0;

    return response;

error:
    json_decref(jmsg);
    close(sockfd);
    sockfd = -1;
    conn_err = TRUE;
    playgame_jmp_buf_valid = 0;
    if (ex_jmp_buf_valid)
        longjmp(ex_jmp_buf, 1);
    return NULL;
}
Example #5
0
/* send a command to the server, handle callbacks (menus, getline, etc) and
 * display data (map data, pline, player status, ...) and finally return the
 * response to the command. */
json_t *send_receive_msg(const char *msgtype, json_t *jmsg)
{
    json_t *recv_msg, *jobj, *jdisplay;
    const char *sendkey;
    char key[BUFSZ];
    void *iter;
    int retry_count = 3;
    
    if (conn_err && ex_jmp_buf_valid)
	longjmp(ex_jmp_buf, 1);
    
    /* quick connection sanity check and restoration attempt */
    if (!test_restore_connection())
	return NULL;
    
    if (!jmsg)
	return NULL;
    
    sendkey = msgtype;
    while (1) {
	/* send the message */
	jobj = json_pack("{sO}", sendkey, jmsg); /* keep the reference to jmsg */
	if (!send_json_msg(jobj)) {
	    json_decref(jobj);
	    if (retry_count-- > 0 && restart_connection())
		continue;
	    goto error;
	}
	
	/* receive the response */
	recv_msg = receive_json_msg();
	if (!recv_msg) {
	    json_decref(jobj);
	    /* If no data is received, there must have been a network error.
	     * Presumably the send didn't succeed either and the sent data
	     * vanished, so reconnect and retry both send and receive. */
	    if (retry_count-- > 0 && restart_connection())
		continue;
	    goto error;
	}
	
	json_decref(jobj);
	jdisplay = json_object_get(recv_msg, "display");
	if (jdisplay) {
	    if (json_is_array(jdisplay))
		handle_display_list(jdisplay);
	    else
		print_error("New display list doesn't have the right data type.");
	    json_object_del(recv_msg, "display");
	}
	
	iter = json_object_iter(recv_msg);
	if (!iter) {
	    print_error("Empty return object.");
	    json_decref(recv_msg);
	    json_decref(jmsg);
	    return json_object();
	}
	
	/* The string returned by json_object_iter_key is only valid while recv_msg
	 * exists. Since we still want the value afterwards, it must be copied */
	strncpy(key, json_object_iter_key(iter), BUFSZ - 1);
	jobj = json_object_iter_value(iter);
	
	if (json_object_iter_next(recv_msg, iter))
	    print_error("Too many JSON objects in response data.");
	
	/* keep only the core of the response and throw away the wrapper */
	json_incref(jobj);
	json_decref(recv_msg);
	
	/* if the response type doesn't match the request type this must be a
	 * callback that needs to be handled first. */
	if (strcmp(key, msgtype)) {
	    jobj = handle_netcmd(key, jobj);
	    if (!jobj) {/* this only happens after server errors */
		if (error_retry_ok && retry_count-- > 0 && restart_connection())
		    continue;
		
		json_decref(jmsg);
		return NULL;
	    }
	    
	    json_decref(jmsg);
	    jmsg = jobj;
	    sendkey = key;
	    
	    /* send the callback data to the server and get a new response */
	    continue;
	}
	json_decref(jmsg);
	break; /* only loop via continue */
    }
    
    return jobj;
    
error:
    json_decref(jmsg);
    close(sockfd);
    sockfd = -1;
    conn_err = TRUE;
    if (ex_jmp_buf_valid)
	longjmp(ex_jmp_buf, 1);
    return NULL;
}