Example #1
0
static void testDBufferNetHandling (void)
{
	dbuffer* buf = new_dbuffer();
	NET_WriteByte(buf, 'b');
	CU_ASSERT_EQUAL(1, dbuffer_len(buf));
	NET_WriteShort(buf, 128);
	CU_ASSERT_EQUAL(3, dbuffer_len(buf));
	NET_WriteLong(buf, 128);
	CU_ASSERT_EQUAL(7, dbuffer_len(buf));
	free_dbuffer(buf);
}
Example #2
0
File: net.cpp Project: ufoai/ufoai
static void NET_ShowStreams_f (void)
{
	char buf[256];
	int cnt = 0;

	for (int i = 0; i < MAX_STREAMS; i++) {
		if (streams[i] == nullptr)
			continue;
		Com_Printf("Steam %i is opened: %s on socket %i (closed: %i, finished: %i, outbound: " UFO_SIZE_T ", inbound: " UFO_SIZE_T ")\n", i,
			NET_StreamPeerToName(streams[i], buf, sizeof(buf), true),
			streams[i]->socket, streams[i]->closed, streams[i]->finished,
			dbuffer_len(streams[i]->outbound), dbuffer_len(streams[i]->inbound));
		cnt++;
	}
	Com_Printf("%i/%i streams opened\n", cnt, MAX_STREAMS);
}
Example #3
0
/**
 * @sa NET_StreamFinished
 * @sa NET_StreamNew
 */
static void NET_StreamClose (struct net_stream* s)
{
	if (!s || s->closed)
		return;

	if (s->socket != INVALID_SOCKET) {
		if (dbuffer_len(s->outbound))
			Com_Printf("The outbound buffer for this socket (%d) is not empty\n", s->socket);
		else if (dbuffer_len(s->inbound))
			Com_Printf("The inbound buffer for this socket (%d) is not empty\n", s->socket);

		FD_CLR(s->socket, &read_fds);
		FD_CLR(s->socket, &write_fds);
		netCloseSocket(s->socket);
		s->socket = INVALID_SOCKET;
	}
	if (s->index >= 0)
		streams[s->index] = nullptr;

	if (s->loopback_peer) {
		/* Detach the peer, so that it won't send us anything more */
		s->loopback_peer->outbound = dbufferptr();
		s->loopback_peer->loopback_peer = nullptr;
	}

	s->closed = true;
	Com_DPrintf(DEBUG_SERVER, "Close stream at index: %i\n", s->index);

	s->outbound = dbufferptr();
	s->socket = INVALID_SOCKET;

	/* Note that this is potentially invalid after the callback returns */
	if (s->finished) {
		s->inbound = dbufferptr();
		if (s->onclose != nullptr)
			s->onclose();
		Mem_Free(s);
		s = nullptr;
	} else if (s->func) {
		s->func(s);
	}

	if (s != nullptr && s->onclose != nullptr)
		s->onclose();
}
Example #4
0
static void testDBufferBigData (void)
{
	int i;
	int count = 100;
	byte *data;
	/* this entity string may not contain any inline models, we don't have the bsp tree loaded here */
	const int size = FS_LoadFile("game/entity.txt", &data);
	dbuffer* buf = new_dbuffer();

	for (i = 0; i < count; i++) {
		dbuffer_add(buf, (char *)data, size);
	}

	CU_ASSERT_EQUAL(size * count, dbuffer_len(buf));
	free_dbuffer(buf);
	FS_FreeFile(data);
}
Example #5
0
/**
 * @brief Call NET_StreamFinished to mark the stream as uninteresting, but to
 * finish sending any data in the buffer. The stream will appear
 * closed after this call, and at some unspecified point in the future
 * s will become an invalid pointer, so it should not be further
 * referenced.
 */
void NET_StreamFinished (struct net_stream* s)
{
	if (!s)
		return;

	s->finished = true;

	if (s->socket != INVALID_SOCKET)
		FD_CLR(s->socket, &read_fds);

	/* Stop the loopback peer from queueing stuff up in here */
	if (s->loopback_peer)
		s->loopback_peer->outbound = dbufferptr();

	const ScopedMutex scopedMutex(netMutex);
	s->inbound = dbufferptr();

	/* If there's nothing in the outbound buffer, any finished stream is
	 * ready to be closed */
	if (dbuffer_len(s->outbound) == 0)
		NET_StreamClose(s);
}
Example #6
0
static struct net_stream* NET_DoConnect (const char* node, const char* service, const struct addrinfo* addr, int i, stream_onclose_func* onclose)
{
	struct net_stream* s;
	SOCKET sock = socket(addr->ai_family, addr->ai_socktype, addr->ai_protocol);
	if (sock == INVALID_SOCKET) {
		Com_Printf("Failed to create socket: %s\n", netStringError(netError));
		return nullptr;
	}

