/*
    PIP - Platform Independent Primitives
    Ethernet, UDP/TCP Broadcast/Multicast
    Ivan Pelipenko peri4ko@yandex.ru, Andrey Bychkov work.a.b@yandex.ru

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU Lesser General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program 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.  See the
    GNU Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/
#include "piethernet.h"

#include "piconfig.h"
#include "piconstchars.h"
#include "piincludes_p.h"
#include "piliterals.h"
#include "pipropertystorage.h"
#include "pisysteminfo.h"
#include "pitranslator.h"
// clang-format off
#ifdef QNX
#  include <arpa/inet.h>
#  include <fcntl.h>
#  include <hw/nicinfo.h>
#  include <ifaddrs.h>
#  include <net/if.h>
#  include <net/if_dl.h>
#  include <netdb.h>
#  include <netinet/in.h>
#  include <sys/ioctl.h>
#  include <sys/socket.h>
#  include <sys/time.h>
#  include <sys/types.h>
#  ifdef BLACKBERRY
#	include <netinet/in.h>
#  else
#	include <sys/dcmd_io-net.h>
#  endif
#  define ip_mreqn    ip_mreq
#  define imr_address imr_interface
#else
#  ifdef WINDOWS
#    include <io.h>
#    include <winsock2.h>
#    include <iphlpapi.h>
#    include <psapi.h>
#    include <ws2tcpip.h>
#    define ip_mreqn ip_mreq
#    define imr_address imr_interface
#  else
#    include <fcntl.h>
#    include <sys/ioctl.h>
#    include <netinet/in.h>
#    include <netinet/tcp.h>
#    include <arpa/inet.h>
#    include <sys/socket.h>
#    include <netdb.h>
#    include <net/if.h>
#    if !defined(ANDROID) && !defined(LWIP)
#      include <ifaddrs.h>
#    endif
#    ifdef LWIP
#      include <lwip/sockets.h>
#    endif
#  endif
#endif
// clang-format on
#include "piwaitevent_p.h"

#include <errno.h>


/** \class PIEthernet piethernet.h
 * \brief
 * Ethernet device
 *
 * \details
 * \section PIEthernet_sec0 Synopsis
 * %PIEthernet designed to work with IPv4 network via two protocols:
 * UDP and TCP. This class allow you send and receive packets to/from
 * another computer through network. Also it supports broadcast and
 * multicast extensions.
 *
 * \section PIEthernet_sec1 IPv4
 *
 *
 * \section PIEthernet_sec2 UDP
 * User Datagram Protocol
 *
 * \section PIEthernet_sec3 TCP
 * Transmission Control Protocol
 *
 * */

#ifndef WINDOWS
PIString getSockAddr(sockaddr * s) {
	if (!s) return PIString();
	char buf[INET_ADDRSTRLEN];
	piZeroMemory(buf, sizeof(buf));
	const char * r = inet_ntop(AF_INET, &((sockaddr_in *)s)->sin_addr, buf, sizeof(buf));
	return r ? PIStringAscii(r) : PIString();
}
#endif


REGISTER_DEVICE(PIEthernet)


PRIVATE_DEFINITION_START(PIEthernet)
	sockaddr_in addr_;
	sockaddr_in saddr_;
	sockaddr_in raddr_;
	PIWaitEvent event;
PRIVATE_DEFINITION_END(PIEthernet)


PIEthernet::PIEthernet(): PIIODevice("", ReadWrite) {
	construct();
	eth_type = UDP;
	setProperty("type", (int)UDP);
	setParameters(PIEthernet::ReuseAddress | PIEthernet::MulticastLoop | PIEthernet::KeepConnection);
}


PIEthernet::PIEthernet(PIEthernet::Type type_, const PIString & ip_port, const PIFlags<PIEthernet::Parameters> params_)
	: PIIODevice(ip_port, ReadWrite) {
	construct();
	addr_r.set(ip_port);
	eth_type = type_;
	setProperty("type", (int)type_);
	setParameters(params_);
	if (type_ != UDP) init();
}


PIEthernet::PIEthernet(int sock_, PIString ip_port): PIIODevice("", ReadWrite) {
	construct();
	addr_s.set(ip_port);
	sock    = sock_;
	opened_ = connected_ = true;
	is_server_client     = true;
	eth_type             = TCP_Client;
	setProperty("type", (int)TCP_Client);
	init();
	setParameters(PIEthernet::ReuseAddress | PIEthernet::MulticastLoop);
	setPath(ip_port);
	ethNonblocking(sock);
	PRIVATE->event.create();
	// piCoutObj << "new tcp client" << sock_;
}


PIEthernet::~PIEthernet() {
	// piCout << "~PIEthernet";
	stopAndWait();
	close();
	PRIVATE->event.destroy();
	// piCout << "~PIEthernet done";
}


void PIEthernet::setReadTimeout(PISystemTime tm) {
	setProperty("readTimeout", tm);
	applyTimeouts();
}


void PIEthernet::setWriteTimeout(PISystemTime tm) {
	setProperty("writeTimeout", tm);
	applyTimeouts();
}


void PIEthernet::setReadBufferSize(int bytes) {
	rcv_buf = bytes;
	applyBuffers();
}


void PIEthernet::setWriteBufferSize(int bytes) {
	snd_buf = bytes;
	applyBuffers();
}


void PIEthernet::construct() {
	// piCout << " PIEthernet" << uint(this);
	setOption(BlockingWrite);
	setReadTimeout(10_s);
	setWriteTimeout(10_s);
	setTTL(64);
	setMulticastTTL(1);
	server_thread_.setData(this);
	server_thread_.setName("_S.tcpserver"_a);
#ifdef MICRO_PIP
	setThreadedReadBufferSize(512);
#else
	setThreadedReadBufferSize(64_KiB);
#endif
	// setPriority(piHigh);
}


void PIEthernet::init() {
	if (isOpened() || is_server_client) return;
	if (sock != -1) return;
	// piCout << "init " << type();
	PRIVATE->event.destroy();
	if (sock_s == sock) sock_s = -1;
	closeSocket(sock);
	closeSocket(sock_s);
	int st = 0, pr = 0;
	if (type() == UDP) {
		st = SOCK_DGRAM;
		pr = IPPROTO_UDP;
	} else {
		st = SOCK_STREAM;
		pr = IPPROTO_TCP;
	}
	sock = ::socket(AF_INET, st, pr);
	ethNonblocking(sock);
	PRIVATE->event.create();
	if (params[SeparateSockets])
		sock_s = ::socket(AF_INET, st, pr);
	else
		sock_s = sock;
	if (sock == -1 || sock_s == -1) {
		piCoutObj << "Can`t create socket," << ethErrorString();
		connected_ = connecting_ = opened_ = false;
		return;
	}
	applyParameters();
	applyTimeouts();
	applyBuffers();
	applyOptInt(IPPROTO_IP, IP_TTL, TTL());
	// piCoutObj << "inited" << path();
}


