net.c (7775B)
1 /* net.c - cliente de red: conexion, protocolo de lineas, reconexion. */ 2 #include <stdarg.h> 3 #include <stdio.h> 4 #include <stdlib.h> 5 #include <string.h> 6 #include "net.h" 7 #include "../server/server.h" 8 9 struct Net { 10 Session *s; 11 LineConn lc; 12 char host[128]; 13 int port; 14 char name[20]; 15 char code[8]; 16 int seat, wseat; 17 char token[33]; 18 bool synced; 19 long long elapsed; /* ms que llevaba el turno segun la cabecera */ 20 struct Server *emb; 21 long long retry_at; 22 char seat_kind[2]; 23 bool seat_on[2]; 24 }; 25 26 /* ------------------------------------------------------------ conexion */ 27 28 static void idle_emb(void *ctx) { srv_poll(ctx, 0); } /* atender al servidor embebido mientras esperamos */ 29 30 bool net_send(Net *n, const char *fmt, ...) 31 { 32 char buf[SRV_LINE + 8]; 33 va_list ap; 34 va_start(ap, fmt); 35 int len = vsnprintf(buf, sizeof buf, fmt, ap); 36 va_end(ap); 37 if (len < 0 || len >= (int)sizeof buf) return false; 38 return lc_send(&n->lc, "%s", buf); 39 } 40 41 bool net_read_line(Net *n, char *line, int len, int timeout_ms) { return lc_read_line(&n->lc, line, len, timeout_ms); } 42 43 static bool handshake(Net *n, char *err, int errlen) 44 { 45 char line[SRV_LINE]; 46 net_send(n, "HELLO %d %s", PROTO_VERSION, n->name); 47 if (!net_read_line(n, line, sizeof line, 5000)) { snprintf(err, (size_t)errlen, "%s", g_lang ? "no answer from server" : "el servidor no responde"); return false; } 48 if (strncmp(line, "WELCOME", 7)) { snprintf(err, (size_t)errlen, "%s", line); return false; } 49 return true; 50 } 51 52 Net *net_connect(const char *host, int port, const char *name, struct Server *emb, char *err, int errlen) 53 { 54 Net *n = calloc(1, sizeof *n); 55 if (!n) return 0; 56 n->emb = emb; 57 snprintf(n->host, sizeof n->host, "%s", host); 58 n->port = port; 59 snprintf(n->name, sizeof n->name, "%s", name && *name ? name : "?"); 60 lc_init(&n->lc, lc_dial(host, port, err, errlen)); 61 if (emb) { n->lc.idle = idle_emb; n->lc.idle_ctx = emb; } 62 if (n->lc.fd < 0 || !handshake(n, err, errlen)) { 63 lc_close(&n->lc); 64 free(n); 65 return 0; 66 } 67 return n; 68 } 69 70 const char *net_code(Net *n) { return n->code; } 71 72 /* ------------------------------------------------------------ protocolo */ 73 74 static void update_status(Net *n) 75 { 76 Session *s = n->s; 77 if (!s) return; 78 int k = snprintf(s->status, sizeof s->status, "%s %s", g_lang ? "Code" : "Código", n->code); 79 if (n->lc.fd < 0) { snprintf(s->status + k, sizeof s->status - (size_t)k, " · %s", g_lang ? "reconnecting..." : "reconectando..."); return; } 80 int other = n->seat ^ 1; 81 if (n->seat_kind[other] == 'H') snprintf(s->status + k, sizeof s->status - (size_t)k, " · %s", g_lang ? "waiting for a rival (share the code)" : "esperando rival (pasá el código)"); 82 else if (n->seat_kind[other] == 'P') 83 snprintf(s->status + k, sizeof s->status - (size_t)k, " · %s %s", s->names[s->my_color ^ 1], n->seat_on[other] ? "●" : "○"); 84 } 85 86 static void handle_line(Net *n, const char *line) 87 { 88 Session *s = n->s; 89 if (!strncmp(line, "GAME ", 5)) { 90 char code[16], token[40]; 91 int seat; 92 if (sscanf(line + 5, "%15s %d %39s", code, &seat, token) == 3) { 93 snprintf(n->code, sizeof n->code, "%.7s", code); 94 snprintf(n->token, sizeof n->token, "%.32s", token); 95 n->seat = seat; 96 n->synced = false; 97 } 98 } else if (!strncmp(line, "HDR S ", 6)) { 99 int wseat; 100 long long el; 101 int used = 0; 102 if (s && sscanf(line + 6, "%d %lld %n", &wseat, &el, &used) == 2 && used && game_hdr_parse(line + 6 + used, &s->g)) { 103 n->wseat = wseat; 104 n->elapsed = el; 105 s->my_color = n->seat == wseat ? WHITE : BLACK; 106 s->kind[s->my_color] = PL_HUMAN; 107 s->kind[s->my_color ^ 1] = PL_REMOTE; 108 s->eval_ply = -1; 109 } 110 } else if (!strncmp(line, "SEAT ", 5)) { 111 int i, on; 112 char k, name[64] = ""; 113 if (sscanf(line + 5, "%d %c %d %63[^\n]", &i, &k, &on, name) >= 3 && (i == 0 || i == 1)) { 114 n->seat_kind[i] = k; 115 n->seat_on[i] = on; 116 if (s) { 117 int color = i == n->wseat ? WHITE : BLACK; 118 if (k == 'H') snprintf(s->names[color], sizeof s->names[color], "(%s)", g_lang ? "free" : "libre"); 119 else snprintf(s->names[color], sizeof s->names[color], "%.23s", name); 120 } 121 update_status(n); 122 } 123 } else if (!strncmp(line, "EV ", 3)) { 124 Act a; 125 if (s && act_parse(line + 3, &a) && act_resolve(&s->g, &a) && game_check(&s->g, &a) == E_OK) sess_on_act(s, &a); 126 } else if (!strcmp(line, "SYNC")) { 127 n->synced = true; 128 if (s) s->turn_start = now_ms() - n->elapsed; 129 update_status(n); 130 } else if (!strncmp(line, "ERR ", 4)) { 131 if (s) { 132 s->last_err = atoi(line + 4); 133 if (s->last_err <= 0 || s->last_err >= E__COUNT) s->last_err = E_ARGS; 134 s->err_pending = true; 135 } 136 } 137 } 138 139 bool net_enter_game(Net *n, Session *s, char *err, int errlen) 140 { 141 memset(s, 0, sizeof *s); 142 s->local = false; 143 s->net = n; 144 s->eval_ply = -1; 145 game_init(&s->g, 0, 0, false); 146 n->s = s; 147 char line[SRV_LINE]; 148 bool got_game = false; 149 while (net_read_line(n, line, sizeof line, 8000)) { 150 if (!strncmp(line, "ERR ", 4)) { 151 const char *t = strchr(line + 4, ' '); 152 snprintf(err, (size_t)errlen, "%s", t ? t + 1 : line); 153 return false; 154 } 155 if (!strncmp(line, "GAME ", 5)) got_game = true; 156 handle_line(n, line); 157 if (got_game && n->synced) { 158 char hp[160]; 159 snprintf(hp, sizeof hp, "%s:%d", n->host, n->port); 160 saved_game_add(n->emb ? "lan" : hp, n->code, n->seat, n->token); 161 return true; 162 } 163 } 164 snprintf(err, (size_t)errlen, "%s", g_lang ? "connection lost" : "se cortó la conexión"); 165 return false; 166 } 167 168 int net_submit(Net *n, Act *a) 169 { 170 Session *s = n->s; 171 if (n->lc.fd < 0 || !n->synced) return E_TURN; 172 /* chequeo local para respuesta inmediata (el servidor vuelve a validar) */ 173 int e = game_check(&s->g, a); 174 if (e) return e; 175 Act b = *a; 176 b.ms = 0; 177 char line[64]; 178 act_fmt(&b, line, sizeof line); 179 net_send(n, "MOVE %s", line); 180 return E_OK; 181 } 182 183 static void try_reconnect(Net *n) 184 { 185 char err[128]; 186 lc_init(&n->lc, lc_dial(n->host, n->port, err, sizeof err)); 187 if (n->lc.fd < 0) return; 188 if (!handshake(n, err, sizeof err)) { lc_close(&n->lc); return; } 189 n->lc.inlen = 0; 190 n->synced = false; 191 net_send(n, "OPEN %s %s", n->code, n->token); 192 } 193 194 void net_poll(Net *n) 195 { 196 if (n->emb) srv_poll(n->emb, 0); 197 if (n->lc.fd < 0) { 198 if (now_ms() >= n->retry_at) { 199 n->retry_at = now_ms() + 3000; 200 try_reconnect(n); 201 update_status(n); 202 } 203 return; 204 } 205 if (!lc_pump(&n->lc)) { 206 n->retry_at = now_ms() + 1000; 207 update_status(n); 208 } 209 char line[SRV_LINE]; 210 while (lc_take(&n->lc, line, sizeof line)) handle_line(n, line); 211 if (n->lc.fd >= 0 && now_ms() - n->lc.last_tx > 30000) net_send(n, "PING"); 212 } 213 214 int net_fds(Net *n, int *fds, int max) 215 { 216 int k = 0; 217 if (n->lc.fd >= 0 && k < max) fds[k++] = n->lc.fd; 218 if (n->emb) k += srv_fds(n->emb, fds + k, max - k); 219 return k; 220 } 221 222 int net_timeout(Net *n) 223 { 224 int t = n->lc.fd < 0 ? 1000 : 30000; 225 if (n->emb) { int b = srv_timeout(n->emb); if (b < t) t = b; } 226 return t; 227 } 228 229 void net_close(Net *n) 230 { 231 if (!n) return; 232 if (n->lc.fd >= 0) { net_send(n, "LEAVE"); lc_close(&n->lc); } 233 if (n->emb) srv_close(n->emb); 234 free(n); 235 }