rubidium@5469: /* $Id$ */ rubidium@5469: rubidium@5469: #ifdef ENABLE_NETWORK rubidium@5469: rubidium@5469: #include "../../stdafx.h" rubidium@5469: #include "../../debug.h" rubidium@5469: #include "../../openttd.h" rubidium@5469: #include "../../variables.h" rubidium@5475: #include "table/strings.h" rubidium@5469: #include "../../functions.h" rubidium@5469: rubidium@5527: #include "../network_data.h" rubidium@5469: #include "packet.h" rubidium@5469: #include "tcp.h" rubidium@5469: rubidium@5469: /** rubidium@5469: * @file tcp.c Basic functions to receive and send TCP packets. rubidium@5469: */ rubidium@5469: rubidium@5469: /** rubidium@5469: * Functions to help NetworkRecv_Packet/NetworkSend_Packet a bit rubidium@5469: * A socket can make errors. When that happens this handles what to do. rubidium@5469: * For clients: close connection and drop back to main-menu rubidium@5469: * For servers: close connection and that is it rubidium@5469: * @param cs the client to close the connection of rubidium@5469: * @return the new status rubidium@5469: */ rubidium@5469: NetworkRecvStatus CloseConnection(NetworkClientState *cs) rubidium@5469: { rubidium@5469: NetworkCloseClient(cs); rubidium@5469: rubidium@5469: /* Clients drop back to the main menu */ rubidium@5469: if (!_network_server && _networking) { rubidium@5469: _switch_mode = SM_MENU; rubidium@5469: _networking = false; rubidium@5469: _switch_mode_errorstr = STR_NETWORK_ERR_LOSTCONNECTION; rubidium@5469: rubidium@5469: return NETWORK_RECV_STATUS_CONN_LOST; rubidium@5469: } rubidium@5469: rubidium@5469: return NETWORK_RECV_STATUS_OKAY; rubidium@5469: } rubidium@5469: rubidium@5469: /** rubidium@5469: * Whether the client has quit or not (used in packet.c) rubidium@5469: * @param cs the client to check rubidium@5469: * @return true if the client has quit rubidium@5469: */ rubidium@5528: bool HasClientQuit(const NetworkClientState *cs) rubidium@5469: { rubidium@5469: return cs->has_quit; rubidium@5469: } rubidium@5469: rubidium@5469: /** rubidium@5469: * This function puts the packet in the send-queue and it is send as rubidium@5469: * soon as possible. This is the next tick, or maybe one tick later rubidium@5469: * if the OS-network-buffer is full) rubidium@5469: * @param packet the packet to send rubidium@5469: * @param cs the client to send to rubidium@5469: */ rubidium@5469: void NetworkSend_Packet(Packet *packet, NetworkClientState *cs) rubidium@5469: { rubidium@5469: Packet *p; rubidium@5469: assert(packet != NULL); rubidium@5469: rubidium@5469: packet->pos = 0; rubidium@5469: packet->next = NULL; rubidium@5469: rubidium@5469: NetworkSend_FillPacketSize(packet); rubidium@5469: rubidium@5469: /* Locate last packet buffered for the client */ rubidium@5469: p = cs->packet_queue; rubidium@5469: if (p == NULL) { rubidium@5469: /* No packets yet */ rubidium@5469: cs->packet_queue = packet; rubidium@5469: } else { rubidium@5469: /* Skip to the last packet */ rubidium@5469: while (p->next != NULL) p = p->next; rubidium@5469: p->next = packet; rubidium@5469: } rubidium@5469: } rubidium@5469: rubidium@5469: /** rubidium@5469: * Sends all the buffered packets out for this client. It stops when: rubidium@5469: * 1) all packets are send (queue is empty) rubidium@5469: * 2) the OS reports back that it can not send any more rubidium@5469: * data right now (full network-buffer, it happens ;)) rubidium@5469: * 3) sending took too long rubidium@5469: * @param cs the client to send the packets for rubidium@5469: */ rubidium@5469: bool NetworkSend_Packets(NetworkClientState *cs) rubidium@5469: { rubidium@5469: ssize_t res; rubidium@5469: Packet *p; rubidium@5469: rubidium@5469: /* We can not write to this socket!! */ rubidium@5469: if (!cs->writable) return false; rubidium@5469: if (cs->socket == INVALID_SOCKET) return false; rubidium@5469: rubidium@5469: p = cs->packet_queue; rubidium@5469: while (p != NULL) { rubidium@5469: res = send(cs->socket, p->buffer + p->pos, p->size - p->pos, 0); rubidium@5469: if (res == -1) { rubidium@5469: int err = GET_LAST_ERROR(); rubidium@5469: if (err != EWOULDBLOCK) { rubidium@5469: /* Something went wrong.. close client! */ rubidium@5469: DEBUG(net, 0, "send failed with error %d", err); rubidium@5469: CloseConnection(cs); rubidium@5469: return false; rubidium@5469: } rubidium@5469: return true; rubidium@5469: } rubidium@5469: if (res == 0) { rubidium@5469: /* Client/server has left us :( */ rubidium@5469: CloseConnection(cs); rubidium@5469: return false; rubidium@5469: } rubidium@5469: rubidium@5469: p->pos += res; rubidium@5469: rubidium@5469: /* Is this packet sent? */ rubidium@5469: if (p->pos == p->size) { rubidium@5469: /* Go to the next packet */ rubidium@5469: cs->packet_queue = p->next; rubidium@5469: free(p); rubidium@5469: p = cs->packet_queue; rubidium@5469: } else { rubidium@5469: return true; rubidium@5469: } rubidium@5469: } rubidium@5469: rubidium@5469: return true; rubidium@5469: } rubidium@5469: rubidium@5469: /** rubidium@5469: * Receives a packet for the given client rubidium@5469: * @param cs the client to (try to) receive a packet for rubidium@5469: * @param status the variable to store the status into rubidium@5469: * @return the received packet (or NULL when it didn't receive one) rubidium@5469: */ rubidium@5469: Packet *NetworkRecv_Packet(NetworkClientState *cs, NetworkRecvStatus *status) rubidium@5469: { rubidium@5469: ssize_t res; rubidium@5469: Packet *p; rubidium@5469: rubidium@5469: *status = NETWORK_RECV_STATUS_OKAY; rubidium@5469: rubidium@5469: if (cs->socket == INVALID_SOCKET) return NULL; rubidium@5469: rubidium@5469: if (cs->packet_recv == NULL) { rubidium@5469: cs->packet_recv = malloc(sizeof(Packet)); rubidium@5469: if (cs->packet_recv == NULL) error("Failed to allocate packet"); rubidium@5469: /* Set pos to zero! */ rubidium@5469: cs->packet_recv->pos = 0; rubidium@5469: cs->packet_recv->size = 0; // Can be ommited, just for safety reasons rubidium@5469: } rubidium@5469: rubidium@5469: p = cs->packet_recv; rubidium@5469: rubidium@5469: /* Read packet size */ rubidium@5469: if (p->pos < sizeof(PacketSize)) { rubidium@5469: while (p->pos < sizeof(PacketSize)) { rubidium@5469: /* Read the size of the packet */ rubidium@5469: res = recv(cs->socket, p->buffer + p->pos, sizeof(PacketSize) - p->pos, 0); rubidium@5469: if (res == -1) { rubidium@5469: int err = GET_LAST_ERROR(); rubidium@5469: if (err != EWOULDBLOCK) { rubidium@5469: /* Something went wrong... (104 is connection reset by peer) */ rubidium@5469: if (err != 104) DEBUG(net, 0, "recv failed with error %d", err); rubidium@5469: *status = CloseConnection(cs); rubidium@5469: return NULL; rubidium@5469: } rubidium@5469: /* Connection would block, so stop for now */ rubidium@5469: return NULL; rubidium@5469: } rubidium@5469: if (res == 0) { rubidium@5469: /* Client/server has left */ rubidium@5469: *status = CloseConnection(cs); rubidium@5469: return NULL; rubidium@5469: } rubidium@5469: p->pos += res; rubidium@5469: } rubidium@5469: rubidium@5469: NetworkRecv_ReadPacketSize(p); rubidium@5469: rubidium@5469: if (p->size > SEND_MTU) { rubidium@5469: *status = CloseConnection(cs); rubidium@5469: return NULL; rubidium@5469: } rubidium@5469: } rubidium@5469: rubidium@5469: /* Read rest of packet */ rubidium@5469: while (p->pos < p->size) { rubidium@5469: res = recv(cs->socket, p->buffer + p->pos, p->size - p->pos, 0); rubidium@5469: if (res == -1) { rubidium@5469: int err = GET_LAST_ERROR(); rubidium@5469: if (err != EWOULDBLOCK) { rubidium@5469: /* Something went wrong... (104 is connection reset by peer) */ rubidium@5469: if (err != 104) DEBUG(net, 0, "recv failed with error %d", err); rubidium@5469: *status = CloseConnection(cs); rubidium@5469: return NULL; rubidium@5469: } rubidium@5469: /* Connection would block */ rubidium@5469: return NULL; rubidium@5469: } rubidium@5469: if (res == 0) { rubidium@5469: /* Client/server has left */ rubidium@5469: *status = CloseConnection(cs); rubidium@5469: return NULL; rubidium@5469: } rubidium@5469: rubidium@5469: p->pos += res; rubidium@5469: } rubidium@5469: rubidium@5469: /* We have a complete packet, return it! */ rubidium@5469: p->pos = 2; rubidium@5469: p->next = NULL; // Should not be needed, but who knows... rubidium@5469: rubidium@5469: /* Prepare for receiving a new packet */ rubidium@5469: cs->packet_recv = NULL; rubidium@5469: rubidium@5469: return p; rubidium@5469: } rubidium@5469: rubidium@5469: #endif /* ENABLE_NETWORK */