/* 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;
}
Beispiel #3
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;
}