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