1 /*
2  *  Copyright (C) 2011-2016  OpenDungeons Team
3  *
4  *  This program 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 3 of the License, or
7  *  (at your option) any later version.
8  *
9  *  This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.
16  */
17 
18 #include "ODSocketClient.h"
19 #include "network/ODPacket.h"
20 #include "network/ServerNotification.h"
21 
22 #include "utils/Helper.h"
23 #include "utils/LogManager.h"
24 
25 #include <boost/date_time/posix_time/posix_time.hpp>
26 #include <boost/filesystem.hpp>
27 
connect(const std::string & host,const int port,uint32_t timeout,const std::string & outputReplayFilename)28 bool ODSocketClient::connect(const std::string& host, const int port, uint32_t timeout, const std::string& outputReplayFilename)
29 {
30     mSource = ODSource::none;
31 
32     // As we use selector, there is no need to set the socket as not-blocking
33     sf::Socket::Status status = mSockClient.connect(host, port, sf::milliseconds(timeout));
34     if (status != sf::Socket::Done)
35     {
36         OD_LOG_ERR("Could not connect to distant server status="
37             + Helper::toString(status));
38         mSockClient.disconnect();
39         return false;
40     }
41     mSockSelector.add(mSockClient);
42     OD_LOG_INF("Connected to server successfully");
43 
44     mOutputReplayFilename = outputReplayFilename;
45 
46     mReplayOutputStream.open(mOutputReplayFilename, std::ios::out | std::ios::binary);
47     mGameClock.restart();
48     mSource = ODSource::network;
49     return true;
50 }
51 
replay(const std::string & filename)52 bool ODSocketClient::replay(const std::string& filename)
53 {
54     OD_LOG_INF("Reading replay from file " + filename);
55     mReplayInputStream.open(filename, std::ios::in | std::ios::binary);
56     mGameClock.restart();
57     mSource = ODSource::file;
58     return true;
59 }
60 
disconnect(bool keepReplay)61 void ODSocketClient::disconnect(bool keepReplay)
62 {
63     mPendingTimestamp = -1;
64     ODSource src = mSource;
65     mSource = ODSource::none;
66     switch(src)
67     {
68         case ODSource::none:
69         {
70             // Nothing to do
71             return;
72         }
73         case ODSource::network:
74         {
75             // Remove any remaining client sockets from the socket selector,
76             // if there is any left.
77             mSockSelector.clear();
78             mSockClient.disconnect();
79             break;
80         }
81         case ODSource::file:
82         {
83             mReplayInputStream.close();
84             return;
85         }
86         default:
87             assert(false);
88             break;
89     }
90 
91     mReplayOutputStream.close();
92     // Delete the replay newly created if asked to.
93     if (!keepReplay)
94         boost::filesystem::remove(mOutputReplayFilename);
95     mOutputReplayFilename.clear();
96 }
97 
isDataAvailable()98 bool ODSocketClient::isDataAvailable()
99 {
100     switch(mSource)
101     {
102         case ODSource::none:
103         {
104             return false;
105         }
106         case ODSource::network:
107         {
108             // There is only 1 socket in the selector so it should be ready if
109             // wait returns true but it doesn't hurt to return isReady...
110             if(!mSockSelector.wait(sf::milliseconds(5)))
111                 return false;
112             return mSockSelector.isReady(mSockClient);
113         }
114         case ODSource::file:
115         {
116             if(mReplayInputStream.eof())
117                 return false;
118 
119             if(mPendingTimestamp == -1)
120                 mPendingTimestamp = mPendingPacket.readPacket(mReplayInputStream);
121 
122             if(mPendingTimestamp < 0)
123                 return false;
124 
125             if(mPendingTimestamp < mGameClock.getElapsedTime().asMilliseconds())
126                 return true;
127 
128             return false;
129         }
130         default:
131             assert(false);
132             break;
133     }
134 
135     return false;
136 }
137 
send(ODPacket & s)138 ODSocketClient::ODComStatus ODSocketClient::send(ODPacket& s)
139 {
140     if(mSource != ODSource::network)
141         return ODComStatus::OK;
142 
143     sf::Socket::Status status = mSockClient.send(s.mPacket);
144     if (status == sf::Socket::Done)
145         return ODComStatus::OK;
146 
147     OD_LOG_ERR("Could not send data from client status="
148         + Helper::toString(status));
149     return ODComStatus::Error;
150 }
151 
recv(ODPacket & s)152 ODSocketClient::ODComStatus ODSocketClient::recv(ODPacket& s)
153 {
154     switch(mSource)
155     {
156         case ODSource::none:
157         {
158             // We should not try to send anything until connected
159             OD_LOG_ERR("Unexpected null server mode");
160             return ODComStatus::Error;
161         }
162         case ODSource::network:
163         {
164             sf::Socket::Status status = mSockClient.receive(s.mPacket);
165             if (status == sf::Socket::Done)
166             {
167                 s.writePacket(mGameClock.getElapsedTime().asMilliseconds(),
168                     mReplayOutputStream);
169                 return ODComStatus::OK;
170             }
171 
172             if((!mSockClient.isBlocking()) &&
173                     (status == sf::Socket::NotReady))
174             {
175                 return ODComStatus::NotReady;
176             }
177 
178             if(status == sf::Socket::Disconnected)
179             {
180                 OD_LOG_WRN("Socket disconnected");
181                 return ODComStatus::Error;
182             }
183             OD_LOG_ERR("Could not receive data from client status=" + Helper::toString(status));
184             return ODComStatus::Error;
185         }
186         case ODSource::file:
187         {
188             OD_ASSERT_TRUE(mPendingPacket != 0);
189             s = mPendingPacket;
190             mPendingTimestamp = -1;
191             return ODComStatus::OK;
192         }
193         default:
194             break;
195     }
196     return ODComStatus::Error;
197 }
198 
isConnected()199 bool ODSocketClient::isConnected()
200 {
201     return mSource != ODSource::none;
202 }
203 
processClientSocketMessages()204 void ODSocketClient::processClientSocketMessages()
205 {
206     // If we receive message for a new turn, after processing every message,
207     // we will refresh what is needed
208     // We loop until no more data is available
209     while(isConnected() && processOneClientSocketMessage());
210 }
211 
processOneClientSocketMessage()212 bool ODSocketClient::processOneClientSocketMessage()
213 {
214     if(!isDataAvailable())
215         return false;
216 
217     ODPacket packetReceived;
218 
219     // Check if data available
220     ODComStatus comStatus = recv(packetReceived);
221     if(comStatus != ODComStatus::OK)
222     {
223         playerDisconnected();
224         return false;
225     }
226 
227     ServerNotificationType serverCommand;
228     OD_ASSERT_TRUE(packetReceived >> serverCommand);
229 
230     return processMessage(serverCommand, packetReceived);
231 }
232