Codechange: Use CargoArray for linkgraph refresher. (#13165)
[openttd-github.git] / src / network / core / tcp.cpp
blob8bd7b44f2f63472e07b48e18c3fcf65a7b514bba
1 /*
2 * This file is part of OpenTTD.
3 * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
4 * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
5 * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
6 */
8 /**
9 * @file tcp.cpp Basic functions to receive and send TCP packets.
12 #include "../../stdafx.h"
13 #include "../../debug.h"
15 #include "tcp.h"
17 #include "../../safeguards.h"
19 /**
20 * Construct a socket handler for a TCP connection.
21 * @param s The just opened TCP connection.
23 NetworkTCPSocketHandler::NetworkTCPSocketHandler(SOCKET s) :
24 NetworkSocketHandler(),
25 sock(s), writable(false)
29 NetworkTCPSocketHandler::~NetworkTCPSocketHandler()
31 this->CloseSocket();
34 /**
35 * Close the actual socket of the connection.
36 * Please make sure CloseConnection is called before CloseSocket, as
37 * otherwise not all resources might be released.
39 void NetworkTCPSocketHandler::CloseSocket()
41 if (this->sock != INVALID_SOCKET) closesocket(this->sock);
42 this->sock = INVALID_SOCKET;
45 /**
46 * This will put this socket handler in a close state. It will not
47 * actually close the OS socket; use CloseSocket for this.
48 * @param error Whether we quit under an error condition or not.
49 * @return new status of the connection.
51 NetworkRecvStatus NetworkTCPSocketHandler::CloseConnection([[maybe_unused]] bool error)
53 this->MarkClosed();
54 this->writable = false;
56 this->packet_queue.clear();
57 this->packet_recv = nullptr;
59 return NETWORK_RECV_STATUS_OKAY;
62 /**
63 * This function puts the packet in the send-queue and it is send as
64 * soon as possible. This is the next tick, or maybe one tick later
65 * if the OS-network-buffer is full)
66 * @param packet the packet to send
68 void NetworkTCPSocketHandler::SendPacket(std::unique_ptr<Packet> &&packet)
70 assert(packet != nullptr);
72 packet->PrepareToSend();
73 this->packet_queue.push_back(std::move(packet));
76 /**
77 * Sends all the buffered packets out for this client. It stops when:
78 * 1) all packets are send (queue is empty)
79 * 2) the OS reports back that it can not send any more
80 * data right now (full network-buffer, it happens ;))
81 * 3) sending took too long
82 * @param closing_down Whether we are closing down the connection.
83 * @return \c true if a (part of a) packet could be sent and
84 * the connection is not closed yet.
86 SendPacketsState NetworkTCPSocketHandler::SendPackets(bool closing_down)
88 /* We can not write to this socket!! */
89 if (!this->writable) return SPS_NONE_SENT;
90 if (!this->IsConnected()) return SPS_CLOSED;
92 while (!this->packet_queue.empty()) {
93 Packet &p = *this->packet_queue.front();
94 ssize_t res = p.TransferOut<int>(send, this->sock, 0);
95 if (res == -1) {
96 NetworkError err = NetworkError::GetLast();
97 if (!err.WouldBlock()) {
98 /* Something went wrong.. close client! */
99 if (!closing_down) {
100 Debug(net, 0, "Send failed: {}", err.AsString());
101 this->CloseConnection();
103 return SPS_CLOSED;
105 return SPS_PARTLY_SENT;
107 if (res == 0) {
108 /* Client/server has left us :( */
109 if (!closing_down) this->CloseConnection();
110 return SPS_CLOSED;
113 /* Is this packet sent? */
114 if (p.RemainingBytesToTransfer() == 0) {
115 /* Go to the next packet */
116 this->packet_queue.pop_front();
117 } else {
118 return SPS_PARTLY_SENT;
122 return SPS_ALL_SENT;
126 * Receives a packet for the given client
127 * @return The received packet (or nullptr when it didn't receive one)
129 std::unique_ptr<Packet> NetworkTCPSocketHandler::ReceivePacket()
131 ssize_t res;
133 if (!this->IsConnected()) return nullptr;
135 if (this->packet_recv == nullptr) {
136 this->packet_recv = std::make_unique<Packet>(this, TCP_MTU);
139 Packet &p = *this->packet_recv.get();
141 /* Read packet size */
142 if (!p.HasPacketSizeData()) {
143 while (p.RemainingBytesToTransfer() != 0) {
144 res = p.TransferIn<int>(recv, this->sock, 0);
145 if (res == -1) {
146 NetworkError err = NetworkError::GetLast();
147 if (!err.WouldBlock()) {
148 /* Something went wrong... */
149 if (!err.IsConnectionReset()) Debug(net, 0, "Recv failed: {}", err.AsString());
150 this->CloseConnection();
151 return nullptr;
153 /* Connection would block, so stop for now */
154 return nullptr;
156 if (res == 0) {
157 /* Client/server has left */
158 this->CloseConnection();
159 return nullptr;
163 /* Parse the size in the received packet and if not valid, close the connection. */
164 if (!p.ParsePacketSize()) {
165 this->CloseConnection();
166 return nullptr;
170 /* Read rest of packet */
171 while (p.RemainingBytesToTransfer() != 0) {
172 res = p.TransferIn<int>(recv, this->sock, 0);
173 if (res == -1) {
174 NetworkError err = NetworkError::GetLast();
175 if (!err.WouldBlock()) {
176 /* Something went wrong... */
177 if (!err.IsConnectionReset()) Debug(net, 0, "Recv failed: {}", err.AsString());
178 this->CloseConnection();
179 return nullptr;
181 /* Connection would block */
182 return nullptr;
184 if (res == 0) {
185 /* Client/server has left */
186 this->CloseConnection();
187 return nullptr;
191 if (!p.PrepareToRead()) {
192 Debug(net, 0, "Invalid packet received (too small / decryption error)");
193 this->CloseConnection();
194 return nullptr;
196 return std::move(this->packet_recv);
200 * Check whether this socket can send or receive something.
201 * @return \c true when there is something to receive.
202 * @note Sets #writable if more data can be sent.
204 bool NetworkTCPSocketHandler::CanSendReceive()
206 assert(this->sock != INVALID_SOCKET);
208 fd_set read_fd, write_fd;
209 struct timeval tv;
211 FD_ZERO(&read_fd);
212 FD_ZERO(&write_fd);
214 FD_SET(this->sock, &read_fd);
215 FD_SET(this->sock, &write_fd);
217 tv.tv_sec = tv.tv_usec = 0; // don't block at all.
218 if (select(FD_SETSIZE, &read_fd, &write_fd, nullptr, &tv) < 0) return false;
220 this->writable = !!FD_ISSET(this->sock, &write_fd);
221 return FD_ISSET(this->sock, &read_fd) != 0;