/* Copyright (C) 1997-2001 Id Software, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include "qcommon.h" #if defined(__APPLE__) || defined (MACOSX) #include #endif /* __APPLE__ || MACOSX */ /* packet header ------------- 31 sequence 1 does this message contain a reliable payload 31 acknowledge sequence 1 acknowledge receipt of even/odd message 16 qport The remote connection never knows if it missed a reliable message, the local side detects that it has been dropped by seeing a sequence acknowledge higher than the last reliable sequence, but without the correct even/odd bit for the reliable set. If the sender notices that a reliable message has been dropped, it will be retransmitted. It will not be retransmitted again until a message after the retransmit has been acknowledged and the reliable still failed to get there. If the sequence number is -1, the packet should be handled without a netcon. The reliable message can be added to at any time by doing MSG_Write* (&netchan->message, ). If the message buffer is overflowed, either by a single message, or by multiple frames worth piling up while the last reliable transmit goes unacknowledged, the netchan signals a fatal error. Reliable messages are always placed first in a packet, then the unreliable message is included if there is sufficient room. To the receiver, there is no distinction between the reliable and unreliable parts of the message, they are just processed out as a single larger message. Illogical packet sequence numbers cause the packet to be dropped, but do not kill the connection. This, combined with the tight window of valid reliable acknowledgement numbers provides protection against malicious address spoofing. The qport field is a workaround for bad address translating routers that sometimes remap the client's source port on a packet during gameplay. If the base part of the net address matches and the qport matches, then the channel matches even if the IP port differs. The IP port should be updated to the new value before sending out any replies. If there is no information that needs to be transfered on a given frame, such as during the connection stage while waiting for the client to load, then a packet only needs to be delivered if there is something in the unacknowledged reliable */ cvar_t *showpackets; cvar_t *showdrop; cvar_t *qport; cvar_t *net_showfragments; // & 1 = client & 2 = server netadr_t net_from; /* =============== Netchan_Init =============== */ void Netchan_Init( void ) { int port; // pick a port value that should be nice and random port = Sys_Milliseconds() & 0xffff; showpackets = Cvar_Get( "showpackets", "0", 0 ); showdrop = Cvar_Get( "showdrop", "0", 0 ); qport = Cvar_Get( "qport", va("%i", port), CVAR_NOSET ); net_showfragments = Cvar_Get( "net_showfragments", "0", 0 ); } /* =============== Netchan_OutOfBand Sends an out-of-band datagram ================ */ void Netchan_OutOfBand( int net_socket, netadr_t adr, int length, qbyte *data ) { msg_t send; qbyte send_buf[MAX_PACKETLEN]; // write the packet header MSG_Init( &send, send_buf, sizeof(send_buf) ); MSG_WriteLong( &send, -1 ); // -1 sequence means out of band MSG_Write( &send, data, length ); // send the datagram NET_SendPacket( net_socket, send.cursize, send.data, adr ); } /* =============== Netchan_OutOfBandPrint Sends a text message in an out-of-band datagram ================ */ void Netchan_OutOfBandPrint (int net_socket, netadr_t adr, char *format, ...) { va_list argptr; static char string[MAX_PACKETLEN - 4]; va_start (argptr, format); vsnprintf (string, sizeof(string), format, argptr); va_end (argptr); Netchan_OutOfBand (net_socket, adr, (int)strlen(string), (qbyte *)string); } /* ============== Netchan_Setup called to open a channel to a remote system ============== */ void Netchan_Setup( netsrc_t sock, netchan_t *chan, netadr_t adr, int qport ) { memset( chan, 0, sizeof(*chan) ); chan->sock = sock; chan->remoteAddress = adr; chan->qport = qport; chan->incomingSequence = 0; chan->outgoingSequence = 1; } static qbyte msg_process_data[MAX_MSGLEN]; static char socknames[3][16] = { "Client", "Server", "Unknown" }; char *SockString( int sock ) { if( sock == NS_CLIENT ) return socknames[0]; else if( sock == NS_SERVER ) return socknames[1]; else return socknames[2]; } //============================================================= // Huffman compression //============================================================= //======================= //Netchan_HuffCompressMessage //======================= int Netchan_HuffCompressMessage( msg_t *msg ) { int length; if( msg == NULL || !msg->data ) return 0; // zero-fill our buffer for huffman compression length = 0; memset( msg_process_data, 0, sizeof(msg_process_data) ); //compress the message length = Huff_EncodeStatic( msg->data, msg->cursize, msg_process_data, sizeof(msg_process_data) ); if( length >= msg->cursize || length >= MAX_MSGLEN ) { return 0; // compressed was bigger. Send uncompressed } //write it back into the original container MSG_Clear( msg ); MSG_Write( msg, msg_process_data, length ); msg->compressed = qtrue; return length; } //======================= //Netchan_HuffDecompressMessage // //Starts decompressing at msg->readcount so it preserves the uncompressed header intact //======================= int Netchan_HuffDecompressMessage( msg_t *msg ) { int length; if( msg == NULL || !msg->data ) return 0; if( msg->compressed == qfalse ) return 0; length = Huff_DecodeStatic( msg->data + msg->readcount, msg->cursize - msg->readcount, msg_process_data, (sizeof(msg_process_data) - msg->readcount) ); if( (msg->cursize + length) >= msg->maxsize ) Com_Error( ERR_FATAL, "Netchan_Process: buffer overflow at decompress" ); //write it back into the original container msg->cursize = msg->readcount; MSG_Write( msg, msg_process_data, length ); msg->compressed = qfalse; return length; } //============================================================= // Zlib compression //============================================================= #include "zlib.h" int Netchan_ZLibDecompressChunk( qbyte *in, int inlen, qbyte *out, int outlen, int wbits ) { z_stream zs; int result; memset( &zs, 0, sizeof(zs) ); zs.next_in = in; zs.avail_in = 0; zs.next_out = out; zs.avail_out = outlen; result = inflateInit2( &zs, wbits ); if( result != Z_OK ) { Com_DPrintf( "ZLib data error! Error %d on inflateInit.\nMessage: %s", result, zs.msg ); return result; } zs.avail_in = inlen; result = inflate(&zs, Z_FINISH); if( result != Z_STREAM_END ) { Com_DPrintf( "ZLib data error! Error %d on inflate.\nMessage: %s", result, zs.msg ); zs.total_out = 0; return -1; } result = inflateEnd( &zs ); if( result != Z_OK ) { Com_DPrintf( "ZLib data error! Error %d on inflateEnd.\nMessage: %s", result, zs.msg ); return -1; } return zs.total_out; } int Netchan_ZLibCompressChunk( qbyte *in, int len_in, qbyte *out, int max_len_out, int method, int wbits ) { z_stream zs; int result; zs.next_in = in; zs.avail_in = len_in; zs.total_in = 0; zs.next_out = out; zs.avail_out = max_len_out; zs.total_out = 0; zs.msg = NULL; zs.state = NULL; zs.zalloc = Z_NULL; zs.zfree = Z_NULL; zs.opaque = NULL; zs.data_type = Z_BINARY; zs.adler = 0; zs.reserved = 0; result = deflateInit2( &zs, method, Z_DEFLATED, wbits, 9, Z_DEFAULT_STRATEGY ); if( result != Z_OK ) return -1; result = deflate( &zs, Z_FINISH ); if( result != Z_STREAM_END ) return -1; result = deflateEnd( &zs ); if( result != Z_OK ) return -1; return zs.total_out; } //======================= //Netchan_ZLibCompressMessage //======================= int Netchan_CompressMessage( msg_t *msg ) { int length; if( msg == NULL || !msg->data ) return 0; // zero-fill our buffer length = 0; memset( msg_process_data, 0, sizeof(msg_process_data) ); //compress the message length = Netchan_ZLibCompressChunk( msg->data, msg->cursize, msg_process_data, sizeof(msg_process_data), Z_DEFAULT_COMPRESSION, -MAX_WBITS ); if( length < 0 ) // failed to compress, return the error return length; if( length >= msg->cursize || length >= MAX_MSGLEN ) { return 0; // compressed was bigger. Send uncompressed } //write it back into the original container MSG_Clear( msg ); MSG_Write( msg, msg_process_data, length ); msg->compressed = qtrue; return length; // return the new size } //======================= //Netchan_ZLibCompressMessage //======================= int Netchan_DecompressMessage( msg_t *msg ) { int length; if( msg == NULL || !msg->data ) return 0; if( msg->compressed == qfalse ) return 0; length = Netchan_ZLibDecompressChunk( msg->data + msg->readcount, msg->cursize - msg->readcount, msg_process_data, (sizeof(msg_process_data) - msg->readcount), -MAX_WBITS ); if( length < 0 ) return length; if( (msg->cursize + length) >= msg->maxsize ) Com_Error( ERR_FATAL, "Netchan_Process: buffer overflow at decompress" ); //write it back into the original container msg->cursize = msg->readcount; MSG_Write( msg, msg_process_data, length ); msg->compressed = qfalse; return length; } //================= //Netchan_TransmitNextFragment // //Send one fragment of the current message //================= void Netchan_TransmitNextFragment( netchan_t *chan ) { msg_t send; qbyte send_buf[MAX_PACKETLEN]; int fragmentLength; qboolean last; // write the packet header MSG_Init( &send, send_buf, sizeof(send_buf) ); MSG_Clear( &send ); if( net_showfragments->integer & 1 && chan->sock == NS_CLIENT ) { Com_Printf( "Transmit fragment (%s) (id:%i)\n", SockString( chan->sock ), chan->outgoingSequence ); } if( net_showfragments->integer & 2 && chan->sock == NS_SERVER ) { Com_Printf( "Transmit fragment (%s) (id:%i)\n", SockString( chan->sock ), chan->outgoingSequence ); } MSG_WriteLong( &send, chan->outgoingSequence | FRAGMENT_BIT ); // wsw : jal : by now our header sends incoming ack too (q3 doesn't) // wsw : also add compressed bit if it's compressed if( chan->unsentIsCompressed ) MSG_WriteLong( &send, chan->incomingSequence | FRAGMENT_BIT ); else MSG_WriteLong( &send, chan->incomingSequence ); // send the qport if we are a client if ( chan->sock == NS_CLIENT ) { MSG_WriteShort( &send, qport->integer ); } // copy the reliable message to the packet first if ( chan->unsentFragmentStart + FRAGMENT_SIZE > chan->unsentLength ) { fragmentLength = chan->unsentLength - chan->unsentFragmentStart; last = qtrue; } else { fragmentLength = ceil((chan->unsentLength - chan->unsentFragmentStart)*1.0 / ceil((chan->unsentLength - chan->unsentFragmentStart)*1.0 / FRAGMENT_SIZE)); last = qfalse; } MSG_WriteShort( &send, chan->unsentFragmentStart ); MSG_WriteShort( &send, (last ? (fragmentLength | FRAGMENT_LAST) : fragmentLength) ); MSG_Write( &send, chan->unsentBuffer + chan->unsentFragmentStart, fragmentLength ); // send the datagram NET_SendPacket( chan->sock, send.cursize, send.data, chan->remoteAddress ); if( showpackets->integer ) { Com_Printf ("%s send %4i : s=%i fragment=%i,%i\n" , SockString( chan->sock ) , send.cursize , chan->outgoingSequence , chan->unsentFragmentStart, fragmentLength ); } chan->unsentFragmentStart += fragmentLength; // this exit condition is a little tricky, because a packet // that is exactly the fragment length still needs to send // a second packet of zero length so that the other side // can tell there aren't more to follow if( chan->unsentFragmentStart == chan->unsentLength && fragmentLength != FRAGMENT_SIZE ) { chan->outgoingSequence++; chan->unsentFragments = qfalse; } } //================= //Netchan_PushAllFragments // //Send all remaining fragments at once //================= void Netchan_PushAllFragments( netchan_t *chan ) { while( chan->unsentFragments ) Netchan_TransmitNextFragment( chan ); } //=============== //Netchan_Transmit // //Sends a message to a connection, fragmenting if necessary //A 0 length will still generate a packet. //================ void Netchan_Transmit( netchan_t *chan, msg_t *msg ) { msg_t send; qbyte send_buf[MAX_PACKETLEN]; if( !msg || !msg->data ) return; if( msg->cursize > MAX_MSGLEN ) { Com_Error( ERR_DROP, "Netchan_Transmit: Excessive length = %i", msg->cursize ); } chan->unsentFragmentStart = 0; chan->unsentIsCompressed = qfalse; // fragment large reliable messages if ( msg->cursize >= FRAGMENT_SIZE ) { chan->unsentFragments = qtrue; chan->unsentLength = msg->cursize; chan->unsentIsCompressed = msg->compressed; memcpy( chan->unsentBuffer, msg->data, msg->cursize ); // only send the first fragment now Netchan_TransmitNextFragment( chan ); return; } // write the packet header MSG_Init( &send, send_buf, sizeof(send_buf) ); MSG_Clear( &send ); MSG_WriteLong( &send, chan->outgoingSequence ); // wsw : jal : by now our header sends incoming ack too (q3 doesn't) // wsw : jal : also add compressed information if it's compressed if( msg->compressed ) MSG_WriteLong( &send, chan->incomingSequence | FRAGMENT_BIT ); else MSG_WriteLong( &send, chan->incomingSequence ); chan->outgoingSequence++; // send the qport if we are a client if ( chan->sock == NS_CLIENT ) { MSG_WriteShort( &send, qport->integer ); } MSG_Write( &send, msg->data, msg->cursize ); // send the datagram NET_SendPacket( chan->sock, send.cursize, send.data, chan->remoteAddress ); if ( showpackets->integer ) { Com_Printf( "%s send %4i : s=%i ack=%i\n" , SockString( chan->sock ) , send.cursize , chan->outgoingSequence - 1 , chan->incomingSequence ); } } //================= //Netchan_Process // //Returns qfalse if the message should not be processed due to being //out of order or a fragment. // //Msg must be large enough to hold MAX_MSGLEN, because if this is the //final fragment of a multi-part message, the entire thing will be //copied out. //================= qboolean Netchan_Process( netchan_t *chan, msg_t *msg ) { int sequence, sequence_ack; int qport = -1; int fragmentStart, fragmentLength; qboolean fragmented = qfalse; int headerlength; qboolean compressed = qfalse; qboolean lastfragment = qfalse; // get sequence numbers MSG_BeginReading( msg ); sequence = MSG_ReadLong( msg ); sequence_ack = MSG_ReadLong( msg ); // wsw : jal : by now our header sends incoming ack too (q3 doesn't) // check for fragment information if ( sequence & FRAGMENT_BIT ) { sequence &= ~FRAGMENT_BIT; fragmented = qtrue; if( net_showfragments->integer & 1 && chan->sock == NS_CLIENT ) { Com_Printf( "Process fragmented packet (%s) (id:%i)\n", SockString( chan->sock ), sequence ); } if( net_showfragments->integer & 2 && chan->sock == NS_SERVER ) { Com_Printf( "Process fragmented packet (%s) (id:%i)\n", SockString( chan->sock ), sequence ); } } else { fragmented = qfalse; } // wsw : jal : check for compressed information if ( sequence_ack & FRAGMENT_BIT ) { sequence_ack &= ~FRAGMENT_BIT; compressed = qtrue; if( !fragmented ) msg->compressed = qtrue; } // read the qport if we are a server if ( chan->sock == NS_SERVER ) { qport = MSG_ReadShort( msg ); } // read the fragment information if ( fragmented ) { fragmentStart = MSG_ReadShort( msg ); fragmentLength = MSG_ReadShort( msg ); if( fragmentLength & FRAGMENT_LAST ) { lastfragment = qtrue; fragmentLength &= ~FRAGMENT_LAST; } } else { fragmentStart = 0; // stop warning message fragmentLength = 0; } if ( showpackets->integer ) { if ( fragmented ) { Com_Printf( "%s recv %4i : s=%i fragment=%i,%i\n" , SockString( chan->sock ) , msg->cursize , sequence , fragmentStart, fragmentLength ); } else { Com_Printf( "%s recv %4i : s=%i\n" , SockString( chan->sock ) , msg->cursize , sequence ); } } // // discard out of order or duplicated packets // if( sequence <= chan->incomingSequence ) { if ( showdrop->integer || showpackets->integer ) { Com_Printf( "%s:Out of order packet %i at %i\n" , NET_AdrToString( &chan->remoteAddress ) , sequence , chan->incomingSequence ); } return qfalse; } // // dropped packets don't keep the message from being used // chan->dropped = sequence - (chan->incomingSequence+1); if( chan->dropped > 0 ) { if( showdrop->integer || showpackets->integer ) { Com_Printf( "%s:Dropped %i packets at %i\n" , NET_AdrToString( &chan->remoteAddress ) , chan->dropped , sequence ); } } // // if this is the final framgent of a reliable message, // bump incoming_reliable_sequence // if( fragmented ) { // TTimo // make sure we add the fragments in correct order // either a packet was dropped, or we received this one too soon // we don't reconstruct the fragments. we will wait till this fragment gets to us again // (NOTE: we could probably try to rebuild by out of order chunks if needed) if ( sequence != chan->fragmentSequence ) { chan->fragmentSequence = sequence; chan->fragmentLength = 0; } // if we missed a fragment, dump the message if( fragmentStart != chan->fragmentLength ) { if( showdrop->integer || showpackets->integer ) { Com_Printf( "%s:Dropped a message fragment\n" , NET_AdrToString( &chan->remoteAddress ) , sequence); } // we can still keep the part that we have so far, // so we don't need to clear chan->fragmentLength return qfalse; } // copy the fragment to the fragment buffer if( fragmentLength < 0 || msg->readcount + fragmentLength > msg->cursize || chan->fragmentLength + fragmentLength > sizeof( chan->fragmentBuffer ) ) { if( showdrop->integer || showpackets->integer ) { Com_Printf("%s:illegal fragment length\n" , NET_AdrToString( &chan->remoteAddress ) ); } return qfalse; } memcpy( chan->fragmentBuffer + chan->fragmentLength, msg->data + msg->readcount, fragmentLength ); chan->fragmentLength += fragmentLength; // if this wasn't the last fragment, don't process anything if( !lastfragment ) { return qfalse; } if( chan->fragmentLength > msg->maxsize ) { Com_Printf( "%s:fragmentLength %i > msg->maxsize\n" , NET_AdrToString( &chan->remoteAddress ), chan->fragmentLength ); return qfalse; } // wsw : jal : reconstruct the message MSG_Clear( msg ); MSG_WriteLong( msg, sequence ); MSG_WriteLong( msg, sequence_ack ); if( chan->sock == NS_SERVER ) MSG_WriteShort( msg, qport ); msg->compressed = compressed; headerlength = msg->cursize; MSG_Write( msg, chan->fragmentBuffer, chan->fragmentLength ); msg->readcount = headerlength; // put read pointer after header again chan->fragmentLength = 0; //let it be finished as standard packets } // the message can now be read from the current message pointer chan->incomingSequence = sequence; // wsw : jal[start] : get the ack from the very first fragment chan->incoming_acknowledged = sequence_ack; // wsw : jal[end] return qtrue; } //============================================================================== /* ============================================================================= LOOPBACK BUFFERS FOR LOCAL PLAYER ============================================================================= */ #define MAX_LOOPBACK 4 typedef struct { qbyte data[MAX_MSGLEN]; int datalen; } loopmsg_t; typedef struct { loopmsg_t msgs[MAX_LOOPBACK]; int get, send; } loopback_t; loopback_t loopbacks[2]; qboolean NET_GetLoopPacket( netsrc_t sock, netadr_t *net_from, msg_t *net_message ) { int i; loopback_t *loop; loop = &loopbacks[sock]; if( loop->send - loop->get > (MAX_LOOPBACK - 1) ) // wsw : jal (from q2pro) loop->get = loop->send - MAX_LOOPBACK + 1; // wsw : jal (from q2pro) if ( loop->get >= loop->send ) return qfalse; i = loop->get & (MAX_LOOPBACK-1); loop->get++; memcpy( net_message->data, loop->msgs[i].data, loop->msgs[i].datalen ); net_message->cursize = loop->msgs[i].datalen; memset( net_from, 0, sizeof(*net_from) ); net_from->type = NA_LOOPBACK; return qtrue; } void NET_SendLoopPacket( netsrc_t sock, int length, void *data ) { int i; loopback_t *loop; loop = &loopbacks[sock^1]; i = loop->send & (MAX_LOOPBACK-1); loop->send++; memcpy( loop->msgs[i].data, data, length ); loop->msgs[i].datalen = length; } /* =================== NET_GetPacket Traps "localhost" for loopback, passes everything else to system =================== */ // jal : q3 doesn't do it this way qboolean NET_GetPacket( netsrc_t sock, netadr_t *net_from, msg_t *net_message ) { if( NET_GetLoopPacket(sock, net_from, net_message) ) return qtrue; return Sys_GetPacket( sock, net_from, net_message ); } /* =================== NET_SendPacket Traps "localhost" for loopback, passes everything else to system =================== */ void NET_SendPacket( netsrc_t sock, int length, /*const*/ void *data, netadr_t to ) { if( to.type < NA_LOOPBACK ) return; if ( to.type == NA_LOOPBACK ) { NET_SendLoopPacket( sock, length, data ); return; } Sys_SendPacket( sock, length, data, to ); } /* =================== NET_AdrToString =================== */ char *NET_AdrToString( netadr_t *a ) { static char s[64]; if( a->type == NA_LOOPBACK ) Q_snprintfz (s, sizeof(s), "loopback"); else if( a->type == NA_IP || a->type == NA_BROADCAST ) Q_snprintfz (s, sizeof(s), "%i.%i.%i.%i:%hu", a->ip[0], a->ip[1], a->ip[2], a->ip[3], BigShort(a->port)); else Q_snprintfz (s, sizeof(s), "%02x%02x%02x%02x:%02x%02x%02x%02x%02x%02x:%hu", a->ipx[0], a->ipx[1], a->ipx[2], a->ipx[3], a->ipx[4], a->ipx[5], a->ipx[6], a->ipx[7], a->ipx[8], a->ipx[9], BigShort(a->port)); //ntohs(a->port)); // was in win version return s; } /* =================== NET_CompareBaseAdr Compares without the port =================== */ qboolean NET_CompareBaseAdr( netadr_t *a, netadr_t *b ) { if (a->type != b->type) return qfalse; if (a->type == NA_LOOPBACK) return qtrue; if (a->type == NA_IP) { if (a->ip[0] == b->ip[0] && a->ip[1] == b->ip[1] && a->ip[2] == b->ip[2] && a->ip[3] == b->ip[3]) return qtrue; return qfalse; } if (a->type == NA_IPX) { if ((memcmp(a->ipx, b->ipx, 10) == 0)) return qtrue; return qfalse; } Com_Printf( "NET_CompareBaseAdr: bad address type\n" ); return qfalse; } /* =================== NET_CompareAdr Compares with the port =================== */ qboolean NET_CompareAdr( netadr_t *a, netadr_t *b ) { if (a->type != b->type) return qfalse; if (a->type == NA_LOOPBACK) return qtrue; if (a->type == NA_IP) { if (a->ip[0] == b->ip[0] && a->ip[1] == b->ip[1] && a->ip[2] == b->ip[2] && a->ip[3] == b->ip[3] && a->port == b->port) return qtrue; return qfalse; } if (a->type == NA_IPX) { if ((memcmp(a->ipx, b->ipx, 10) == 0) && a->port == b->port) return qtrue; return qfalse; } Com_Printf ("NET_CompareAdr: bad address type\n"); return qfalse; } /* =================== NET_IsLocalAddress =================== */ qboolean NET_IsLocalAddress( netadr_t *adr ) { return (adr->type == NA_LOOPBACK); } /* ============= NET_StringToAdr Traps "localhost" for loopback, passes everything else to system ============= */ //jal: Does not match with Q3 one. qboolean NET_StringToAdr( char *s, netadr_t *a ) { qboolean ret; // trap localhost if( !strcmp(s, "localhost") ) { memset( a, 0, sizeof(*a) ); a->type = NA_LOOPBACK; return qtrue; } // colon filtering not implemented by now ret = Sys_StringToAdr( s, a ); // call system function if( !ret ) { a->type = NA_NOTRANSMIT; return qfalse; } // inet_addr returns this if out of range if( a->ip[0] == 255 && a->ip[1] == 255 && a->ip[2] == 255 && a->ip[3] == 255 ) { a->type = NA_NOTRANSMIT; return qfalse; } //port replacing not implemented by now return ret; }