PIString PIEthernet::macFromBytes(const PIByteArray & mac) {
	PIString r;
	for (int i = 0; i < mac.size_s(); ++i) {
		r += PIString::fromNumber(mac[i], 16).expandLeftTo(2, '0');
		if (i < mac.size_s() - 1) r += ":";
	}
	return r;
}


PIByteArray PIEthernet::macToBytes(const PIString & mac) {
	PIByteArray r;
	PIStringList sl = mac.split(PIStringAscii(":"));
	for (const auto & i: sl)
		r << uchar(i.toInt(16));
	return r;
}


PIString PIEthernet::applyMask(const PIString & ip, const PIString & mask) {
	struct in_addr ia;
	ia.s_addr = inet_addr(ip.dataAscii()) & inet_addr(mask.dataAscii());
	return PIStringAscii(inet_ntoa(ia));
}


PINetworkAddress PIEthernet::applyMask(const PINetworkAddress & ip, const PINetworkAddress & mask) {
	PINetworkAddress ret(ip);
	ret.ip_ &= mask.ip_;
	return ret;
}


PIString PIEthernet::getBroadcast(const PIString & ip, const PIString & mask) {
	struct in_addr ia;
	ia.s_addr = inet_addr(ip.dataAscii()) | ~inet_addr(mask.dataAscii());
	return PIStringAscii(inet_ntoa(ia));
}


PINetworkAddress PIEthernet::getBroadcast(const PINetworkAddress & ip, const PINetworkAddress & mask) {
	PINetworkAddress ret(ip);
	ret.ip_ |= ~mask.ip_;
	return ret;
}


bool PIEthernet::openDevice() {
	if (connected_) return true;
	// piCoutObj << "open";
	init();
	if (sock == -1 || path().isEmpty()) return false;
	addr_r.set(path());
	// if (type() == TCP_Client)
	//	connecting_ = true;
	if (type() != UDP || mode() == PIIODevice::WriteOnly) return true;
	piZeroMemory(PRIVATE->addr_);
	PRIVATE->addr_.sin_family = AF_INET;
	PRIVATE->addr_.sin_port   = htons(addr_r.port());
	if (params[PIEthernet::Broadcast])
		PRIVATE->addr_.sin_addr.s_addr = INADDR_ANY;
	else
		PRIVATE->addr_.sin_addr.s_addr = addr_r.ip();
#ifdef QNX
	PRIVATE->addr_.sin_len = sizeof(PRIVATE->addr_);
#endif
	// piCout << "bind to" << (params[PIEthernet::Broadcast] ? "255.255.255.255" : ip_) << ":" << port_ << " ...";
	int tries = 0;
	while ((bind(sock, (sockaddr *)&PRIVATE->addr_, sizeof(PRIVATE->addr_)) == -1) && (tries < 2)) {
		init();
		tries++;
	}
	if (tries == 2) {
		piCoutObj << "Can`t bind to" << addr_r << "," << ethErrorString();
		return false;
	}
	opened_ = true;
	while (!mcast_queue.isEmpty())
		joinMulticastGroup(mcast_queue.dequeue());
	applyTimeouts();
	applyBuffers();
	applyOptInt(IPPROTO_IP, IP_TTL, TTL());
	addr_lr.clear();
	return true;
}


bool PIEthernet::closeDevice() {
	// piCoutObj << "close";
	bool ned   = connected_;
	connected_ = connecting_ = false;
	stopThreadedListen();
	clients_mutex.lock();
	auto cl = clients_;
	clients_.clear();
	clients_mutex.unlock();
	piDeleteAll(cl);
	if (ned) {
		// piCoutObj << "Disconnect on close";
		disconnected(false);
	}
	return true;
}


void PIEthernet::closeSocket(int & sd) {
	if (sd != -1) ethClosesocket(sd, type() != PIEthernet::UDP);
	sd = -1;
}


void PIEthernet::applyTimeouts() {
	if (sock < 0) return;
	PISystemTime rtm = readTimeout(), wtm = writeTimeout();
	applyTimeout(sock, SO_RCVTIMEO, rtm);
	applyTimeout(sock, SO_SNDTIMEO, wtm);
	if (sock_s != sock && sock_s != -1) {
		applyTimeout(sock_s, SO_RCVTIMEO, rtm);
		applyTimeout(sock_s, SO_SNDTIMEO, wtm);
	}
}


void PIEthernet::applyBuffers() {
	if (sock < 0) return;
	if (rcv_buf > 0) ethSetsockoptInt(sock, SOL_SOCKET, SO_RCVBUF, rcv_buf);
	if (snd_buf > 0) {
		if (sock_s != sock) {
			ethSetsockoptInt(sock_s, SOL_SOCKET, SO_SNDBUF, snd_buf);
		} else {
			ethSetsockoptInt(sock, SOL_SOCKET, SO_SNDBUF, snd_buf);
		}
	}
}


void PIEthernet::applyTimeout(int fd, int opt, PISystemTime tm) {
	if (fd == 0) return;
		// piCoutObj << "setReadIsBlocking" << yes;
#ifdef WINDOWS
	DWORD _tm = tm.toMilliseconds();
#else
	timeval _tm;
	_tm.tv_sec  = tm.seconds;
	_tm.tv_usec = tm.nanoseconds / 1000;
#endif
	ethSetsockopt(fd, SOL_SOCKET, opt, &_tm, sizeof(_tm));
}


void PIEthernet::applyOptInt(int level, int opt, int val) {
	if (sock != -1) ethSetsockoptInt(sock, level, opt, val);
	if (sock_s != sock && sock_s != -1) ethSetsockoptInt(sock_s, level, opt, val);
}