	if (!NET_SocketSetNonBlocking(sock)) {
		netCloseSocket(sock);
		return nullptr;
	}

	if (connect(sock, addr->ai_addr, addr->ai_addrlen) != 0) {
		const int err = netError;
#ifdef _WIN32
		if (err != WSAEWOULDBLOCK) {
#else
		if (err != EINPROGRESS) {
#endif
			Com_Printf("Failed to start connection to %s:%s: %s\n", node, service, netStringError(err));
			netCloseSocket(sock);
			return nullptr;
		}
	}

	s = NET_StreamNew(i);
	s->socket = sock;
	s->inbound = dbufferptr(new dbuffer(4096));
	s->outbound = dbufferptr(new dbuffer(4096));
	s->family = addr->ai_family;
	s->addrlen = addr->ai_addrlen;
	s->onclose = onclose;

	maxfd = std::max(sock + 1, maxfd);
	FD_SET(sock, &read_fds);

	return s;
}

/**
 * @brief Try to connect to a given host on a given port
 * @param[in] node The host to connect to
 * @param[in] service The port to connect to
 * @param[in] onclose The callback that is called on closing the returned stream. This is useful if
 * you hold the pointer for the returned stream anywhere else and would like to get notified once
 * this pointer is invalid.
 * @sa NET_DoConnect
 * @sa NET_ConnectToLoopBack
 * @todo What about a timeout
 */
struct net_stream* NET_Connect (const char* node, const char* service, stream_onclose_func* onclose)
{
	struct addrinfo* res;
	struct addrinfo hints;
	int rc;
	struct net_stream* s = nullptr;
	int index;

	OBJZERO(hints);
	hints.ai_flags = AI_ADDRCONFIG | AI_NUMERICSERV;
	hints.ai_socktype = SOCK_STREAM;
	/* force ipv4 */
	if (net_ipv4->integer)
		hints.ai_family = AF_INET;

	rc = getaddrinfo(node, service, &hints, &res);
	if (rc != 0) {
		Com_Printf("Failed to resolve host %s:%s: %s\n", node, service, gai_strerror(rc));
		return nullptr;
	}

	index = NET_StreamGetFree();
	if (index == -1) {
		Com_Printf("Failed to connect to host %s:%s, too many streams open\n", node, service);
		freeaddrinfo(res);
		return nullptr;
	}

	s = NET_DoConnect(node, service, res, index, onclose);

	freeaddrinfo(res);
	return s;
}

/**
 * @param[in] onclose The callback that is called on closing the returned stream. This is useful if
 * you hold the pointer for the returned stream anywhere else and would like to get notified once
 * this pointer is invalid.
 * @sa NET_Connect
 */
struct net_stream* NET_ConnectToLoopBack (stream_onclose_func* onclose)
{
	struct net_stream* client, *server;
	int server_index, client_index;

	if (!server_running)
		return nullptr;

	server_index = NET_StreamGetFree();
	client_index = NET_StreamGetFree();

	if (server_index == -1 || client_index == -1 || server_index == client_index) {
		Com_Printf("Failed to connect to loopback server, too many streams open\n");
		return nullptr;
	}

	client = NET_StreamNew(client_index);
	client->loopback = true;
	client->inbound = dbufferptr(new dbuffer(4096));
	client->outbound = dbufferptr(new dbuffer(4096));
	client->onclose = onclose;

	server = NET_StreamNew(server_index);
	server->loopback = true;
	server->inbound = client->outbound;
	server->outbound = client->inbound;
	server->func = server_func;
	server->onclose = nullptr;

	client->loopback_peer = server;
	server->loopback_peer = client;

	server_func(server);

