1 /*****************************************************************************
2  * PokerTH - The open source texas holdem engine                             *
3  * Copyright (C) 2006-2012 Felix Hammer, Florian Thauer, Lothar May          *
4  *                                                                           *
5  * This program is free software: you can redistribute it and/or modify      *
6  * it under the terms of the GNU Affero General Public License as            *
7  * published by the Free Software Foundation, either version 3 of the        *
8  * License, or (at your option) any later version.                           *
9  *                                                                           *
10  * This program is distributed in the hope that it will be useful,           *
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of            *
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the             *
13  * GNU Affero General Public License for more details.                       *
14  *                                                                           *
15  * You should have received a copy of the GNU Affero General Public License  *
16  * along with this program.  If not, see <http://www.gnu.org/licenses/>.     *
17  *                                                                           *
18  *                                                                           *
19  * Additional permission under GNU AGPL version 3 section 7                  *
20  *                                                                           *
21  * If you modify this program, or any covered work, by linking or            *
22  * combining it with the OpenSSL project's OpenSSL library (or a             *
23  * modified version of that library), containing parts covered by the        *
24  * terms of the OpenSSL or SSLeay licenses, the authors of PokerTH           *
25  * (Felix Hammer, Florian Thauer, Lothar May) grant you additional           *
26  * permission to convey the resulting work.                                  *
27  * Corresponding Source for a non-source form of such a combination          *
28  * shall include the source code for the parts of OpenSSL used as well       *
29  * as that of the covered work.                                              *
30  *****************************************************************************/
31 
32 // Load test program for PokerTH
33 
34 #include <third_party/asn1/PokerTHMessage.h>
35 #include <boost/program_options.hpp>
36 #include <boost/asio.hpp>
37 #include <boost/thread.hpp>
38 #include <gsasl.h>
39 
40 #include <iostream>
41 #include <sstream>
42 
43 #define STL_STRING_FROM_OCTET_STRING(_a) (string((const char *)(_a).buf, (_a).size))
44 
45 using namespace std;
46 using boost::asio::ip::tcp;
47 namespace po = boost::program_options;
48 
49 
50 #define BUF_SIZE 1024
51 
52 struct NetSession {
NetSessionNetSession53 	NetSession(boost::asio::io_service &ioService) : recBufPos(0), authSession(NULL), socket(ioService) {}
54 	boost::array<char, BUF_SIZE> recBuf;
55 	size_t recBufPos;
56 	boost::array<char, BUF_SIZE> sendBuf;
57 	Gsasl_session *authSession;
58 	tcp::socket socket;
59 	string name;
60 };
61 
62 static int
net_packet_print_to_string(const void * buffer,size_t size,void * packetStr)63 net_packet_print_to_string(const void *buffer, size_t size, void *packetStr)
64 {
65 	string *tmpString = (string *)packetStr;
66 	*tmpString += string((const char *)buffer, size);
67 	return 0;
68 }
69 
70 /*string packetString;
71 xer_encode(&asn_DEF_PokerTHMessage, msg, XER_F_BASIC, &net_packet_print_to_string, &packetString);
72 cout << packetString << endl;*/
73 
74 PokerTHMessage_t *
receiveMessage(NetSession * session)75 receiveMessage(NetSession *session)
76 {
77 	PokerTHMessage_t *msg = NULL;
78 	do {
79 		asn_dec_rval_t retVal = ber_decode(0, &asn_DEF_PokerTHMessage, (void **)&msg, session->recBuf.data(), session->recBufPos);
80 		if(retVal.code == RC_OK && msg != NULL) {
81 			if (retVal.consumed < session->recBufPos) {
82 				session->recBufPos -= retVal.consumed;
83 				memmove(session->recBuf.c_array(), session->recBuf.c_array() + retVal.consumed, session->recBufPos);
84 			} else {
85 				session->recBufPos = 0;
86 			}
87 			if (asn_check_constraints(&asn_DEF_PokerTHMessage, msg, NULL, NULL) != 0) {
88 				cerr << "Invalid packet received:" << endl;
89 				string packetString;
90 				xer_encode(&asn_DEF_PokerTHMessage, msg, XER_F_BASIC, &net_packet_print_to_string, &packetString);
91 				cout << packetString << endl;
92 			}
93 		} else {
94 			// Free the partially decoded message (if applicable).
95 			ASN_STRUCT_FREE(asn_DEF_PokerTHMessage, msg);
96 			msg = NULL;
97 			session->recBufPos += session->socket.receive(boost::asio::buffer(session->recBuf.c_array() + session->recBufPos, BUF_SIZE - session->recBufPos));
98 		}
99 	} while (msg == NULL);
100 	return msg;
101 }
102 
103 bool
sendMessage(NetSession * session,PokerTHMessage_t * msg)104 sendMessage(NetSession *session, PokerTHMessage_t *msg)
105 {
106 	bool retVal = false;
107 	if (msg) {
108 		asn_enc_rval_t e = der_encode_to_buffer(&asn_DEF_PokerTHMessage, msg, session->sendBuf.data(), BUF_SIZE);
109 		if (e.encoded != -1) {
110 			session->socket.send(boost::asio::buffer(session->sendBuf.data(), e.encoded));
111 			retVal = true;
112 		}
113 		ASN_STRUCT_FREE(asn_DEF_PokerTHMessage, msg);
114 	}
115 	return retVal;
116 }
117 
118 int
main(int argc,char * argv[])119 main(int argc, char *argv[])
120 {
121 	try {
122 		// Check command line options.
123 		po::options_description desc("Allowed options");
124 		desc.add_options()
125 		("help,h", "produce help message")
126 		("server,s", po::value<string>(), "PokerTH server name")
127 		("port,P", po::value<string>(), "PokerTH server port")
128 		("numGames,n", po::value<unsigned>(), "Number of games to open")
129 		("firstId,f", po::value<int>(), "First id of username testx")
130 		;
131 
132 		po::variables_map vm;
133 		po::store(po::parse_command_line(argc, argv, desc), vm);
134 		po::notify(vm);
135 
136 		if (vm.count("help")) {
137 			cout << desc << endl;
138 			return 1;
139 		}
140 		if (!vm.count("server") || !vm.count("port") || !vm.count("numGames") || !vm.count("firstId")) {
141 			cout << "Missing option!" << endl << desc << endl;
142 			return 1;
143 		}
144 
145 		string server(vm["server"].as<string>());
146 		string port = vm["port"].as<string>();
147 		unsigned numGames = vm["numGames"].as<unsigned>();
148 		int firstId = vm["firstId"].as<int>();
149 
150 		// Initialise gsasl.
151 		Gsasl *authContext;
152 		int res = gsasl_init(&authContext);
153 		if (res != GSASL_OK) {
154 			cout << "gsasl init failed" << endl;
155 			return 1;
156 		}
157 
158 		if (!gsasl_client_support_p(authContext, "SCRAM-SHA-1")) {
159 			gsasl_done(authContext);
160 			cout << "This version of gsasl does not support SCRAM-SHA-1" << endl;
161 			return 1;
162 		}
163 
164 		// Connect to the PokerTH server.
165 		boost::asio::io_service ioService;
166 		tcp::resolver resolver(ioService);
167 		tcp::resolver::query query(server, port);
168 		tcp::resolver::iterator endpoint_iterator = resolver.resolve(query);
169 		tcp::resolver::iterator end;
170 		boost::system::error_code error = boost::asio::error::host_not_found;
171 		tcp::resolver::iterator curEndpoint;
172 		tcp::socket tmpSocket(ioService);
173 		while (error && endpoint_iterator != end) {
174 			curEndpoint = endpoint_iterator++;
175 			tmpSocket.connect(*curEndpoint, error);
176 			tmpSocket.close();
177 		}
178 
179 		if (error) {
180 			cout << "Connect failed" << endl;
181 			return 1;
182 		}
183 
184 		PokerTHMessage_t *msg = NULL;
185 		int errorCode;
186 		char *tmpOut;
187 		size_t tmpOutSize;
188 		string nextGsaslMsg;
189 		NetSession **sessionArray = new NetSession *[numGames * 10];
190 		unsigned *gameId = new unsigned[numGames];
191 		const int LoginsPerLoop = 50;
192 		for (int t = 0; t < (numGames * 10) / LoginsPerLoop + 1; t++) {
193 			int startNum = t * LoginsPerLoop;
194 			int endNum = (t + 1) * LoginsPerLoop;
195 			if (endNum > numGames * 10) {
196 				endNum = numGames * 10;
197 			}
198 			for (int i = startNum; i < endNum; i++) {
199 				sessionArray[i] = new NetSession(ioService);
200 				NetSession *session = sessionArray[i];
201 
202 				session->socket.connect(*curEndpoint, error);
203 
204 				// Receive server information
205 				msg = receiveMessage(session);
206 				if (!msg || msg->present != PokerTHMessage_PR_announceMessage) {
207 					cout << "Announce failed" << endl;
208 					return 1;
209 				}
210 				ASN_STRUCT_FREE(asn_DEF_PokerTHMessage, msg);
211 
212 				// Send init
213 				msg = (PokerTHMessage_t *)calloc(1, sizeof(PokerTHMessage_t));
214 				msg->present = PokerTHMessage_PR_initMessage;
215 				InitMessage_t *netInit = &msg->choice.initMessage;
216 				netInit->requestedVersion.major = 2;
217 				netInit->requestedVersion.minor = 0;
218 				errorCode = gsasl_client_start(authContext, "SCRAM-SHA-1", &session->authSession);
219 				if (errorCode != GSASL_OK) {
220 					cout << "Auth start error." << endl;
221 					return 1;
222 				}
223 				ostringstream param;
224 				param << "test" << i + firstId;
225 				cout << "User " << param.str() << " logging in." << endl;
226 				session->name = param.str();
227 				gsasl_property_set(session->authSession, GSASL_AUTHID, session->name.c_str());
228 				gsasl_property_set(session->authSession, GSASL_PASSWORD, session->name.c_str());
229 
230 				netInit->login.present = login_PR_authenticatedLogin;
231 				AuthenticatedLogin_t *authLogin = &netInit->login.choice.authenticatedLogin;
232 
233 				errorCode = gsasl_step(session->authSession, NULL, 0, &tmpOut, &tmpOutSize);
234 				if (errorCode == GSASL_NEEDS_MORE) {
235 					nextGsaslMsg = string(tmpOut, tmpOutSize);
236 				} else {
237 					cout << "gsasl step 1 failed" << endl;
238 					return 1;
239 				}
240 				gsasl_free(tmpOut);
241 
242 				OCTET_STRING_fromBuf(&authLogin->clientUserData,
243 									 nextGsaslMsg.c_str(),
244 									 nextGsaslMsg.length());
245 				if (!sendMessage(session, msg)) {
246 					cout << "Init auth request failed" << endl;
247 					return 1;
248 				}
249 			}
250 
251 			for (int i = startNum; i < endNum; i++) {
252 				NetSession *session = sessionArray[i];
253 				msg = receiveMessage(session);
254 				if (!msg || msg->present != PokerTHMessage_PR_authMessage) {
255 					cout << "Auth request failed" << endl;
256 					return 1;
257 				}
258 
259 				AuthMessage_t *netAuth = &msg->choice.authMessage;
260 				AuthServerChallenge_t *netChallenge = &netAuth->choice.authServerChallenge;
261 				string challengeStr = STL_STRING_FROM_OCTET_STRING(netChallenge->serverChallenge);
262 				errorCode = gsasl_step(session->authSession, challengeStr.c_str(), challengeStr.size(), &tmpOut, &tmpOutSize);
263 				if (errorCode == GSASL_NEEDS_MORE) {
264 					nextGsaslMsg = string(tmpOut, tmpOutSize);
265 				} else {
266 					cout << "gsasl step 2 failed" << endl;
267 					return 1;
268 				}
269 				gsasl_free(tmpOut);
270 				ASN_STRUCT_FREE(asn_DEF_PokerTHMessage, msg);
271 				msg = (PokerTHMessage_t *)calloc(1, sizeof(PokerTHMessage_t));
272 				msg->present = PokerTHMessage_PR_authMessage;
273 				AuthMessage_t *outAuth = &msg->choice.authMessage;
274 				outAuth->present = AuthMessage_PR_authClientResponse;
275 				AuthClientResponse_t *outResponse = &outAuth->choice.authClientResponse;
276 
277 				OCTET_STRING_fromBuf(&outResponse->clientResponse,
278 									 nextGsaslMsg.c_str(),
279 									 nextGsaslMsg.length());
280 				if (!sendMessage(session, msg)) {
281 					cout << "Init auth response failed" << endl;
282 					return 1;
283 				}
284 			}
285 
286 			for (int i = startNum; i < endNum; i++) {
287 				NetSession *session = sessionArray[i];
288 				msg = receiveMessage(session);
289 				if (!msg || msg->present != PokerTHMessage_PR_authMessage) {
290 					cout << "Auth response failed" << endl;
291 					return 1;
292 				}
293 				ASN_STRUCT_FREE(asn_DEF_PokerTHMessage, msg);
294 
295 				// Receive init ack
296 				msg = receiveMessage(session);
297 				if (!msg || msg->present != PokerTHMessage_PR_initAckMessage) {
298 					cout << "Init ack failed" << endl;
299 					return 1;
300 				}
301 				ASN_STRUCT_FREE(asn_DEF_PokerTHMessage, msg);
302 
303 				for (int j = i; j >= 0; j--) {
304 					size_t bytes_readable = sessionArray[j]->socket.available();
305 					while (bytes_readable > 0) {
306 						msg = receiveMessage(sessionArray[j]);
307 						ASN_STRUCT_FREE(asn_DEF_PokerTHMessage, msg);
308 						bytes_readable = sessionArray[j]->socket.available();
309 					}
310 				}
311 			}
312 		}
313 
314 		for (int g = 0; g < numGames; g++) {
315 			NetSession *session = sessionArray[g * 10];
316 			// Send create game
317 			cout << "Player " << session->name << " creating game " << g+1 << endl;
318 			msg = (PokerTHMessage_t *)calloc(1, sizeof(PokerTHMessage_t));
319 			msg->present = PokerTHMessage_PR_joinGameRequestMessage;
320 			JoinGameRequestMessage_t *netJoinGame = &msg->choice.joinGameRequestMessage;
321 			string tmpGamePassword("blah123");
322 			netJoinGame->password = OCTET_STRING_new_fromBuf(
323 										&asn_DEF_UTF8String,
324 										tmpGamePassword.c_str(),
325 										tmpGamePassword.length());
326 			netJoinGame->joinGameAction.present = joinGameAction_PR_joinNewGame;
327 			JoinNewGame_t *joinNew = &netJoinGame->joinGameAction.choice.joinNewGame;
328 			string tmpGameName("_loadtest_do_not_join_" + session->name);
329 			joinNew->gameInfo.netGameType		= netGameType_normalGame;
330 			joinNew->gameInfo.maxNumPlayers		= 10;
331 			joinNew->gameInfo.raiseIntervalMode.present	= raiseIntervalMode_PR_raiseEveryHands;
332 			joinNew->gameInfo.raiseIntervalMode.choice.raiseEveryHands = 1;
333 			joinNew->gameInfo.endRaiseMode		= endRaiseMode_keepLastBlind;
334 			joinNew->gameInfo.proposedGuiSpeed			= 5;
335 			joinNew->gameInfo.delayBetweenHands			= 5;
336 			joinNew->gameInfo.playerActionTimeout		= 5;
337 			joinNew->gameInfo.endRaiseSmallBlindValue	= 0;
338 			joinNew->gameInfo.firstSmallBlind			= 200;
339 			joinNew->gameInfo.startMoney				= 10000;
340 			OCTET_STRING_fromBuf(&joinNew->gameInfo.gameName,
341 								 tmpGameName.c_str(),
342 								 tmpGameName.length());
343 			if (!sendMessage(session, msg)) {
344 				cout << "Create game failed" << endl;
345 				return 1;
346 			}
347 			msg = NULL;
348 			// Receive join game ack
349 			do {
350 				ASN_STRUCT_FREE(asn_DEF_PokerTHMessage, msg);
351 				msg = receiveMessage(session);
352 				if (!msg) {
353 					cout << "Receive in lobby failed" << endl;
354 					return 1;
355 				}
356 				if (msg->present == PokerTHMessage_PR_errorMessage) {
357 					cout << "Received error" << endl;
358 					return 1;
359 				}
360 			} while (msg->present != PokerTHMessage_PR_joinGameReplyMessage);
361 			if (msg->choice.joinGameReplyMessage.joinGameResult.present != joinGameResult_PR_joinGameAck) {
362 				cout << "Join game ack failed" << endl;
363 				return 1;
364 			}
365 			gameId[g] = msg->choice.joinGameReplyMessage.gameId;
366 			ASN_STRUCT_FREE(asn_DEF_PokerTHMessage, msg);
367 		}
368 
369 		for (int t = 0; t < (numGames * 10) / LoginsPerLoop + 1; t++) {
370 			int startNum = t * LoginsPerLoop;
371 			int endNum = (t + 1) * LoginsPerLoop;
372 			if (endNum > numGames * 10) {
373 				endNum = numGames * 10;
374 			}
375 			for (int i = startNum; i < endNum; i++) {
376 				if (i % 10 == 0) {
377 					continue;
378 				}
379 				NetSession *session = sessionArray[i];
380 
381 				cout << "Player " << session->name << " joining game " << (i / 10)+1 << endl;
382 				msg = (PokerTHMessage_t *)calloc(1, sizeof(PokerTHMessage_t));
383 				msg->present = PokerTHMessage_PR_joinGameRequestMessage;
384 				JoinGameRequestMessage_t *netJoinGame = &msg->choice.joinGameRequestMessage;
385 				string tmpGamePassword("blah123");
386 				netJoinGame->password = OCTET_STRING_new_fromBuf(
387 											&asn_DEF_UTF8String,
388 											tmpGamePassword.c_str(),
389 											tmpGamePassword.length());
390 				netJoinGame->joinGameAction.present = joinGameAction_PR_joinExistingGame;
391 				JoinExistingGame_t *joinExisting = &netJoinGame->joinGameAction.choice.joinExistingGame;
392 				joinExisting->gameId = gameId[i / 10];
393 				if (!sendMessage(session, msg)) {
394 					cout << "Join game failed" << endl;
395 					return 1;
396 				}
397 				msg = NULL;
398 			}
399 			for (int i = startNum; i < endNum; i++) {
400 				if (i % 10 == 0) {
401 					continue;
402 				}
403 				NetSession *session = sessionArray[i];
404 				// Receive join game ack
405 				do {
406 					ASN_STRUCT_FREE(asn_DEF_PokerTHMessage, msg);
407 					msg = receiveMessage(session);
408 					if (!msg) {
409 						cout << "Receive in lobby failed" << endl;
410 						return 1;
411 					}
412 					if (msg->present == PokerTHMessage_PR_errorMessage) {
413 						cout << "Received error" << endl;
414 						return 1;
415 					}
416 				} while (msg->present != PokerTHMessage_PR_joinGameReplyMessage);
417 				if (msg->choice.joinGameReplyMessage.joinGameResult.present != joinGameResult_PR_joinGameAck) {
418 					cout << "Join game ack failed" << endl;
419 					return 1;
420 				}
421 				ASN_STRUCT_FREE(asn_DEF_PokerTHMessage, msg);
422 				msg = NULL;
423 			}
424 		}
425 		bool terminated = false;
426 		while (!terminated) {
427 			for (int i = 0; i < numGames * 10; i++) {
428 				NetSession *session = sessionArray[i];
429 
430 				size_t bytes_readable = session->socket.available();
431 				while (bytes_readable > 0) {
432 					msg = receiveMessage(session);
433 					if (msg->present == PokerTHMessage_PR_endOfGameMessage) {
434 						cout << "One game was ended." << endl;
435 						terminated = true;
436 					}
437 					ASN_STRUCT_FREE(asn_DEF_PokerTHMessage, msg);
438 					bytes_readable = session->socket.available();
439 				}
440 			}
441 			boost::this_thread::sleep(boost::posix_time::milliseconds(100));
442 		}
443 		gsasl_done(authContext);
444 
445 	} catch (const exception &e) {
446 		cout << "Exception caught " << e.what() << endl;
447 		return 1;
448 	}
449 
450 	return 0;
451 }
452 
453