bool PIEthernet::joinMulticastGroup(const PIString & group) {
	if (sock == -1) init();
	if (sock == -1) return false;
	if (type() != UDP) {
		piCoutObj << "Only UDP sockets can join multicast groups";
		return false;
	}
	if (isClosed()) {
		if (mcast_queue.contains(group)) return false;
		mcast_queue.enqueue(group);
		if (!mcast_groups.contains(group)) mcast_groups << group;
		return true;
	}
	addr_r.set(path());
#ifndef LWIP
	struct ip_mreqn mreq;
#else
	struct ip_mreq mreq;
#endif
	piZeroMemory(mreq);
#ifdef LINUX
	// mreq.imr_address.s_addr = INADDR_ANY;
	/*PIEthernet::InterfaceList il = interfaces();
	const PIEthernet::Interface * ci = il.getByAddress(addr_r.ipString());
	if (ci != 0) mreq.imr_ifindex = ci->index;*/
#endif
	if (params[PIEthernet::Broadcast])
#ifndef LWIP
		mreq.imr_address.s_addr = INADDR_ANY;
#else
		mreq.imr_interface.s_addr = INADDR_ANY;
#endif
	else
#ifndef LWIP
		mreq.imr_address.s_addr = addr_r.ip();
#else
		mreq.imr_interface.s_addr = addr_r.ip();
#endif

	// piCout << "join group" << group << "ip" << ip_ << "with index" << mreq.imr_ifindex << "socket" << sock;
	mreq.imr_multiaddr.s_addr = inet_addr(group.dataAscii());
	if (ethSetsockopt(sock, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq)) != 0) {
		piCoutObj << "Can`t join multicast group" << group << "," << ethErrorString();
		return false;
	}
	if (params[PIEthernet::MulticastLoop]) ethSetsockoptInt(sock, IPPROTO_IP, IP_MULTICAST_LOOP);
	applyOptInt(IPPROTO_IP, IP_MULTICAST_TTL, multicastTTL());
	if (!mcast_groups.contains(group)) mcast_groups << group;
	return true;
}


bool PIEthernet::leaveMulticastGroup(const PIString & group) {
	if (sock == -1) init();
	if (sock == -1) return false;
	if (type() != UDP) {
		piCoutObj << "Only UDP sockets can leave multicast groups";
		return false;
	}
	addr_r.set(path());
#ifndef LWIP
	struct ip_mreqn mreq;
#else
	struct ip_mreq mreq;
#endif
	piZeroMemory(mreq);
	if (params[PIEthernet::Broadcast])
#ifndef LWIP
		mreq.imr_address.s_addr = INADDR_ANY;
#else
		mreq.imr_interface.s_addr = INADDR_ANY;
#endif
	else
#ifndef LWIP
		mreq.imr_address.s_addr = addr_r.ip();
#else
		mreq.imr_interface.s_addr = addr_r.ip();
#endif
	mreq.imr_multiaddr.s_addr = inet_addr(group.dataAscii());
	if (ethSetsockopt(sock, IPPROTO_IP, IP_DROP_MEMBERSHIP, &mreq, sizeof(mreq)) == -1) {
		piCoutObj << "Can`t leave multicast group" << group << "," << ethErrorString();
		return false;
	}
	mcast_groups.removeAll(group);
	return true;
}


bool PIEthernet::connect(bool threaded) {
	if (threaded) {
		connecting_ = true;
		return true;
	}
	if (connected_) return false;
	if (sock == -1) init();
	if (sock == -1) return false;
	piZeroMemory(PRIVATE->addr_);
	addr_r.set(path());
	PRIVATE->addr_.sin_port        = htons(addr_r.port());
	PRIVATE->addr_.sin_addr.s_addr = addr_r.ip();
	PRIVATE->addr_.sin_family      = AF_INET;
#ifdef QNX
	PRIVATE->addr_.sin_len = sizeof(PRIVATE->addr_);
#endif
	connecting_ = true;
	connected_  = connectTCP();
	connecting_ = false;
	if (!connected_) {
		piCoutObj << "Can`t connect to" << addr_r << "," << ethErrorString();
	}
	opened_.exchange(connected_);
	if (connected_) {
		connected();
	}
	return connected_;
}


bool PIEthernet::listen(bool threaded) {
	if (sock == -1) init();
	if (sock == -1) return false;
	if (threaded) {
		if (server_thread_.isRunning()) {
			if (!server_bounded) return true;
			server_thread_.stop();
			if (!server_thread_.waitForFinish(100_ms)) server_thread_.terminate();
		}
		listen_threaded = true;
		server_bounded  = false;
		server_thread_.start(server_func);
		server_thread_.waitForStart();
		return true;
	}
	listen_threaded = server_bounded = false;
	addr_r.set(path());
	piZeroMemory(PRIVATE->addr_);
	PRIVATE->addr_.sin_port        = htons(addr_r.port());
	PRIVATE->addr_.sin_addr.s_addr = addr_r.ip();
	PRIVATE->addr_.sin_family      = AF_INET;
#ifdef QNX
	PRIVATE->addr_.sin_len = sizeof(PRIVATE->addr_);
#endif
	opened_   = false;
	int tries = 0;
	while ((bind(sock, (sockaddr *)&PRIVATE->addr_, sizeof(PRIVATE->addr_)) == -1) && (tries < 2)) {
		init();
		tries++;
	}
	if (tries == 2) {
		piCoutObj << "Can`t bind to" << addr_r << "," << ethErrorString();
		return false;
	}
	if (::listen(sock, 64) == -1) {
		piCoutObj << "Can`t listen on" << addr_r << "," << ethErrorString();
		return false;
	}
	opened_ = server_bounded = true;
	piCoutObj << "listen on" << path();
	server_thread_.start(server_func);
	return true;
}


bool PIEthernet::listen(const PINetworkAddress & addr, bool threaded) {
	setReadAddress(addr);
	return listen(threaded);
}


void PIEthernet::stopThreadedListen() {
	server_thread_.stop();
	PRIVATE->event.interrupt();
	if (server_thread_.isRunning()) {
		if (!server_thread_.waitForFinish(1_s)) server_thread_.terminate();
	}
	PRIVATE->event.destroy();
	if (sock_s == sock) sock_s = -1;
	closeSocket(sock);
	closeSocket(sock_s);
}


PIEthernet * PIEthernet::client(int index) {
	PIMutexLocker locker(clients_mutex);
	return clients_[index];
}


int PIEthernet::clientsCount() const {
	PIMutexLocker locker(clients_mutex);
	return clients_.size_s();
}


