SA-MP/server/amx/amxDGram.c
2024-06-23 22:11:12 +08:00

353 lines
9.7 KiB
C

/* Datagram sending/receiving module for the Pawn Abstract Machine
*
* This module uses the UDP protocol (from the TCP/IP protocol suite).
*
* Copyright (c) ITB CompuPhase, 2005
*
* This software is provided "as-is", without any express or implied warranty.
* In no event will the authors be held liable for any damages arising from
* the use of this software.
*
* Permission is granted to anyone to use this software for any purpose,
* including commercial applications, and to alter it and redistribute it
* freely, subject to the following restrictions:
*
* 1. The origin of this software must not be misrepresented; you must not
* claim that you wrote the original software. If you use this software in
* a product, an acknowledgment in the product documentation would be
* appreciated but is not required.
* 2. Altered source versions must be plainly marked as such, and must not be
* misrepresented as being the original software.
* 3. This notice may not be removed or altered from any source distribution.
*
* Version: $Id: amxDGram.c 3367 2005-07-24 12:25:04Z thiadmer $
*/
#include <assert.h>
#include <stdio.h>
#if defined LINUX
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <netdb.h>
#else
#include <malloc.h>
#include <winsock.h>
#endif
#include "osdefs.h"
#include "amx.h"
#define SRC_BUFSIZE 22
#define BUFLEN 512
#define AMX_DGRAMPORT 9930 /* default port */
static int sLocal;
static unsigned long udp_GetHostAddr(const char *host,int index)
{
unsigned long addr=inet_addr(host);
if (addr==0xffffffffL) {
struct hostent FAR *phost=gethostbyname(host);
if (phost!=NULL) {
/* count the number of addresses in the list */
int count;
for (count=0; phost->h_addr_list[count]!=0; count++)
/* nothing */;
if (index<count)
addr=*(unsigned long *)phost->h_addr_list[index];
} /* if */
} /* if */
return addr;
}
static int udp_Open(void)
{
#if defined __WIN32 || defined _WIN32 || defined WIN32
WORD wVersionRequested = MAKEWORD(1,1);
WSADATA wsaData;
#endif
int optval = 1;
#if defined __WIN32 || defined _WIN32 || defined WIN32
WSAStartup(wVersionRequested, &wsaData);
#endif
if ((sLocal=socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) == -1)
return -1;
if (setsockopt(sLocal, SOL_SOCKET, SO_BROADCAST, (void*)&optval, sizeof optval) == -1)
return -1;
return sLocal;
}
static int udp_Close(void)
{
if (sLocal>=0) {
#if defined __WIN32 || defined _WIN32 || defined WIN32
closesocket(sLocal);
#else
close(sLocal);
#endif
} /* if */
#if defined __WIN32 || defined _WIN32 || defined WIN32
WSACleanup();
#endif
return 0;
}
static int udp_Send(const char *host,short port,const char *message,int size)
{
struct sockaddr_in sRemote;
if (sLocal<0)
return -1;
memset((void *)&sRemote,sizeof sRemote,0);
sRemote.sin_family=AF_INET;
sRemote.sin_port=htons(port);
sRemote.sin_addr.s_addr= (host==NULL) ? htonl(INADDR_BROADCAST) : udp_GetHostAddr(host,0);
if (sendto(sLocal,message,size,0,(struct sockaddr *)&sRemote,sizeof sRemote)==-1)
return -1;
return size;
}
/* This call is blocking
* if source is not NULL, it must point to a buffer that can contain at least
* 22 characters.
*/
static int udp_Receive(char *message,size_t maxmsg,char *source)
{
struct sockaddr_in sSource;
int slen=sizeof(sSource);
int size;
size=recvfrom(sLocal, message, maxmsg, 0, (struct sockaddr *)&sSource, &slen);
if (size==-1)
return -1;
if (source!=NULL)
sprintf(source, "%s:%d", inet_ntoa(sSource.sin_addr), ntohs(sSource.sin_port));
return size;
}
static int udp_IsPacket(void)
{
int result;
fd_set rdset;
struct timeval time;
/* the select() function waits until the socket can be read, or until a
* time-out occurs; the time-out is set to 1 microsecond (the shortest
* delay possible).
*/
time.tv_sec=0;
time.tv_usec=1;
FD_ZERO(&rdset);
FD_SET(sLocal,&rdset);
result=select(0,&rdset,NULL,NULL,&time);
if (result==SOCKET_ERROR)
return -1;
return result != 0;
}
static int udp_Listen(short port)
{
struct sockaddr_in sFrom;
memset((void *)&sFrom,sizeof sFrom,0);
sFrom.sin_family=AF_INET;
sFrom.sin_port=htons(port);
sFrom.sin_addr.s_addr=htonl(INADDR_ANY);
if (bind(sLocal,(struct sockaddr *)&sFrom,sizeof sFrom)==-1)
return -1;
return 0;
}
static AMX_DEBUG PrevIdle = NULL;
static int idxReceiveString = -1;
static int idxReceivePacket = -1;
static short dgramPort = 0;
static int dgramBound = 0;
/* sendstring(const message[], const destination[]="")
* destination has the format "127.0.0.1:9930"; when set to an empty string,
* a broadcast is sent.
* To mark the text as a "string", the function inserts a "byte order mark" in
* front of it. It does this for Extended ASCII strings too, although this is
* not entirely correct.
* Returns true on success, false on failure.
*/
static cell AMX_NATIVE_CALL n_sendstring(AMX *amx, cell *params)
{
int r = 0, length;
cell *cstr;
char *host, *message, *ptr;
short port=AMX_DGRAMPORT;
amx_GetAddr(amx, params[1], &cstr);
amx_UTF8Len(cstr, &length);
if ((message = alloca(length + 3 + 1)) != NULL) {
/* insert the byte order mark (BOM) */
message[0]='\xef';
message[1]='\xbb';
message[2]='\xbf';
/* if this is a wide string, convert it to UTF-8 */
if ((ucell)*cstr<=UNPACKEDMAX) {
ptr=message+3;
while (*cstr!=0)
amx_UTF8Put(ptr, &ptr, length - (ptr-message), *cstr++);
*ptr='\0';
} else {
amx_GetString(message+3, cstr, 0, UNLIMITED);
} /* if */
amx_StrParam(amx, params[2], host);
if (host != NULL && (ptr=strchr(host,':'))!=NULL && isdigit(ptr[1])) {
*ptr++='\0';
port=(short)atoi(ptr);
} /* if */
r= (udp_Send(host,port,message,strlen(message)+1) > 0);
} /* if */
return r;
}
/* sendpacket(const packet[], size, const destination[]="")
* destination has the format "127.0.0.1:9930"; when set to an empty string,
* a broadcast is sent.
* Returns true on success, false on failure.
*/
static cell AMX_NATIVE_CALL n_sendpacket(AMX *amx, cell *params)
{
cell *cstr;
char *host, *ptr;
short port=AMX_DGRAMPORT;
amx_GetAddr(amx, params[1], &cstr);
amx_StrParam(amx, params[3], host);
if (host != NULL && (ptr=strchr(host,':'))!=NULL && isdigit(ptr[1])) {
*ptr++='\0';
port=(short)atoi(ptr);
} /* if */
return (udp_Send(host,port,(const char *)cstr,params[2] * sizeof(cell)) > 0);
}
/* listenport(port)
* A program must call this function from main() or another start-up function
* because the module will use the default port 9930 otherwise.
*/
static cell AMX_NATIVE_CALL n_listenport(AMX *amx, cell *params)
{
(void)amx;
dgramPort = (short)params[1];
return 0;
}
static int AMXAPI amx_DGramIdle(AMX *amx)
{
char message[BUFLEN], source[SRC_BUFSIZE];
cell amx_addr_msg, amx_addr_src;
int len, chars;
int err=0;
assert(idxReceiveString >= 0 || idxReceivePacket >= 0);
if (PrevIdle != NULL)
PrevIdle(amx);
/* set up listener (first call only) */
if (!dgramBound) {
if (dgramPort==0)
dgramPort=AMX_DGRAMPORT; /* use default port if none was set */
if (udp_Listen(dgramPort)==-1)
return AMX_ERR_GENERAL;
dgramBound=1;
} /* if */
if (udp_IsPacket()) {
len=udp_Receive(message, sizeof message / sizeof message[0], source);
amx_PushString(amx,&amx_addr_src,NULL,source,1,0);
/* check the presence of a byte order mark: if it is absent, the received
* packet is no string; also check the packet size against string length
*/
if ((message[0]!='\xef' || message[1]!='\xbb' || message[2]!='\xbf'
|| len!=(int)strlen(message)+1 || idxReceiveString<0) && idxReceivePacket>=0)
{
/* receive as "packet" */
amx_Push(amx,len);
amx_PushArray(amx,&amx_addr_msg,NULL,(cell*)message,len);
err=amx_Exec(amx,NULL,idxReceivePacket);
} else {
const char *msg=message;
if (msg[0]=='\xef' && msg[1]=='\xbb' && msg[2]=='\xbf')
msg+=3; /* skip BOM */
/* optionally convert from UTF-8 to a wide string */
if (amx_UTF8Check(msg,&chars)==AMX_ERR_NONE) {
cell *array=alloca((chars+1)*sizeof(cell));
cell *ptr=array;
if (array!=NULL) {
while (err==AMX_ERR_NONE && *msg!='\0')
amx_UTF8Get(msg,&msg,ptr++);
*ptr=0; /* zero-terminate */
amx_PushArray(amx,&amx_addr_msg,NULL,array,chars+1);
} /* if */
} else {
amx_PushString(amx,&amx_addr_msg,NULL,msg,1,0);
} /* if */
err=amx_Exec(amx,NULL,idxReceiveString);
} /* if */
while (err==AMX_ERR_SLEEP)
err=amx_Exec(amx,NULL,AMX_EXEC_CONT);
amx_Release(amx,amx_addr_msg);
amx_Release(amx,amx_addr_src);
} /* if */
return err;
}
#if defined __cplusplus
extern "C"
#endif
AMX_NATIVE_INFO dgram_Natives[] = {
{ "sendstring", n_sendstring },
{ "sendpacket", n_sendpacket },
{ "listenport", n_listenport },
{ NULL, NULL } /* terminator */
};
int AMXEXPORT amx_DGramInit(AMX *amx)
{
dgramBound = 0;
if (udp_Open()==-1)
return AMX_ERR_GENERAL;
/* see whether there is an @receivestring() function */
if (amx_FindPublic(amx,"@receivestring",&idxReceiveString)==AMX_ERR_NONE
|| amx_FindPublic(amx,"@receivepacket",&idxReceivePacket)==AMX_ERR_NONE)
{
if (amx_GetUserData(amx,AMX_USERTAG('I','d','l','e'),(void**)&PrevIdle)!=AMX_ERR_NONE)
PrevIdle=NULL;
amx_SetUserData(amx,AMX_USERTAG('I','d','l','e'),amx_DGramIdle);
} /* if */
return amx_Register(amx,dgram_Natives,-1);
}
int AMXEXPORT amx_DGramCleanup(AMX *amx)
{
(void)amx;
udp_Close();
return AMX_ERR_NONE;
}