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