PIVector<PIEthernet *> PIEthernet::clients() const {
	PIMutexLocker locker(clients_mutex);
	return clients_;
}


bool PIEthernet::send(const void * data, int size, bool threaded) {
	if (threaded) {
		writeThreaded(data, size);
		return true;
	}
	return (write(data, size) == size);
}


bool PIEthernet::send(const PINetworkAddress & addr, const void * data, int size, bool threaded) {
	addr_s = addr;
	if (threaded) {
		writeThreaded(data, size);
		return true;
	}
	PINetworkAddress pa = addr_s;
	addr_s              = addr;
	int wr              = write(data, size);
	addr_s              = pa;
	return (wr == size);
}


bool PIEthernet::send(const PIByteArray & data, bool threaded) {
	if (threaded) {
		writeThreaded(data);
		return true;
	}
	return (write(data) == data.size_s());
}


bool PIEthernet::send(const PINetworkAddress & addr, const PIByteArray & data, bool threaded) {
	if (threaded) {
		writeThreaded(data);
		return true;
	}
	PINetworkAddress pa = addr_s;
	addr_s              = addr;
	int wr              = write(data);
	addr_s              = pa;
	return (wr == data.size_s());
}


void PIEthernet::interrupt() {
	// piCout << "interrupt";
	PRIVATE->event.interrupt();
}


