Line data Source code
1 : /* Copyright (C) 2021 Wildfire Games.
2 : * This file is part of 0 A.D.
3 : *
4 : * 0 A.D. is free software: you can redistribute it and/or modify
5 : * it under the terms of the GNU General Public License as published by
6 : * the Free Software Foundation, either version 2 of the License, or
7 : * (at your option) any later version.
8 : *
9 : * 0 A.D. is distributed in the hope that it will be useful,
10 : * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 : * GNU General Public License for more details.
13 : *
14 : * You should have received a copy of the GNU General Public License
15 : * along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
16 : */
17 :
18 : #include "precompiled.h"
19 :
20 : #include "NetFileTransfer.h"
21 :
22 : #include "lib/timer.h"
23 : #include "network/NetMessage.h"
24 : #include "network/NetSession.h"
25 : #include "ps/CLogger.h"
26 :
27 0 : Status CNetFileTransferer::HandleMessageReceive(const CNetMessage& message)
28 : {
29 0 : switch (message.GetType())
30 : {
31 0 : case NMT_FILE_TRANSFER_RESPONSE:
32 0 : return OnFileTransferResponse(static_cast<const CFileTransferResponseMessage&>(message));
33 :
34 0 : case NMT_FILE_TRANSFER_DATA:
35 0 : return OnFileTransferData(static_cast<const CFileTransferDataMessage&>(message));
36 :
37 0 : case NMT_FILE_TRANSFER_ACK:
38 0 : return OnFileTransferAck(static_cast<const CFileTransferAckMessage&>(message));
39 :
40 0 : default:
41 0 : return INFO::SKIPPED;
42 : }
43 : }
44 :
45 0 : Status CNetFileTransferer::OnFileTransferResponse(const CFileTransferResponseMessage& message)
46 : {
47 0 : const FileReceiveTasksMap::iterator it = m_FileReceiveTasks.find(message.m_RequestID);
48 0 : if (it == m_FileReceiveTasks.end())
49 : {
50 0 : LOGERROR("Net transfer: Unsolicited file transfer response (id=%lu)", message.m_RequestID);
51 0 : return ERR::FAIL;
52 : }
53 :
54 0 : if (message.m_Length == 0 || message.m_Length > MAX_FILE_TRANSFER_SIZE)
55 : {
56 0 : LOGERROR("Net transfer: Invalid size for file transfer response (length=%lu)", message.m_Length);
57 0 : return ERR::FAIL;
58 : }
59 :
60 0 : CNetFileReceiveTask& task = *it->second;
61 :
62 0 : task.m_Length = message.m_Length;
63 0 : task.m_Buffer.reserve(message.m_Length);
64 :
65 0 : LOGMESSAGERENDER("Downloading data over network (%lu KB) - please wait...", task.m_Length / 1024);
66 0 : m_LastProgressReportTime = timer_Time();
67 :
68 0 : return INFO::OK;
69 : }
70 :
71 0 : Status CNetFileTransferer::OnFileTransferData(const CFileTransferDataMessage& message)
72 : {
73 0 : FileReceiveTasksMap::iterator it = m_FileReceiveTasks.find(message.m_RequestID);
74 0 : if (it == m_FileReceiveTasks.end())
75 : {
76 0 : LOGERROR("Net transfer: Unsolicited file transfer data (id=%lu)", message.m_RequestID);
77 0 : return ERR::FAIL;
78 : }
79 :
80 0 : CNetFileReceiveTask& task = *it->second;
81 :
82 0 : task.m_Buffer += message.m_Data;
83 :
84 0 : if (task.m_Buffer.size() > task.m_Length)
85 : {
86 0 : LOGERROR("Net transfer: Invalid size for file transfer data (length=%lu actual=%zu)", task.m_Length, task.m_Buffer.size());
87 0 : return ERR::FAIL;
88 : }
89 :
90 0 : CFileTransferAckMessage ackMessage;
91 0 : ackMessage.m_RequestID = task.m_RequestID;
92 0 : ackMessage.m_NumPackets = 1; // TODO: would be nice to send a single ack for multiple packets at once
93 0 : m_Session->SendMessage(&ackMessage);
94 :
95 0 : if (task.m_Buffer.size() == task.m_Length)
96 : {
97 0 : LOGMESSAGERENDER("Download completed");
98 :
99 0 : task.OnComplete();
100 0 : m_FileReceiveTasks.erase(message.m_RequestID);
101 0 : return INFO::OK;
102 : }
103 :
104 : // TODO: should report progress using proper GUI
105 :
106 : // Report the download status occassionally
107 0 : double t = timer_Time();
108 0 : if (t > m_LastProgressReportTime + 0.5)
109 : {
110 0 : LOGMESSAGERENDER("Downloading data: %.1f%% of %lu KB", 100.f * task.m_Buffer.size() / task.m_Length, task.m_Length / 1024);
111 0 : m_LastProgressReportTime = t;
112 : }
113 :
114 0 : return INFO::OK;
115 : }
116 :
117 0 : Status CNetFileTransferer::OnFileTransferAck(const CFileTransferAckMessage& message)
118 : {
119 0 : FileSendTasksMap::iterator it = m_FileSendTasks.find(message.m_RequestID);
120 0 : if (it == m_FileSendTasks.end())
121 : {
122 0 : LOGERROR("Net transfer: Unsolicited file transfer ack (id=%lu)", message.m_RequestID);
123 0 : return ERR::FAIL;
124 : }
125 :
126 0 : CNetFileSendTask& task = it->second;
127 :
128 0 : if (message.m_NumPackets > task.packetsInFlight)
129 : {
130 0 : LOGERROR("Net transfer: Invalid num packets for file transfer ack (num=%lu inflight=%lu)",
131 : message.m_NumPackets, task.packetsInFlight);
132 0 : return ERR::FAIL;
133 : }
134 :
135 0 : task.packetsInFlight -= message.m_NumPackets;
136 :
137 0 : return INFO::OK;
138 :
139 : }
140 :
141 0 : void CNetFileTransferer::StartTask(const std::shared_ptr<CNetFileReceiveTask>& task)
142 : {
143 0 : u32 requestID = m_NextRequestID++;
144 :
145 0 : task->m_RequestID = requestID;
146 0 : m_FileReceiveTasks[requestID] = task;
147 :
148 0 : CFileTransferRequestMessage request;
149 0 : request.m_RequestID = requestID;
150 0 : m_Session->SendMessage(&request);
151 0 : }
152 :
153 0 : void CNetFileTransferer::StartResponse(u32 requestID, const std::string& data)
154 : {
155 0 : CNetFileSendTask task;
156 0 : task.requestID = requestID;
157 0 : task.buffer = data;
158 0 : task.offset = 0;
159 0 : task.packetsInFlight = 0;
160 0 : task.maxWindowSize = DEFAULT_FILE_TRANSFER_WINDOW_SIZE;
161 :
162 0 : m_FileSendTasks[task.requestID] = task;
163 0 : CFileTransferResponseMessage respMessage;
164 0 : respMessage.m_RequestID = requestID;
165 0 : respMessage.m_Length = task.buffer.size();
166 0 : m_Session->SendMessage(&respMessage);
167 0 : }
168 :
169 0 : void CNetFileTransferer::Poll()
170 : {
171 : // Find tasks which have fewer packets in flight than their window size,
172 : // and send more packets
173 0 : for (std::pair<const u32, CNetFileSendTask>& p : m_FileSendTasks)
174 : {
175 0 : CNetFileSendTask& task = p.second;
176 :
177 0 : while (task.packetsInFlight < task.maxWindowSize && task.offset < task.buffer.size())
178 : {
179 0 : CFileTransferDataMessage dataMessage;
180 0 : dataMessage.m_RequestID = task.requestID;
181 0 : ssize_t packetSize = std::min(DEFAULT_FILE_TRANSFER_PACKET_SIZE, task.buffer.size() - task.offset);
182 0 : dataMessage.m_Data = task.buffer.substr(task.offset, packetSize);
183 0 : task.offset += packetSize;
184 0 : ++task.packetsInFlight;
185 0 : m_Session->SendMessage(&dataMessage);
186 : }
187 : }
188 :
189 : // TODO: need to garbage-collect finished tasks
190 0 : }
|