	return client;
}

/**
 * @brief Enqueue a network message into a stream
 * @sa NET_StreamDequeue
 */
void NET_StreamEnqueue (struct net_stream* s, const char* data, int len)
{
	if (len <= 0 || !s || s->closed || s->finished)
		return;

	if (s->outbound) {
		const ScopedMutex scopedMutex(netMutex);
		s->outbound->add(data, len);
	}

	/* on linux, socket is int, and INVALID_SOCKET -1
	 * on windows it is unsigned and  INVALID_SOCKET (~0)
	 * Let's hope that checking for INVALID_SOCKET is good enough for linux. */
	//if (s->socket >= 0)
	if (s->socket != INVALID_SOCKET)
		FD_SET(s->socket, &write_fds);

	if (s->loopback_peer) {
		loopback_ready = true;
		s->loopback_peer->ready = true;
	}
}

/**
 * @brief Returns the length of the waiting inbound buffer
 */
static int NET_StreamPeek (struct net_stream* s, char* data, int len)
{
	if (len <= 0 || !s)
		return 0;

	dbufferptr& dbuf = s->inbound;
	if ((s->closed || s->finished) && dbuffer_len(dbuf) == 0)
		return 0;

	return dbuf->get(data, len);
}

/**
 * @sa NET_StreamEnqueue
 */
int NET_StreamDequeue (struct net_stream* s, char* data, int len)
{
	if (len <= 0 || !s || s->finished)
		return 0;

	return s->inbound->extract(data, len);
}

/**
 * @brief Reads messages from the network channel and adds them to the dbuffer
 * where you can use the NET_Read* functions to get the values in the correct
 * order
 * @sa NET_StreamDequeue
 */
dbuffer* NET_ReadMsg (struct net_stream* s)
{
	unsigned int v;
	const ScopedMutex scopedMutex(netMutex);

	if (NET_StreamPeek(s, (char*)&v, 4) < 4)
		return nullptr;

	int len = LittleLong(v);
	if (NET_StreamGetLength(s) < (4 + len))
		return nullptr;

	char tmp[256];
	const int size = sizeof(tmp);
	NET_StreamDequeue(s, tmp, 4);

	dbuffer* buf = new dbuffer();
	while (len > 0) {
		const int x = NET_StreamDequeue(s, tmp, std::min(len, size));
		buf->add(tmp, x);
		len -= x;
	}

	return buf;
}
Example #7
0
/**
 * @sa Qcommon_Frame
 */
void NET_Wait (int timeout)
{
	struct timeval tv;
	int ready;
	int i;

	fd_set read_fds_out;
	fd_set write_fds_out;

	memcpy(&read_fds_out, &read_fds, sizeof(read_fds_out));
	memcpy(&write_fds_out, &write_fds, sizeof(write_fds_out));

	/* select() won't notice that loopback streams are ready, so we'll
	 * eliminate the delay directly */
	if (loopback_ready)
		timeout = 0;

	tv.tv_sec = timeout / 1000;
	tv.tv_usec = 1000 * (timeout % 1000);
#ifdef _WIN32
	if (read_fds_out.fd_count == 0) {
		Sys_Sleep(timeout);
		ready = 0;
	} else
#endif
		ready = select(maxfd, &read_fds_out, &write_fds_out, nullptr, &tv);

	if (ready == -1) {
		Com_Printf("select failed: %s\n", netStringError(netError));
		return;
	}

	if (ready == 0 && !loopback_ready)
		return;

	if (server_socket != INVALID_SOCKET && FD_ISSET(server_socket, &read_fds_out)) {
		const SOCKET client_socket = accept(server_socket, nullptr, 0);
		if (client_socket == INVALID_SOCKET) {
			if (errno != EAGAIN)
				Com_Printf("accept on socket %d failed: %s\n", server_socket, netStringError(netError));
		} else
			do_accept(client_socket);
	}

	for (i = 0; i < MAX_STREAMS; i++) {
		struct net_stream* s = streams[i];

		if (!s)
			continue;

		if (s->loopback) {
			/* If the peer is gone and the buffer is empty, close the stream */
			if (!s->loopback_peer && NET_StreamGetLength(s) == 0) {
				NET_StreamClose(s);
			}
			/* Note that s is potentially invalid after the callback returns - we'll close dead streams on the next pass */
			else if (s->ready && s->func) {
				s->func(s);
			}

			continue;
		}

		if (s->socket == INVALID_SOCKET)
			continue;

		if (FD_ISSET(s->socket, &write_fds_out)) {
			char buf[4096];
			int len;

			if (dbuffer_len(s->outbound) == 0) {
				FD_CLR(s->socket, &write_fds);

				/* Finished streams are closed when their outbound queues empty */
				if (s->finished)
					NET_StreamClose(s);

				continue;
			}

			{
				const ScopedMutex scopedMutex(netMutex);
				len = s->outbound->get(buf, sizeof(buf));
				len = send(s->socket, buf, len, 0);

				s->outbound->remove(len);
			}

			if (len < 0) {
				Com_Printf("write on socket %d failed: %s\n", s->socket, netStringError(netError));
				NET_StreamClose(s);
				continue;
			}

			Com_DPrintf(DEBUG_SERVER, "wrote %d bytes to stream %d (%s)\n", len, i, NET_StreamPeerToName(s, buf, sizeof(buf), true));
		}

		if (FD_ISSET(s->socket, &read_fds_out)) {
			char buf[4096];
			const int len = recv(s->socket, buf, sizeof(buf), 0);
			if (len <= 0) {
				if (len == -1)
					Com_Printf("read on socket %d failed: %s\n", s->socket, netStringError(netError));
				NET_StreamClose(s);
				continue;
			} else {
				if (s->inbound) {
					SDL_LockMutex(netMutex);
					s->inbound->add(buf, len);
					SDL_UnlockMutex(netMutex);

					Com_DPrintf(DEBUG_SERVER, "read %d bytes from stream %d (%s)\n", len, i, NET_StreamPeerToName(s, buf, sizeof(buf), true));

					/* Note that s is potentially invalid after the callback returns */
					if (s->func)
						s->func(s);

					continue;
				}
			}
		}
	}

	for (i = 0; i < MAX_DATAGRAM_SOCKETS; i++) {
		struct datagram_socket* s = datagram_sockets[i];

		if (!s)
			continue;

		if (FD_ISSET(s->socket, &write_fds_out)) {
			if (s->queue) {
				struct datagram* dgram = s->queue;
				const int len = sendto(s->socket, dgram->msg, dgram->len, 0, (struct sockaddr* )dgram->addr, s->addrlen);
				if (len == -1)
					Com_Printf("sendto on socket %d failed: %s\n", s->socket, netStringError(netError));
				/* Regardless of whether it worked, we don't retry datagrams */
				s->queue = dgram->next;
				Mem_Free(dgram->msg);
				Mem_Free(dgram->addr);
				Mem_Free(dgram);
				if (!s->queue)
					s->queue_tail = &s->queue;
			} else {
				FD_CLR(s->socket, &write_fds);
			}
		}

		if (FD_ISSET(s->socket, &read_fds_out)) {
			char buf[256];
			char addrbuf[256];
			socklen_t addrlen = sizeof(addrbuf);
			const int len = recvfrom(s->socket, buf, sizeof(buf), 0, (struct sockaddr* )addrbuf, &addrlen);
			if (len == -1)
				Com_Printf("recvfrom on socket %d failed: %s\n", s->socket, netStringError(netError));
			else
				s->func(s, buf, len, (struct sockaddr* )addrbuf);
		}
	}

	loopback_ready = false;
}
Example #8
0
static inline int NET_StreamGetLength (struct net_stream* s)
{
	return s ? dbuffer_len(s->inbound) : 0;
}
Example #9
0
static void testDBuffer (void)
{
	int i;
	const int size = 10000000;
	dbuffer* buf = new_dbuffer();
	char data[128];
	size_t dataSize = sizeof(data);
	for (i = 0; i < size; i++)
		dbuffer_add(buf, "b", 1);
	CU_ASSERT_EQUAL(size, dbuffer_len(buf));

	CU_ASSERT_EQUAL(dataSize, dbuffer_get(buf, data, dataSize));
	CU_ASSERT_EQUAL(size, dbuffer_len(buf));

	CU_ASSERT_EQUAL(dataSize, dbuffer_extract(buf, data, dataSize));
	CU_ASSERT_EQUAL(size - dataSize, dbuffer_len(buf));

	free_dbuffer(buf);

	buf = new_dbuffer();
	dbuffer_add(buf, "b", 1);
	CU_ASSERT_EQUAL(1, dbuffer_len(buf));

	CU_ASSERT_EQUAL(1, dbuffer_get(buf, data, dataSize));
	CU_ASSERT_EQUAL(1, dbuffer_len(buf));

	CU_ASSERT_EQUAL(1, dbuffer_extract(buf, data, dataSize));
	CU_ASSERT_EQUAL(0, dbuffer_len(buf));

	buf = dbuffer_dup(buf);
	CU_ASSERT_EQUAL(0, dbuffer_len(buf));

	for (i = 0; i <= dataSize; i++)
		dbuffer_add(buf, "b", 1);
	CU_ASSERT_EQUAL(dataSize + 1, dbuffer_len(buf));

	CU_ASSERT_EQUAL(dataSize, dbuffer_extract(buf, data, dataSize));
	CU_ASSERT_EQUAL(1, dbuffer_len(buf));

	CU_ASSERT_EQUAL(1, dbuffer_remove(buf, 1));
	CU_ASSERT_EQUAL(0, dbuffer_len(buf));

	CU_ASSERT_EQUAL(0, dbuffer_remove(buf, 1));

	CU_ASSERT_EQUAL(0, dbuffer_get_at(buf, 1, data, dataSize));

	for (i = 0; i <= dataSize; i++)
		dbuffer_add(buf, "b", 1);
	CU_ASSERT_EQUAL(dataSize + 1, dbuffer_len(buf));

	CU_ASSERT_EQUAL(dataSize, dbuffer_get_at(buf, 1, data, dataSize));
	CU_ASSERT_EQUAL(dataSize + 1, dbuffer_len(buf));
}