ssize_t PIEthernet::readDevice(void * read_to, ssize_t max_size) {
	// piCout << "read" << sock;
	if (sock == -1) init();
	if (sock == -1 || read_to == 0) return -1;
	int rs = 0, lerr = 0;
	// piCoutObj << "read from " << path() << connecting_;
	switch (type()) {
	case TCP_Client:
		if (connecting_) {
			addr_r.set(path());
			piZeroMemory(PRIVATE->addr_);
			PRIVATE->addr_.sin_port        = htons(addr_r.port());
			PRIVATE->addr_.sin_addr.s_addr = addr_r.ip();
			PRIVATE->addr_.sin_family      = AF_INET;
#ifdef QNX
			PRIVATE->addr_.sin_len = sizeof(PRIVATE->addr_);
#endif
			// piCoutObj << "connect to " << path() << "...";
			connected_ = connectTCP();
			// piCoutObj << "connect to " << path() << connected_;
			if (!connected_) piCoutObj << "Can`t connect to" << addr_r << "," << ethErrorString();
			opened_.exchange(connected_);
			if (connected_) {
				connecting_ = false;
				connected();
			} else
				piMSleep(10);
			// piCout << "connected to" << path();
		}
		if (!connected_) return -1;
		errorClear();
#ifdef WINDOWS
		{
			long wr = waitForEvent(PRIVATE->event, FD_READ | FD_CLOSE);
			switch (wr) {
			case 0: return -1;
			case FD_READ:
				// piCout << "fd_read ...";
				rs = ethRecv(sock, read_to, max_size);
				break;
			case FD_CLOSE:
				// piCout << "fd_close ...";
				rs = -1;
				break;
			default: break;
			}
		}
#else
		if (PRIVATE->event.wait(sock)) {
			errorClear();
			rs = ethRecv(sock, read_to, max_size);
		}
#endif
		// piCoutObj << "readed" << rs;
		if (rs <= 0) {
			lerr = ethErrorCore();
			// piCoutObj << "readed" << rs << "error" << lerr;

			// async normal returns
#ifdef WINDOWS
			if (lerr == WSAEWOULDBLOCK) {
#else
			if (lerr == EWOULDBLOCK || lerr == EAGAIN || lerr == EINTR) {
#endif
				// piCoutObj << "Ignore would_block" << lerr;
				return -1;
			}

			// if no disconnect on timeout
			if (!params[DisonnectOnTimeout]) {
#ifdef WINDOWS
				if (lerr == WSAETIMEDOUT) {
#else
				if (lerr == ETIMEDOUT) {
#endif
					// piCoutObj << "Ignore read timeout";
					return -1;
				}
			}
			// disconnect here
			// piCoutObj << "Disconnnected, check for event, connected =" << connected_;
			if (connected_.exchange(false)) {
				opened_ = false;
				// piCoutObj << "Disconnect on read," << ethErrorString();
				closeSocket(sock);
				init();
				disconnected(rs < 0);
			}
			if (params[KeepConnection]) {
				connect();
			}
			// piCoutObj << "eth" << ip_ << "disconnected";
		}
		if (rs > 0) received(read_to, rs);
		return rs;
	case UDP: {
		piZeroMemory(PRIVATE->raddr_);
		// piCoutObj << "read from" << path() << "...";
#ifdef WINDOWS
		long wr = waitForEvent(PRIVATE->event, FD_READ | FD_CLOSE);
		switch (wr) {
		case FD_READ:
			// piCout << "fd_read ...";
			rs = ethRecvfrom(sock, read_to, max_size, 0, (sockaddr *)&PRIVATE->raddr_);
			break;
		case FD_CLOSE:
			// piCout << "fd_close ...";
			rs = -1;
			break;
		default: break;
		}
#else
		rs = ethRecvfrom(sock, read_to, max_size, 0, (sockaddr *)&PRIVATE->raddr_);
#endif
		// piCoutObj << "read from" << path() << rs << "bytes";
		if (rs > 0) {
			addr_lr.set(uint(PRIVATE->raddr_.sin_addr.s_addr), ntohs(PRIVATE->raddr_.sin_port));
			// piCoutObj << "read from" << ip_r << ":" << port_r << rs << "bytes";
			received(read_to, rs);
		}
		// else piCoutObj << "read returt" << rs << ", error" << ethErrorString();
		return rs;
	}
	default: break;
	}
	return -1;
}


ssize_t PIEthernet::writeDevice(const void * data, ssize_t max_size) {
	if (sock == -1) init();
	if (sock == -1 || !isWriteable()) {
		// piCoutObj << "Can`t send to uninitialized socket";
		return -1;
	}
	// piCoutObj << "sending to " << ip_s << ":" << port_s << "  " << max_size << " bytes";
	int ret = 0;
	switch (type()) {
	case UDP:
		PRIVATE->saddr_.sin_port        = htons(addr_s.port());
		PRIVATE->saddr_.sin_addr.s_addr = addr_s.ip();
		PRIVATE->saddr_.sin_family      = AF_INET;
		// piCoutObj << "write to" << ip_s << ":" << port_s << "socket" << sock_s << max_size << "bytes ...";
		return ethSendto(sock_s,
		                 data,
		                 max_size,
#ifndef WINDOWS
		                 isOptionSet(BlockingWrite) ? 0 : MSG_DONTWAIT
#else
		                 0
#endif
		                 ,
		                 (sockaddr *)&PRIVATE->saddr_,
		                 sizeof(PRIVATE->saddr_));
		// piCout << "[PIEth] write to" << ip_s << ":" << port_s << "ok";
	case TCP_Client: {
		if (connecting_) {
			piZeroMemory(PRIVATE->addr_);
			addr_r.set(path());
			PRIVATE->addr_.sin_port        = htons(addr_r.port());
			PRIVATE->addr_.sin_addr.s_addr = addr_r.ip();
			PRIVATE->addr_.sin_family      = AF_INET;
#ifdef QNX
			PRIVATE->addr_.sin_len = sizeof(PRIVATE->addr_);
#endif
			// piCoutObj << "connect to " << ip << ":" << port_;
			connected_ = connectTCP();
			if (!connected_) piCoutObj << "Can`t connect to" << addr_r << "," << ethErrorString();
			opened_.exchange(connected_);
			if (connected_) {
				connecting_ = false;
				connected();
			}
		}
		if (!connected_) return -1;
		auto disconnectFunc = [this]() {
			if (connected_.exchange(false)) {
				opened_ = false;
				// piCoutObj << "Disconnect on write," << ethErrorString();
				closeSocket(sock);
				init();
				disconnected(true);
				if (params[KeepConnection]) {
					connect();
				}
			}
		};
		if (!isOptionSet(BlockingWrite)) {
			ret = ::send(sock, (const char *)data, max_size, 0);
			if (ret < 0) {
				disconnectFunc();
				return -1;
			}
		} else {
			ssize_t remain_size      = max_size;
			const char * remain_data = (const char *)data;
			while (remain_size > 0) {
				int sr = ::send(sock, remain_data, remain_size, 0);
				if (sr < 0) {
					int err = ethErrorCore();
#ifdef WINDOWS
					if (err == WSAEWOULDBLOCK) {
#else
					if (err == EAGAIN || err == EWOULDBLOCK) {
#endif
						piMinSleep();
						// piCoutObj << "wait for write";
						continue;
					} else {
						disconnectFunc();
						return -1;
					}
				}
				ret += sr;
				remain_data += sr;
				remain_size -= sr;
			}
		}
		return ret;
	}
	default: break;
	}
	return -1;
}


PIIODevice::DeviceInfoFlags PIEthernet::deviceInfoFlags() const {
	switch (type()) {
	case UDP: return 0;
	case TCP_Client:
	case TCP_Server: return Sequential | Reliable;
	default: break;
	}
	return 0;
}


void PIEthernet::applyParameters() {
	if (sock == -1) return;
	if (params[PIEthernet::ReuseAddress]) ethSetsockoptBool(sock, SOL_SOCKET, SO_REUSEADDR);
	if (params[PIEthernet::Broadcast]) ethSetsockoptBool(sock, SOL_SOCKET, SO_BROADCAST);
	if (params[PIEthernet::NoDelay] && (type() == TCP_Client)) ethSetsockoptBool(sock, IPPROTO_TCP, TCP_NODELAY, true);
}


void PIEthernet::clientDeleted(PIObject * o) {
	PIMutexLocker locker(clients_mutex);
	clients_.removeOne((PIEthernet *)o);
}


void PIEthernet::server_func(void * eth) {
	PIEthernet * ce = (PIEthernet *)eth;
	if (ce->listen_threaded) {
		if (!ce->server_bounded) {
			if (!ce->listen(false)) {
				ce->listen_threaded = true;
				piMSleep(100);
				return;
			}
		}
	}
	sockaddr_in client_addr;
	socklen_t slen = sizeof(client_addr);
#ifdef WINDOWS
	long wr = ce->waitForEvent(ce->PRIVATEWB->event, FD_ACCEPT | FD_CLOSE);
	if (wr != FD_ACCEPT) {
		piMSleep(10);
		return;
	}
#else
	if (!ce->PRIVATEWB->event.wait(ce->sock)) {
		piMSleep(10);
		return;
	}
#endif
	// piCout << "server" << "accept ...";
	int s = accept(ce->sock, (sockaddr *)&client_addr, &slen);
	// piCout << "server" << "accept done" << ethErrorString();
	if (s == -1) {
		int lerr = ethErrorCore();
#ifdef WINDOWS
		if (lerr == WSAETIMEDOUT) {
#elif defined(ANDROID)
		if ((lerr == EAGAIN || lerr == EINTR)) {
#else
		if (lerr == EAGAIN) {
#endif
			piMSleep(10);
			return;
		}
		if (ce->debug())
			piCout << "[PIEthernet]"
				   << "Can`t accept new connection, %1"_tr("PIEthernet").arg(ethErrorString());
		piMSleep(50);
		return;
	}
	PIString ip = PIStringAscii(inet_ntoa(client_addr.sin_addr));
	ip += ":" + PIString::fromNumber(htons(client_addr.sin_port));
	PIEthernet * e = new PIEthernet(s, ip);
	CONNECT1(void, PIObject *, e, deleted, ce, clientDeleted);
	ce->clients_mutex.lock();
	ce->clients_ << e;
	ce->clients_mutex.unlock();
	ce->newConnection(e);
	// cout << "connected " << ip << endl;
}


void PIEthernet::setType(Type t, bool reopen) {
	eth_type = t;
	setProperty("type", (int)t);
	if (reopen && isOpened()) {
		closeDevice();
		init();
		openDevice();
	}
}


bool PIEthernet::connectTCP() {
	::connect(sock, (sockaddr *)&(PRIVATE->addr_), sizeof(PRIVATE->addr_));
	// piCout << errorString();
#ifdef WINDOWS
	long wr = waitForEvent(PRIVATE->event, FD_CONNECT | FD_CLOSE);
	switch (wr) {
	case FD_CONNECT:
		// piCout << "fd_connect ...";
		return ethIsWriteable(sock);
	default: break;
	}
#else
	if (PRIVATE->event.wait(sock, PIWaitEvent::CheckWrite)) {
		if (ethIsWriteable(sock))
			return true;
		else {
			closeSocket(sock);
			init();
		}
	}
#endif
	return false;
}


#ifdef WINDOWS
long PIEthernet::waitForEvent(PIWaitEvent & event, long mask) {
	if (!event.isCreate() || sock < 0) return 0;
	if (WSAEventSelect(sock, event.getEvent(), mask) == SOCKET_ERROR) {
		if (ethErrorCore() == WSAEINPROGRESS) return 0;
	}
	if (event.wait()) {
		// DWORD wr = WSAWaitForMultipleEvents(1, &(PRIVATE->read_event), FALSE, WSA_INFINITE, TRUE);
		// if (wr == WSA_WAIT_EVENT_0) {
		WSANETWORKEVENTS events;
		piZeroMemory(events);
		WSAEnumNetworkEvents(sock, event.getEvent(), &events);
		// piCoutObj << "wait result" << events.lNetworkEvents;
		return events.lNetworkEvents;
	}
	return 0;
}
#endif


bool PIEthernet::configureDevice(const void * e_main, const void * e_parent) {
	PIConfig::Entry * em = (PIConfig::Entry *)e_main;
	PIConfig::Entry * ep = (PIConfig::Entry *)e_parent;
	setReadIP(readDeviceSetting<PIString>("ip", readIP(), em, ep));
	setReadPort(readDeviceSetting<int>("port", readPort(), em, ep));
	setParameter(PIEthernet::Broadcast, readDeviceSetting<bool>("broadcast", isParameterSet(PIEthernet::Broadcast), em, ep));
	setParameter(PIEthernet::ReuseAddress, readDeviceSetting<bool>("reuseAddress", isParameterSet(PIEthernet::ReuseAddress), em, ep));
	return true;
}


void PIEthernet::propertyChanged(const char * name) {
	PIConstChars pn(name);
	if (pn.endsWith("Timeout")) applyTimeouts();
	if (pn == "TTL") applyOptInt(IPPROTO_IP, IP_TTL, TTL());
	if (pn == "MulticastTTL") applyOptInt(IPPROTO_IP, IP_MULTICAST_TTL, multicastTTL());
}


PIString PIEthernet::constructFullPathDevice() const {
	PIString ret;
	ret += (type() == PIEthernet::UDP ? "UDP" : "TCP");
	ret += ":" + readIP() + ":" + PIString::fromNumber(readPort());
	if (type() == PIEthernet::UDP) {
		ret += ":" + sendIP() + ":" + PIString::fromNumber(sendPort());
		for (const auto & m: multicastGroups())
			ret += ":mcast:" + m;
	}
	return ret;
}


void PIEthernet::configureFromFullPathDevice(const PIString & full_path) {
	PIStringList pl = full_path.split(":");
	bool mcast      = false;
	for (int i = 0; i < pl.size_s(); ++i) {
		PIString p(pl[i].trimmed());
		switch (i) {
		case 0:
			p = p.toLowerCase();
			if (p == "udp") setType(UDP);
			if (p == "tcp") setType(TCP_Client);
			break;
		case 1:
			setReadIP(p);
			setSendIP(p);
			break;
		case 2:
			setReadPort(p.toInt());
			setSendPort(p.toInt());
			break;
		case 3: setSendIP(p); break;
		case 4: setSendPort(p.toInt()); break;
		}
		if (i <= 4) continue;
		if (i % 2 == 1) {
			if (p.toLowerCase() == "mcast") mcast = true;
		} else {
			if (mcast) {
				joinMulticastGroup(p);
				mcast = false;
			}
		}
	}
}


PIPropertyStorage PIEthernet::constructVariantDevice() const {
	PIPropertyStorage ret;
	PIVariantTypes::Enum e;

	e << "UDP"
	  << "TCP";
	if (type() == PIEthernet::UDP)
		e.selectValue(0);
	else
		e.selectValue(1);
	ret.addProperty("protocol", e);

	ret.addProperty("read IP", readIP());
	ret.addProperty("read port", readPort());
	ret.addProperty("send IP", sendIP());
	ret.addProperty("send port", sendPort());
	ret.addProperty("multicast", multicastGroups());
	return ret;
}


void PIEthernet::configureFromVariantDevice(const PIPropertyStorage & d) {
	setType(d.propertyValueByName("protocol").toEnum().selectedValue() == 0 ? UDP : TCP_Client);
	setReadIP(d.propertyValueByName("read IP").toString());
	setReadPort(d.propertyValueByName("read port").toInt());
	setSendIP(d.propertyValueByName("send IP").toString());
	setSendPort(d.propertyValueByName("send port").toInt());
	PIStringList mcgl = d.propertyValueByName("multicast").toStringList();
	for (const auto & g: mcgl) {
		joinMulticastGroup(g);
	}
}


PIEthernet::InterfaceList PIEthernet::interfaces() {
	// piCout << "PIEthernet::interfaces()";
	PIEthernet::InterfaceList il;
	Interface ci;
	ci.index = -1;
	ci.mtu   = 1500;
#ifdef WINDOWS
	int ret                       = 0;
	ulong ulOutBufLen             = sizeof(IP_ADAPTER_INFO);
	PIP_ADAPTER_INFO pAdapterInfo = (PIP_ADAPTER_INFO)HeapAlloc(GetProcessHeap(), 0, sizeof(IP_ADAPTER_INFO));
	if (!pAdapterInfo) {
		piCout << "[PIEthernet]"
			   << "Error allocating memory needed to call GetAdaptersInfo"_tr("PIEthernet");
		return il;
	}
	if (GetAdaptersInfo(pAdapterInfo, &ulOutBufLen) == ERROR_BUFFER_OVERFLOW) {
		HeapFree(GetProcessHeap(), 0, pAdapterInfo);
		pAdapterInfo = (PIP_ADAPTER_INFO)HeapAlloc(GetProcessHeap(), 0, ulOutBufLen);
		if (!pAdapterInfo) {
			piCout << "[PIEthernet]"
				   << "Error allocating memory needed to call GetAdaptersInfo"_tr("PIEthernet");
			return il;
		}
	}
	if ((ret = GetAdaptersInfo(pAdapterInfo, &ulOutBufLen)) == NO_ERROR) {
		PIP_ADAPTER_INFO pAdapter = pAdapterInfo;
		while (pAdapter) {
			ci.name  = PIString(pAdapter->AdapterName);
			ci.index = pAdapter->Index;
			ci.mac   = macFromBytes(PIByteArray(pAdapter->Address, pAdapter->AddressLength));
			ci.flags = PIEthernet::ifActive | PIEthernet::ifRunning;
			if (pAdapter->Type == MIB_IF_TYPE_PPP) ci.flags |= PIEthernet::ifPTP;
			if (pAdapter->Type == MIB_IF_TYPE_LOOPBACK) ci.flags |= PIEthernet::ifLoopback;
			ci.broadcast.clear();
			ci.ptp.clear();
			IP_ADDR_STRING * as = &(pAdapter->IpAddressList);
			while (as) {
				//				piCout << "[pAdapter]" << ci.name << PIString(as->IpAddress.String);
				ci.address = PIStringAscii(as->IpAddress.String);
				ci.netmask = PIStringAscii(as->IpMask.String);
				if (ci.address == "0.0.0.0") {
					as = as->Next;
					continue;
				}
				il << ci;
				as = as->Next;
			}
			pAdapter = pAdapter->Next;
		}
	} else {
		switch (ret) {
		case ERROR_NO_DATA: break;
		case ERROR_NOT_SUPPORTED: piCout << "[PIEthernet] GetAdaptersInfo not supported"; break;
		default: piCout << "[PIEthernet] GetAdaptersInfo failed with error:" << ret;
		}
	}
	if (pAdapterInfo) HeapFree(GetProcessHeap(), 0, pAdapterInfo);
#else
#  ifdef MICRO_PIP
#  else
#	ifdef ANDROID
	struct ifconf ifc;
	int s = ::socket(AF_INET, SOCK_STREAM, IPPROTO_IP);
	if (s == -1) {
		piCout << "[PIEthernet]"
			   << "Can`t create socket: %1"_tr("PIEthernet").arg(errorString());
		return il;
	}
	ifc.ifc_len = 256;
	ifc.ifc_buf = new char[ifc.ifc_len];
	if (ioctl(s, SIOCGIFCONF, &ifc) < 0) {
		piCout << "[PIEthernet]"
			   << "Can`t get interfaces: %1"_tr("PIEthernet").arg(errorString());
		delete[] ifc.ifc_buf;
		::close(s);
		return il;
	}
	int icnt = ifc.ifc_len / sizeof(ifreq);
	PIStringList inl;
	struct ifreq ir;
	for (int i = 0; i < icnt; ++i) {
		ci.flags    = 0;
		PIString in = PIStringAscii(ifc.ifc_req[i].ifr_name);
		if (in.isEmpty()) continue;
		ci.name = in;
		strcpy(ir.ifr_name, in.dataAscii());
		if (ioctl(s, SIOCGIFHWADDR, &ir) == 0) ci.mac = macFromBytes(PIByteArray(ir.ifr_hwaddr.sa_data, 6));
		if (ioctl(s, SIOCGIFADDR, &ir) >= 0) ci.address = getSockAddr(&ir.ifr_addr);
		if (ioctl(s, SIOCGIFNETMASK, &ir) >= 0) ci.netmask = getSockAddr(&ir.ifr_netmask);
		if (ioctl(s, SIOCGIFMTU, &ir) == 0) {
			ci.mtu = ir.ifr_mtu;
		}
		if (ci.address == "127.0.0.1") ci.flags |= PIEthernet::ifLoopback;
		il << ci;
	}
	delete[] ifc.ifc_buf;
	::close(s);
#	else
	struct ifaddrs *ret, *cif = 0;
	int s = ::socket(AF_INET, SOCK_STREAM, IPPROTO_IP);
	if (getifaddrs(&ret) == 0) {
		cif = ret;
		while (cif != 0) {
			if (cif->ifa_addr == 0) {
				cif = cif->ifa_next;
				continue;
			}
			if (cif->ifa_addr->sa_family != AF_INET) {
				cif = cif->ifa_next;
				continue;
			}
			ci.name    = PIString(cif->ifa_name);
			ci.address = getSockAddr(cif->ifa_addr);
			ci.netmask = getSockAddr(cif->ifa_netmask);
			ci.mac.clear();
#	  ifdef QNX
#		ifndef BLACKBERRY
			int fd = ::open((PIString("/dev/io-net/") + ci.name).dataAscii(), O_RDONLY);
			if (fd >= 0) {
				nic_config_t nic;
				devctl(fd, DCMD_IO_NET_GET_CONFIG, &nic, sizeof(nic), 0);
				::close(fd);
				ci.mac = macFromBytes(PIByteArray(nic.permanent_address, 6));
			}
#		endif
#	  else
#		ifdef MAC_OS
			PIString req = PISystemInfo::instance()->ifconfigPath + " " + ci.name + " | grep ether";
			FILE * fp    = popen(req.dataAscii(), "r");
			if (fp != 0) {
				char in[256];
				if (fgets(in, 256, fp) != 0) {
					req    = PIString(in).trim();
					ci.mac = req.cutLeft(req.find(" ") + 1).trim().toUpperCase();
				}
				pclose(fp);
			}
#		else
			if (s != -1) {
				struct ifreq ir;
				memset(&ir, 0, sizeof(ir));
				strncpy(ir.ifr_name, cif->ifa_name, sizeof(ir.ifr_name));
				if (ioctl(s, SIOCGIFHWADDR, &ir) == 0) {
					ci.mac = macFromBytes(PIByteArray(ir.ifr_hwaddr.sa_data, 6));
				}
				if (ioctl(s, SIOCGIFMTU, &ir) == 0) {
					ci.mtu = ir.ifr_mtu;
				}
			}
#		endif
#	  endif
			ci.flags = 0;
			if (cif->ifa_flags & IFF_UP) ci.flags |= PIEthernet::ifActive;
			if (cif->ifa_flags & IFF_RUNNING) ci.flags |= PIEthernet::ifRunning;
			if (cif->ifa_flags & IFF_BROADCAST) ci.flags |= PIEthernet::ifBroadcast;
			if (cif->ifa_flags & IFF_MULTICAST) ci.flags |= PIEthernet::ifMulticast;
			if (cif->ifa_flags & IFF_LOOPBACK) ci.flags |= PIEthernet::ifLoopback;
			if (cif->ifa_flags & IFF_POINTOPOINT) ci.flags |= PIEthernet::ifPTP;
			ci.broadcast.clear();
			ci.ptp.clear();
			if (ci.flags[PIEthernet::ifBroadcast]) ci.broadcast = getSockAddr(cif->ifa_broadaddr);
			if (ci.flags[PIEthernet::ifPTP]) ci.ptp = getSockAddr(cif->ifa_dstaddr);
			ci.index = if_nametoindex(cif->ifa_name);
			il << ci;
			cif = cif->ifa_next;
		}
		freeifaddrs(ret);
	} else
		piCout << "[PIEthernet]"
			   << "Can`t get interfaces: %1"_tr("PIEthernet").arg(errorString());
	if (s != -1) ::close(s);
#	endif
#  endif
#endif
	return il;
}


PINetworkAddress PIEthernet::interfaceAddress(const PIString & interface_) {
#if defined(WINDOWS) || defined(MICRO_PIP)
	piCout << "[PIEthernet] Not implemented, use \"PIEthernet::allAddresses\" or \"PIEthernet::interfaces\" instead";
	return PINetworkAddress();
#else
	struct ifreq ifr;
	piZeroMemory(ifr);
	strncpy(ifr.ifr_name, interface_.dataAscii(), sizeof(ifr.ifr_name));
	int s = ::socket(AF_INET, SOCK_DGRAM, 0);
	if (s != -1) {
		ioctl(s, SIOCGIFADDR, &ifr);
		::close(s);
	}
	struct sockaddr_in * sa = (struct sockaddr_in *)&ifr.ifr_addr;
	return PINetworkAddress(uint(sa->sin_addr.s_addr));
#endif
}


PIVector<PINetworkAddress> PIEthernet::allAddresses() {
	PIEthernet::InterfaceList il = interfaces();
	PIVector<PINetworkAddress> ret;
	bool has_127 = false;
	for (const auto & i: il) {
		if (i.address.startsWith("127.0.0.")) has_127 = true;
		PINetworkAddress a(i.address);
		if (a.ip() == 0) continue;
		ret << a;
	}
	//	piCout << "[PIEthernet::allAddresses]" << al;
	if (!has_127) ret << PINetworkAddress("127.0.0.1");
	return ret;
}


// System wrap

int PIEthernet::ethErrorCore() {
#ifdef WINDOWS
	return WSAGetLastError();
#else
	return errno;
#endif
}


PIString PIEthernet::ethErrorString() {
#ifdef WINDOWS
	char * msg = nullptr;
	int err    = WSAGetLastError();
	FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
	               NULL,
	               err,
	               MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
	               (LPSTR)&msg,
	               0,
	               NULL);
	PIString ret = PIStringAscii("code ") + PIString::fromNumber(err) + PIStringAscii(" - ");
	if (msg) {
		ret += PIString::fromSystem(msg).trim();
		LocalFree(msg);
	} else
		ret += '?';
	return ret;
#else
	return errorString();
#endif
}


int PIEthernet::ethRecv(int sock, void * buf, int size, int flags) {
	if (sock < 0) return -1;
	return recv(sock,
#ifdef WINDOWS
	            (char *)
#endif
	                buf,
	            size,
	            flags);
}


int PIEthernet::ethRecvfrom(int sock, void * buf, int size, int flags, sockaddr * addr) {
	if (sock < 0) return -1;
#ifdef QNX
	return recv(sock, buf, size, flags);
#else
	socklen_t len = sizeof(sockaddr);
	return recvfrom(sock,
#  ifdef WINDOWS
	                (char *)
#  endif
	                    buf,
	                size,
	                flags,
	                addr,
	                &len);
#endif
}


int PIEthernet::ethSendto(int sock, const void * buf, int size, int flags, sockaddr * addr, int addr_len) {
	if (sock < 0) return -1;
	return sendto(sock,
#ifdef WINDOWS
	              (const char *)
#endif
	                  buf,
	              size,
	              flags,
	              addr,
	              addr_len);
}


void PIEthernet::ethClosesocket(int sock, bool shutdown) {
	// piCout << "close socket" << sock << shutdown;
	if (sock < 0) return;
	if (shutdown)
		::shutdown(sock,
#ifdef WINDOWS
		           SD_BOTH);
	closesocket(sock);
#else
		           SHUT_RDWR);
	::close(sock);
#endif
}


int PIEthernet::ethSetsockopt(int sock, int level, int optname, const void * optval, int optlen) {
	if (sock < 0) return -1;
	auto ret = setsockopt(sock,
	                      level,
	                      optname,
#ifdef WINDOWS
	                      (char *)
#endif
	                          optval,
	                      optlen);
	if (ret != 0) piCout << "setsockopt error:" << ethErrorString();
	return ret;
}


int PIEthernet::ethSetsockoptInt(int sock, int level, int optname, int value) {
	if (sock < 0) return -1;
#ifdef WINDOWS
	DWORD
#else
	int
#endif
	so = value;
	return ethSetsockopt(sock, level, optname, &so, sizeof(so));
}


int PIEthernet::ethSetsockoptBool(int sock, int level, int optname, bool value) {
	if (sock < 0) return -1;
#ifdef WINDOWS
	BOOL
#else
	int
#endif
		so = (value ? 1 : 0);
	return ethSetsockopt(sock, level, optname, &so, sizeof(so));
}


void PIEthernet::ethNonblocking(int sock) {
	if (sock < 0) return;
#ifdef WINDOWS
	u_long mode = 1;
	ioctlsocket(sock, FIONBIO, &mode);
#else
	fcntl(sock, F_SETFL, O_NONBLOCK);
#endif
}


bool PIEthernet::ethIsWriteable(int sock) {
/*	fd_set fd_test;
	FD_ZERO(&fd_test);
	FD_SET(sock, &fd_test);
	int fds = 0;
#ifndef WINDOWS
	fds = sock + 1;
#endif
	timeval timeout;
	timeout.tv_sec = timeout.tv_usec = 0;
	::select(fds, nullptr, &fd_test, nullptr, &timeout);
	return FD_ISSET(sock, &fd_test);*/
#ifdef WINDOWS
	fd_set fd_test;
	FD_ZERO(&fd_test);
	FD_SET(sock, &fd_test);
	timeval timeout;
	timeout.tv_sec = timeout.tv_usec = 0;
	::select(0, nullptr, &fd_test, nullptr, &timeout);
	return FD_ISSET(sock, &fd_test);
#else
	int ret       = 0;
	socklen_t len = sizeof(ret);
	getsockopt(sock, SOL_SOCKET, SO_ERROR, (char *)&ret, &len);
	return ret == 0;
#endif
}
