View Issue Details

IDProjectCategoryView StatusLast Update
0000163LDMudNetworkingpublic2004-11-26 21:05
ReporterlarsAssigned To 
PrioritynormalSeverityfeatureReproducibilityN/A
Status newResolutionopen 
Summary0000163: TCP Support
DescriptionShort: TCP Support
Type: Feature
State: Unclassified
From: Marcus Meissner <marcus@jet.franken.de>
From: Lars
Date: Tue, 22 Dec 1998 11:57:41 +0100 (MET)
See also: f-991104-0, p-020912, p-021021

See also: MudOS, Anarres version, regarding the telnet machine.

Ich meine damit, erstmal genau ueberlegen, wie die API aussehen soll
und nicht einfach die UNIX socket* functions nachbilden, sonder etwas
LPC bezogenes basteln.

Ich hab mal drueber nachgedacht, einige Punkte, die wir beachten muessen:

- Wir muessen immer nonblocking arbeiten.
  -> Writes muessen wir wohl im Driver buffern, weil das zu kompliziert fuer
     LPC Benutzer ist.
  -> Reads sollten als Callbacks durchgegeben werden.
  -> accept/connect Behandlung braucht extra callbacks.

- Kein neuer LPC Variablentyp, das wuerde nur den Driver komplizieren.

Soweit ich mit Gedanken gekommen bin:
- Sockets werden in LPC spezifiziert durch die lokale Portnummer (die ist
  fuer TCP unique auf dem Rechner, nur wird der Portnummer Adressraum von UDP
  ueberlappt...Sprich, man muesste UDP anders behandeln. Und UNIX Domain
  sockets, und IPX ... )
- Wer darf write ausfuehren? Nur der Master, der dann evt. dieses Privileg
  per closure/functionptr weitergibt? Oder sollten wir nen Socket an ein Objekt
  binden?
-> Generell Privileg Vergabe abklaeren.

Insgesamt hatte ich ungefaehre folgende API im Kopf entwickelt erstmal,
die noch einige unklare Stellen hat:

# Macht socket/bind/listen/ socket-> select auf readability
tcp_accept(int localport,closure _accept_fun); // eventuell hostmask ?

# Wenn der accept socket readable wird ... accept und dann diese Funktion
# aufrufen.
_accept_fun(int localportnr,string remotehost,int remoteport) {
        /* Problem: Wir muessen dem GD jetzt irgendwie sagen, wem dieser
         * socket gehoert und wo er readcallbacks aufrufen soll
         */
}

# write(2) ... Mit buffering im driver. Schreibt immer soviel wie angegeben aus
# LPC sicht, wirft NICHTS weg.
void tcp_write(localport,int*|string);

# close(2) ... sollte den rest des obigen writebuffers leeren und dann erst
# den socket schliessen. Bzw. das durch evt. zusaetzliches Flag machen.
void tcp_close(localport);

# socket(2)/(connect(2) nonblocking). Ruft _connect_fun auf, wenn select(2)
# Schreibbarkeit oder Lesbarkeit signalisiert.
void tcp_connect(string remotehost,int remoteport,closure _connect_fun);

# bei readable/writeable connect aufgerufen. (remotehost/remoteport evt.
# unnoetig)
_connect_fun(int localportnr,string remotehost,int remoteport) {
        /* sollte irgendwie jetzt auch dem GD sagen, wem der Socket gehoert
         * und wo er readcallbacks aufrufen soll
         */
}

Dass Problem ist jetzt das spezifizieren von readcallbacks. Evt. kann man
die closure als returnwert von _connect_fun oder _accept_fun zurueckgeben,
oder noch eine API function verwenden, wie zB

tcp_bind(int localportnr,closure _socket_callback);

_socket_callback(int localport,int type,mixed stuff) {
        /*type: SOCKET_READ
         * stuff enthaelt entweder int* oder string (je nach intelligenter
         * binary detection.
         *type: SOCKET_ERROR
         * hmm
         *type: SOCKET_CLOSE
         * hmm
         */
}

Ueber besonders das letztere bin ich mir noch nicht ganz klar.

Vielleiocht sollte ich mir auch erstmal die MudOS implementation ansehen, bevor
ich mir NIH vorwerfen lasse :)

And lars wrote:

> Sprich, es spricht kaum was gegen ans Objekte binden.
> Ausser, wie genau man das nun machen sollte.

Driverintern relativ einfach, da mit der interactive-sentence die
notwendige Basisarchitektur vorhanden ist.

> Problem gibts wohl nur bei den leidigen Referencecounter.

?

> > > # write(2) ... Mit buffering im driver. Schreibt immer soviel wie
> > > # angegeben aus LPC sicht, wirft NICHTS weg.
> > > void tcp_write(localport,int*|string);
> >
> > Das wuerde ich socket_write() nennen, und zumindest einen Fehlercode
> > zurueckgeben lassen. Prinzipiell koennte der Socket ja auch ein UDP-
> > oder sonstwas Socket sein, 'tcp_' waere dann irrefuehrend.
>
> Hmm. Dann sollte man aber mit mehr als localport arbeiten, weil sich
> die portnummerraeume von TCP und UDP ueberschneiden.
> Oder man nimmt als Unique ID einen string mit "tcp:nr" oder "udp:nr".

Es sit moeglich ueber einen Socket/Port gleichzeitig TCP und UDP zu machen
(habs noch nie probiert und die TCP/IP-Buecher sind in der Bibliothek) - oder
iaW: TCP und UDP sind keine Namensraeume an sich.

Eine Unique-ID eruebrigt sich, da der Socket durch das Objekt definiert
ist. Funktionen wie socket_write() wuerden dann entweder ein
Socketobjekt als erstes Argument erwarten, oder nur funktionieren wenn
this_object() ein Socketobject ist.

query_mud_port(socket_object) wuerde die Portnummer liefern,
query_socket_type(socket_object) das Protokoll ("tcp", "udp", oder
"player" fuer normale Playerobjekte), query(_once)_interactive() koennte
sowas wie 'tcp:4242' zurueckgeben. Fuer die Remoteadressen hats bereits
query_ip_name(), man koennte da noch query_ip_port() hinzufuegen.

Ich schreib jetzt mal Funktionsideen auf wie sie mir gerade einfallen
(oder in deiner vorigen Mail stehen). Wenn nicht anders gesagt, setzen
die Funktionen voraus, dass this_object() das betroffene socketobjekt
ist.

void net_bind(int portno, string protocol)
  erzeugt einen Socket fuer Port <portno> (0 fuer 'any') und <protocol>
  ("udp", "tcp",...) und bindet ihn an this_object().

void net_accept(object obj, mixed extra...)
void net_accept(int portno, object obj, mixed extra...)
  Akzeptiert eine TCP-Verbindung, bindet sie an <obj> und ruft
  <obj>::_accepted(mixed extra...) auf. In der zweiten Form fuehrt
  accept() erstmal ein bind(portno, "tcp") wenn this_object kein
  socketobject ist, bzw. wirft einen Fehler wenn die Portnummer des
  gebundenen Sockets nicht mit <portno> uebereinstimmt.

void net_connect(string remotehost,int remoteport, mixed extra...);
void net_connect(int portno, string remotehost,int remoteport, mixed
extra...);
  Versucht eine Verbindung mit der gegebenen Adresse herzustellen.
  Klappt das, wird this_object::_connected(mixed extra...) aufgerufen.
  Ist this_object noch kein Socketobjekt, wird vorher noch ein
  bind(any, "tcp") ausgefuehrt etc.

void net_write(string|int* data)
  Schreibt <data> auf den Socket.

void net_read(int amount, mixed extra...)
  Liest den naechsten Block Daten, max. amount bytes, vom Socket und
  ruft this_object::_read(int* data, mixed extra...) auf.

void net_close(bool noflush)
  Schliesst den Socket, optional ohne die noch anhaengigen Daten
  wegzuschreiben.

Es braeuchte dann noch einen Fehlercallback, der asynchrone Fehler
meldet:

void _error(int(string?) type, int errcode, mixed additional...)
  wobei <type> zwischen connect, accept, read und unexpected close
  (z.B. durch destruct des Socketobjektes) unterscheidet.

Synchrone Fehler in der Ausfuehrung der efuns selber erzeugen ganz
normale runtime errors.
TagsNo tags attached.
External Data (URL)

Activities

lars

2004-11-26 20:57

reporter   ~0000212

p-02912: TCP Sockets as implemented by Batmud:

Short: TCP sockets for LDMud
From: Tomi Valkeinen
Date: 2002-09-12
Type: Patch
State: New
Driver: 3.2.9-dev.248
See also: f-981226-1, f-991104-0, p-021021

UDP is not handled, hardly documented, modelled after MudOS, but stable.

Steps:
1. Apply the diff
2. Replace the socket.c with the one provided below

2004-11-26 21:03

 

p-020912 (49,928 bytes)   
Short: TCP sockets for LDMud
From: Tomi Valkeinen
Date: 2002-09-12
Type: Patch
State: New
Driver: 3.2.9-dev.248
See also: f-981226-1, f-991104-0, p-021021

UDP is not handled, hardly documented, modelled after MudOS, but stable.

Steps:
1. Apply the diff
2. Replace the socket.c with the one provided below

----------------- Diff -------------------
diff -burN 3-3/doc/efun/socket_accept 3-3.sock/doc/efun/socket_accept
--- 3-3/doc/efun/socket_accept	Thu Jan  1 02:00:00 1970
+++ 3-3.sock/doc/efun/socket_accept	Thu Sep 12 14:16:34 2002
@@ -0,0 +1,9 @@
+SYNOPSIS
+        int socket_accept(int fd, closure read_callback,
+                          closure close_callback)
+
+DESCRIPTION
+        Returns SE_SUCCESS or the error code.
+
+SEE ALSO
+
diff -burN 3-3/doc/efun/socket_address 3-3.sock/doc/efun/socket_address
--- 3-3/doc/efun/socket_address	Thu Jan  1 02:00:00 1970
+++ 3-3.sock/doc/efun/socket_address	Thu Sep 12 14:17:47 2002
@@ -0,0 +1,8 @@
+SYNOPSIS
+        string socket_send(int fd)
+
+DESCRIPTION
+        Returns the remote address of the socket.
+
+SEE ALSO
+
diff -burN 3-3/doc/efun/socket_bind 3-3.sock/doc/efun/socket_bind
--- 3-3/doc/efun/socket_bind	Thu Jan  1 02:00:00 1970
+++ 3-3.sock/doc/efun/socket_bind	Thu Sep 12 14:13:20 2002
@@ -0,0 +1,8 @@
+SYNOPSIS
+        int socket_bind(int fd, int port)
+
+DESCRIPTION
+        Returns SE_SUCCESS or the error code.
+
+SEE ALSO
+
diff -burN 3-3/doc/efun/socket_close 3-3.sock/doc/efun/socket_close
--- 3-3/doc/efun/socket_close	Thu Jan  1 02:00:00 1970
+++ 3-3.sock/doc/efun/socket_close	Thu Sep 12 14:12:01 2002
@@ -0,0 +1,8 @@
+SYNOPSIS
+        int socket_close(int fd)
+
+DESCRIPTION
+        Returns SE_SUCCESS or the error code.
+
+SEE ALSO
+
diff -burN 3-3/doc/efun/socket_connect 3-3.sock/doc/efun/socket_connect
--- 3-3/doc/efun/socket_connect	Thu Jan  1 02:00:00 1970
+++ 3-3.sock/doc/efun/socket_connect	Thu Sep 12 14:14:16 2002
@@ -0,0 +1,10 @@
+SYNOPSIS
+        int socket_connect(int fd, string address, closure connect_callback,
+                           closure read_callback)
+
+DESCRIPTION
+        The address is in the form "ipnumber:port".
+        Returns SE_SUCCESS or the error code.
+
+SEE ALSO
+
diff -burN 3-3/doc/efun/socket_create 3-3.sock/doc/efun/socket_create
--- 3-3/doc/efun/socket_create	Thu Jan  1 02:00:00 1970
+++ 3-3.sock/doc/efun/socket_create	Thu Sep 12 14:11:18 2002
@@ -0,0 +1,9 @@
+SYNOPSIS
+        int socket_create(closure close_callback)
+
+DESCRIPTION
+        Returns fd (zero or positive) for the newly created socket or
+        error number (negative) if the creation failed.
+
+SEE ALSO
+
diff -burN 3-3/doc/efun/socket_listen 3-3.sock/doc/efun/socket_listen
--- 3-3/doc/efun/socket_listen	Thu Jan  1 02:00:00 1970
+++ 3-3.sock/doc/efun/socket_listen	Thu Sep 12 14:14:41 2002
@@ -0,0 +1,8 @@
+SYNOPSIS
+        int socket_listen(int fd, closure listen_callback)
+
+DESCRIPTION
+        Returns SE_SUCCESS or the error code.
+
+SEE ALSO
+
diff -burN 3-3/doc/efun/socket_send 3-3.sock/doc/efun/socket_send
--- 3-3/doc/efun/socket_send	Thu Jan  1 02:00:00 1970
+++ 3-3.sock/doc/efun/socket_send	Thu Sep 12 14:17:17 2002
@@ -0,0 +1,8 @@
+SYNOPSIS
+        int socket_send(int fd, string data)
+
+DESCRIPTION
+        Returns SE_SUCCESS or the error code.
+
+SEE ALSO
+
diff -burN 3-3/doc/efun/socket_strerror 3-3.sock/doc/efun/socket_strerror
--- 3-3/doc/efun/socket_strerror	Thu Jan  1 02:00:00 1970
+++ 3-3.sock/doc/efun/socket_strerror	Thu Sep 12 14:18:16 2002
@@ -0,0 +1,8 @@
+SYNOPSIS
+        string socket_strerror(int errno)
+
+DESCRIPTION
+        Returns string representation of the given socket error.
+
+SEE ALSO
+
diff -burN 3-3/mudlib/socket1.c 3-3.sock/mudlib/socket1.c
--- 3-3/mudlib/socket1.c	Thu Jan  1 02:00:00 1970
+++ 3-3.sock/mudlib/socket1.c	Thu Sep 12 14:20:56 2002
@@ -0,0 +1,28 @@
+#include "sys/sockets.h"
+
+close_callback(int fd)
+{
+    debug_message(sprintf("close cb %d\n", fd));
+}
+
+read_callback(int fd, string msg)
+{
+    debug_message(sprintf("read cb %d, size %d: '%s'\n", fd, sizeof(msg), msg));
+}
+
+connect_callback(int fd, int err)
+{
+    debug_message(sprintf("connect cb %d, %s\n", fd, socket_strerror(err)));
+
+	if(err == SE_SUCCESS)
+	{
+		socket_send(fd, "hello world\n");
+	}
+}
+
+test()
+{
+    int fd = socket_create( (: close_callback($1) :) );
+
+    socket_connect(fd, "127.0.0.1:9999", #'connect_callback, #'read_callback);
+}
diff -burN 3-3/mudlib/socket2.c 3-3.sock/mudlib/socket2.c
--- 3-3/mudlib/socket2.c	Thu Jan  1 02:00:00 1970
+++ 3-3.sock/mudlib/socket2.c	Thu Sep 12 14:21:00 2002
@@ -0,0 +1,26 @@
+
+
+close_callback(int fd)
+{
+    debug_message(sprintf("close cb %d\n", fd));
+}
+
+read_callback(int fd, string msg)
+{
+    debug_message(sprintf("read cb %d, size %d: '%s'\n", fd, sizeof(msg), msg));
+}
+
+listen_callback(int fd)
+{
+    printf("listen cb %O %O\n", previous_object(), this_object());
+
+    socket_accept(fd, #'read_callback, #'close_callback);
+}
+
+test()
+{
+    int fd = socket_create( #'close_callback );
+
+	socket_bind(fd, 8888);
+	socket_listen(fd, #'listen_callback );
+}
diff -burN 3-3/mudlib/sys/sockets.h 3-3.sock/mudlib/sys/sockets.h
--- 3-3/mudlib/sys/sockets.h	Thu Jan  1 02:00:00 1970
+++ 3-3.sock/mudlib/sys/sockets.h	Thu Sep 12 14:05:11 2002
@@ -0,0 +1,21 @@
+#ifndef LPC_SOCKETS_H_
+#define LPC_SOCKETS_H_ 1
+
+#define SE_SUCCESS 0
+#define SE_UNKNOWN -1
+#define SE_CONNREFUSED -2
+#define SE_HOSTDOWN -3
+#define SE_HOSTUNREACH -4
+#define SE_NOMORESOCKETS -5
+#define SE_CREATESOCKET -6
+#define SE_SETNONBLOCKING -7
+#define SE_SETSOCKOPT -8
+#define SE_BADFD -9
+#define SE_ILLEGALADDR -10
+#define SE_INVALIDPORT -11
+#define SE_PIPE -12
+#define SE_ADDRINUSE -13
+#define SE_INVAL -14
+#define SE_CONNRESET -15
+
+#endif /* LPC_SOCKETS_H_ */
diff -burN 3-3/mudlib/test_master.c 3-3.sock/mudlib/test_master.c
--- 3-3/mudlib/test_master.c	Fri Aug 16 06:03:45 2002
+++ 3-3.sock/mudlib/test_master.c	Thu Sep 12 14:19:20 2002
@@ -47,6 +47,18 @@
         return;
     }
 
+	if (arg == "socket1")
+	{
+		load_object("socket1")->test();
+		return;
+	}
+
+	if (arg == "socket2")
+	{
+		load_object("socket2")->test();
+		return;
+	}
+
     if (arg == "gc")
     {
         garbage_collection();
diff -burN 3-3/src/Makefile.in 3-3.sock/src/Makefile.in
--- 3-3/src/Makefile.in	Wed Aug 21 21:07:36 2002
+++ 3-3.sock/src/Makefile.in	Thu Sep 12 13:36:18 2002
@@ -77,7 +77,7 @@
       parser.c parse.c parse_old.c pkg-alists.c pkg-mysql.c pkg-pcre.c port.c \
       ptrtable.c \
       random.c regexp.c simulate.c simul_efun.c stdstrings.c \
-      strfuns.c sprintf.c swap.c wiz_list.c xalloc.c 
+      strfuns.c sprintf.c swap.c wiz_list.c xalloc.c sockets.c
 OBJ = access_check.o actions.o array.o backend.o bitstrings.o call_out.o \
       closure.o comm.o \
       dumpstat.o ed.o efuns.o files.o gcollect.o hash.o heartbeat.o \
@@ -87,7 +87,7 @@
       parser.o parse.o parse_old.o pkg-alists.o pkg-mysql.o pkg-pcre.o port.o \
       ptrtable.o \
       random.o regexp.o simulate.o simul_efun.o stdstrings.o \
-      strfuns.o sprintf.o swap.o wiz_list.o xalloc.o @ALLOCA@ 
+      strfuns.o sprintf.o swap.o wiz_list.o xalloc.o sockets.o @ALLOCA@ 
 
 driver@EXEEXT@: $(OBJ)
 	$(CC) @OPTIMIZE_LINKING@ $(LDFLAGS) $(OBJ) -o $@ $(LIBS)
diff -burN 3-3/src/backend.c 3-3.sock/src/backend.c
--- 3-3/src/backend.c	Wed Aug 21 21:07:36 2002
+++ 3-3.sock/src/backend.c	Thu Sep 12 13:54:29 2002
@@ -71,6 +71,7 @@
 #include "swap.h"
 #include "wiz_list.h"
 #include "xalloc.h"
+#include "sockets.h"
 
 #include "../mudlib/sys/driver_hook.h"
 #include "../mudlib/sys/debug_message.h"
@@ -646,6 +647,8 @@
             wiz_decay();
         }
 
+		socket_poll();
+		
     } /* end of main loop */
 
     /* NOTREACHED */
diff -burN 3-3/src/func_spec 3-3.sock/src/func_spec
--- 3-3/src/func_spec	Fri Sep  6 10:00:07 2002
+++ 3-3.sock/src/func_spec	Thu Sep 12 13:37:54 2002
@@ -643,4 +643,15 @@
 
 #endif /* USE_MYSQL */
 
+string  socket_strerror(int);
+void    socket_print_stats();
+int     socket_create(closure);
+int     socket_close(int);
+int     socket_connect(int, string, closure, closure);
+int     socket_bind(int, int);
+int     socket_listen(int, closure);
+int     socket_accept(int, closure, closure);
+int     socket_send(int, string);
+string  socket_address(int);
+
 /***************************************************************************/
diff -burN 3-3/src/main.c 3-3.sock/src/main.c
--- 3-3/src/main.c	Thu Sep  5 00:02:49 2002
+++ 3-3.sock/src/main.c	Thu Sep 12 13:55:23 2002
@@ -68,6 +68,7 @@
 #include "swap.h"
 #include "wiz_list.h"
 #include "xalloc.h"
+#include "sockets.h"
 
 /*-------------------------------------------------------------------------*/
 
@@ -441,6 +442,8 @@
 #endif /* ERQ_DEMON */
     initialize_host_ip_number();
 
+	socket_init();
+	
     (void)signal(SIGFPE, SIG_IGN);
     current_object = &dummy_current_object_for_loads;
     if (setjmp(toplevel_context.con.text)) {
diff -burN 3-3/src/simulate.c 3-3.sock/src/simulate.c
--- 3-3/src/simulate.c	Wed Sep  4 21:38:36 2002
+++ 3-3.sock/src/simulate.c	Thu Sep 12 13:56:40 2002
@@ -2223,6 +2223,8 @@
     if (ob->flags & O_DESTRUCTED)
         return;
 
+    socket_object_destructed(ob);
+
 #ifdef CHECK_OBJECT_REF
     xallocate(shadow, sizeof(*shadow), "destructed object shadow");
 #endif /* CHECK_OBJECT_REF */
diff -burN 3-3/src/sockets.c 3-3.sock/src/sockets.c
--- 3-3/src/sockets.c	Thu Jan  1 02:00:00 1970
+++ 3-3.sock/src/sockets.c	Thu Sep 12 13:53:38 2002
@@ -0,0 +1,1027 @@
+/*
+ * Sockets for ldmud 3.3
+ *
+ * 5-July-2002 - Tomba @ Batmud
+ *   * First version
+ *
+ * 20-July-2002 - Tomba @ Batmud
+ *   * Added ECONNRESET
+ *   * Fixed socket_address to return empty string on error
+ *   * sock->send_buffer was not always cleared when freed
+ *   * removed a few debug printfs
+ *
+ * TODO: UDP support
+ * TODO: access right handling
+ * TODO: event handling with select (comm.c) instead of poll
+ *
+ */
+
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/poll.h>
+#include <netdb.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <stdio.h>
+
+#include "main.h"
+#include "svalue.h"
+#include "interpret.h"
+#include "simulate.h"
+#include "object.h"
+#include "mstrings.h"
+#include "comm.h"
+#include "actions.h"
+
+#define SE_SUCCESS 0
+#define SE_UNKNOWN -1
+#define SE_CONNREFUSED -2
+#define SE_HOSTDOWN -3
+#define SE_HOSTUNREACH -4
+#define SE_NOMORESOCKETS -5
+#define SE_CREATESOCKET -6
+#define SE_SETNONBLOCKING -7
+#define SE_SETSOCKOPT -8
+#define SE_BADFD -9
+#define SE_ILLEGALADDR -10
+#define SE_INVALIDPORT -11
+#define SE_PIPE -12
+#define SE_ADDRINUSE -13
+#define SE_INVAL -14
+#define SE_CONNRESET -15
+
+static char *socket_errors[] = {
+	"Success",
+	"Unknown error",
+	"Connection refused",
+	"Host is down",
+	"No route to host",
+	"No more mud sockets available",
+	"Failed to create socket",
+	"Failed to set socket to non-blocking mode",
+	"Failed to set socket options",
+	"Bad socket descriptor",
+	"Illegal address",
+	"Invalid port",
+	"Broken pipe",
+	"Address already in use",
+	"Invalid argument",
+	"Connection reset by peer"
+};
+
+
+enum socket_state
+{
+	STATE_UNUSED = 0,
+	STATE_READY,
+	STATE_BOUND,
+	STATE_LISTEN,
+	STATE_CONNECTING,
+	STATE_CONNECTED,
+	STATE_CLOSING
+};
+
+struct socket_struct
+{
+	int fd;
+	int lpc_fd;				   /* "fd" shown to lpc */
+	enum socket_state state;
+	object_t* object;
+	struct sockaddr_in local_addr;
+	struct sockaddr_in remote_addr;
+	svalue_t read_callback;
+	svalue_t close_callback;
+	svalue_t connect_callback;
+	svalue_t listen_callback;
+	char* send_buffer;
+	int send_buffer_size;
+	int send_buffer_used;
+} typedef socket_t;
+
+
+#define MAX_LPC_FD 100
+
+static socket_t g_socket_table[MAX_LPC_FD];
+static struct pollfd g_poll_table[MAX_LPC_FD];
+
+#define READ_BUFFER_SIZE 10240
+static char g_read_buffer[READ_BUFFER_SIZE];
+
+void socket_process_events(socket_t* sock, short events);
+
+
+
+void socket_init()
+{
+	int i;
+	for(i = 0; i < MAX_LPC_FD; i++)
+	{
+		struct socket_struct* sock = &g_socket_table[i];
+	
+		sock->state = STATE_UNUSED;
+		sock->lpc_fd = i;
+
+		g_poll_table[i].fd = -1;
+		g_poll_table[i].events = POLLIN | POLLOUT;
+		g_poll_table[i].revents = 0;
+
+	}
+}
+
+int socket_conv_errno(int err)
+{
+	switch(err)
+	{
+		case ECONNREFUSED:
+			return SE_CONNREFUSED;
+
+		case EHOSTDOWN:
+			return SE_HOSTDOWN;
+
+		case EHOSTUNREACH:
+			return SE_HOSTUNREACH;
+
+		case EPIPE:
+			return SE_PIPE;
+
+		case EADDRINUSE:
+			return SE_ADDRINUSE;
+
+		case EINVAL:
+			return SE_INVAL;
+
+		case ECONNRESET:
+			return SE_CONNRESET;
+
+		default:
+			fprintf(stderr, "socket_conv_errno: unknown system error %d, %s\n", err, strerror(err));
+			return SE_UNKNOWN;
+	}
+}
+
+struct socket_struct* new_socket_entry()
+{
+	int i;
+	
+	for (i = 0; i < MAX_LPC_FD; i++)
+	{
+		struct socket_struct* sock = &g_socket_table[i];
+		
+		if (sock->state == STATE_UNUSED)
+		{
+			sock->fd = -1;
+			sock->send_buffer = 0;
+			sock->send_buffer_size = 0;
+			sock->send_buffer_used = 0;
+		
+			put_number(&sock->read_callback, 0);
+			put_number(&sock->close_callback, 0);
+			put_number(&sock->connect_callback, 0);
+			put_number(&sock->listen_callback, 0);
+		
+			return sock;
+		}
+	}
+	
+	return 0;
+}
+
+void free_socket_entry(socket_t* sock)
+{
+	if(sock != 0)
+	{
+		if(sock->fd != -1)
+		{
+			if(close(sock->fd) == -1)
+			{
+				perror("free_socket_entry: close");
+			}
+
+			sock->fd = -1;
+		}
+	
+		if(sock->send_buffer)
+		{
+			free(sock->send_buffer);
+			sock->send_buffer = 0;
+		}
+
+		sock->state = STATE_UNUSED;
+
+		free_svalue(&sock->read_callback);
+		free_svalue(&sock->close_callback);
+		free_svalue(&sock->connect_callback);
+		free_svalue(&sock->listen_callback);
+	
+		g_poll_table[sock->lpc_fd].fd = -1;
+	}
+}
+
+socket_t* get_socket_entry(int lpc_fd)
+{
+	if(lpc_fd < 0 || lpc_fd >= MAX_LPC_FD ||
+	   g_socket_table[lpc_fd].state == STATE_UNUSED)
+	{
+		return 0;
+	}
+
+	return &g_socket_table[lpc_fd];
+}
+
+void socket_object_destructed(object_t* ob)
+{
+	int i;
+	
+	for(i = 0; i < MAX_LPC_FD; i++)
+	{
+		struct socket_struct* sock = &g_socket_table[i];
+
+		if(sock->state != STATE_UNUSED)
+		{
+			if(sock->object == ob)
+			{
+				free_socket_entry(sock);
+			}
+		}
+	}
+}
+
+
+
+
+
+
+svalue_t* f_socket_strerror(svalue_t* sp)
+{
+	int err = 0 - sp->u.number;
+	sp--;
+
+	if(err < 0 || err > sizeof(socket_errors))
+	{
+		push_string(sp, new_mstring("Unspecified error"));
+	}
+	else
+	{	
+		push_string(sp, new_mstring(socket_errors[err]));
+	}
+	
+	return sp;
+}
+
+svalue_t* f_socket_print_stats(svalue_t* sp)
+{
+	int i;
+	
+	add_message("-- Socket stats --\n");
+		
+	for(i = 0; i < MAX_LPC_FD; i++)
+	{
+		socket_t* sock = &g_socket_table[i];
+	
+		if(sock->state != STATE_UNUSED)
+		{
+			add_message("lpc_fd %d, fd %d, state %d, ob %s\n",
+						i, sock->fd, sock->state, get_txt(sock->object->name));
+		}
+	}
+
+	return sp;
+}
+
+int socket_init_socket(socket_t* sock, int fd, svalue_t* close_callback)
+{
+	int tmp;
+	
+	if(fcntl(fd, F_SETFL, O_NDELAY) == -1)
+	{
+		perror("socket_init_socket: fcntl O_NDELAY");
+
+		return SE_SETNONBLOCKING;
+	}
+
+	tmp = 1;
+	if(setsockopt(fd, SOL_SOCKET, SO_REUSEADDR,
+				  (char *)&tmp, sizeof(tmp)) == -1)
+	{
+		perror("socket_init_socket: setsockopt SO_REUSEADDR");
+
+		return SE_SETSOCKOPT;
+	}
+
+	sock->fd = fd;
+	sock->state = STATE_READY;
+	sock->object = current_object;
+
+	assign_svalue(&sock->close_callback, close_callback);
+	
+	g_poll_table[sock->lpc_fd].fd = sock->fd;
+
+	return 0;
+}
+
+int socket_string_to_sockaddr(char* name, struct sockaddr_in* saddr)
+{
+#define ADDR_BUF_SIZE 512
+	int port;
+	char *cp, addr[ADDR_BUF_SIZE];
+
+	strncpy(addr, name, ADDR_BUF_SIZE);
+
+	cp = strchr(addr, ':');
+	if (cp == NULL)
+	{
+		return 0;
+	}
+	
+	*cp = '\0';
+	port = atoi(cp + 1);
+
+	saddr->sin_family = AF_INET;
+	saddr->sin_port = htons((unsigned short)port);
+	if(inet_aton(addr, &saddr->sin_addr) == 0)
+	{
+		return 0;
+	}
+
+	return 1;
+#undef ADDR_BUF_SIZE
+}
+
+
+
+svalue_t* f_socket_create(svalue_t* sp)
+{
+	int err, fd;
+	socket_t* sock;
+	
+	if(current_object->flags & O_DESTRUCTED)
+	{
+		error("socket_create: this_object has been destructed");
+	}
+
+	sock = new_socket_entry();
+	
+	if(sock == 0)
+	{
+		free_svalue(sp--);
+		push_number(sp, SE_NOMORESOCKETS);
+		return sp;
+	}
+
+	fd = socket(AF_INET, SOCK_STREAM, 0);
+
+	if(fd == -1)
+	{
+		perror("socket_create: socket");
+	
+		free_socket_entry(sock);
+	
+		free_svalue(sp--);
+	
+		push_number(sp, SE_CREATESOCKET);
+		return sp;
+	}
+	
+	err = socket_init_socket(sock, fd, &sp[0]);
+	
+	free_svalue(sp--);
+	
+	if(err != 0)
+	{
+		close(fd);
+	
+		free_socket_entry(sock);
+
+		push_number(sp, err);
+
+		return sp;
+	}
+	
+	push_number(sp, sock->lpc_fd);
+
+	return sp;
+}
+
+svalue_t* f_socket_close(svalue_t* sp)
+{
+	int ret, myerrno;
+
+	int lpc_fd = (sp--)->u.number;
+	
+	socket_t* sock = get_socket_entry(lpc_fd);
+
+	if(sock == 0)
+	{
+		push_number(sp, SE_BADFD);
+		return sp;
+	}
+
+	sock->state = STATE_CLOSING;
+	
+	ret = close(sock->fd);
+	myerrno = errno;
+	sock->fd = -1;
+
+	if(ret == -1)
+	{
+		perror("socket_close: close");
+
+		push_number(sp, socket_conv_errno(myerrno));
+	}
+	else
+	{
+		push_number(sp, SE_SUCCESS);
+	}
+	
+	return sp;
+}
+
+svalue_t* f_socket_connect(svalue_t* sp)
+{
+	int lpc_fd, myerrno;
+	int ret;
+	socket_t* sock;
+
+	lpc_fd = sp[-3].u.number;
+
+	sock = get_socket_entry(lpc_fd);
+	if(sock == 0)
+	{
+		free_svalue(sp--);
+		free_svalue(sp--);
+		free_svalue(sp--);
+		free_svalue(sp--);
+	
+		push_number(sp, SE_BADFD);
+		return sp;
+	}
+
+	if(socket_string_to_sockaddr(get_txt(sp[-2].u.str), &sock->remote_addr) == 0)
+	{
+		free_svalue(sp--);
+		free_svalue(sp--);
+		free_svalue(sp--);
+		free_svalue(sp--);
+	
+		push_number(sp, SE_ILLEGALADDR);
+		return sp;
+	}
+	
+	ret = connect(sock->fd, (struct sockaddr *)&sock->remote_addr,
+				  sizeof(sock->remote_addr));
+	myerrno = errno;
+	
+	if(ret == -1 && myerrno != EINPROGRESS)
+	{
+		fprintf(stderr, "socket_connect: connect failed: %s\n", strerror(myerrno));
+
+		free_svalue(sp--);
+		free_svalue(sp--);
+		free_svalue(sp--);
+		free_svalue(sp--);
+		
+		push_number(sp, socket_conv_errno(myerrno));
+	}
+	else
+	{
+		sock->state = STATE_CONNECTING;
+
+		transfer_svalue(&sock->connect_callback, &sp[-1]);
+		transfer_svalue(&sock->read_callback, &sp[0]);
+
+		free_svalue(sp--);
+		free_svalue(sp--);
+		free_svalue(sp--);
+		free_svalue(sp--);
+
+		push_number(sp, SE_SUCCESS);
+	}
+	
+	return sp;
+}
+
+svalue_t* f_socket_bind(svalue_t* sp)
+{
+	socket_t* sock;
+	int myerrno;
+   
+	int port = (sp--)->u.number;
+	int lpc_fd = (sp--)->u.number;
+
+	sock = get_socket_entry(lpc_fd);
+	if(sock == 0)
+	{
+		push_number(sp, SE_BADFD);
+		return sp;
+	}
+
+	if(port < 0)
+	{
+		push_number(sp, SE_INVALIDPORT);
+		return sp;
+	}
+
+	sock->local_addr.sin_port = htons((unsigned short)port);
+	sock->local_addr.sin_addr.s_addr = INADDR_ANY;
+	sock->local_addr.sin_family = AF_INET;
+
+	if(bind(sock->fd, (struct sockaddr*)&sock->local_addr,
+			sizeof(sock->local_addr)) == -1)
+	{
+		myerrno = errno;
+	
+		perror("socket_bind: bind");
+		push_number(sp, socket_conv_errno(myerrno));
+	}
+	else
+	{
+		sock->state = STATE_BOUND;
+		push_number(sp, SE_SUCCESS);
+	}
+	 
+	return sp;
+}
+
+svalue_t* f_socket_listen(svalue_t* sp)
+{
+	socket_t* sock;
+	int myerrno;
+   
+	int lpc_fd = sp[-1].u.number;
+
+	sock = get_socket_entry(lpc_fd);
+	if(sock == 0)
+	{
+		free_svalue(sp--);
+		free_svalue(sp--);
+		push_number(sp, SE_BADFD);
+		return sp;
+	}
+	
+	transfer_svalue(&sock->listen_callback, &sp[0]);
+	
+	free_svalue(sp--);
+	free_svalue(sp--);
+
+	if(listen(sock->fd, 10) == -1)
+	{
+		myerrno = errno;
+		perror("socket_listen: listen");
+		push_number(sp, socket_conv_errno(myerrno));
+	}
+	else
+	{
+		sock->state = STATE_LISTEN;
+		push_number(sp, SE_SUCCESS);
+	}
+
+	return sp;
+}
+
+svalue_t* f_socket_accept(svalue_t* sp)
+{
+	socket_t* sock;
+	socket_t* new_sock;
+	int new_fd;
+	struct sockaddr_in addr;
+	int addr_size = sizeof(addr);
+	int err;
+	int myerrno;
+   
+	int lpc_fd = sp[-2].u.number;
+	
+	sock = get_socket_entry(lpc_fd);
+	if(sock == 0)
+	{
+		free_svalue(sp--);
+		free_svalue(sp--);
+		free_svalue(sp--);
+	
+		push_number(sp, SE_BADFD);
+		return sp;
+	}
+
+	new_fd = accept(sock->fd, (struct sockaddr*)&addr, &addr_size);
+	myerrno = errno;
+	if(new_fd < 0)
+	{
+		perror("socket_accept: accept");
+	
+		free_svalue(sp--);
+		free_svalue(sp--);
+		free_svalue(sp--);
+
+		push_number(sp, socket_conv_errno(myerrno));
+		return sp;
+	}
+
+	new_sock = new_socket_entry();
+	
+	if(new_sock == 0)
+	{
+		close(new_fd);
+	
+		free_svalue(sp--);
+		free_svalue(sp--);
+		free_svalue(sp--);
+	
+		push_number(sp, SE_NOMORESOCKETS);
+		return sp;
+	}
+
+	err = socket_init_socket(new_sock, new_fd, &sp[0]);
+
+	if(err != 0)
+	{
+		close(new_fd);
+
+		free_socket_entry(new_sock);
+	
+		free_svalue(sp--);
+		free_svalue(sp--);
+		free_svalue(sp--);
+
+		push_number(sp, err);
+	}
+
+	assign_svalue(&new_sock->read_callback, &sp[-1]);
+
+	memcpy(&new_sock->remote_addr, &addr, sizeof(addr));
+	
+	free_svalue(sp--);
+	free_svalue(sp--);
+	free_svalue(sp--);
+
+	new_sock->state = STATE_CONNECTED;
+	
+	push_number(sp, new_sock->lpc_fd);
+
+	return sp;
+}
+
+svalue_t* f_socket_address(svalue_t* sp)
+{
+#define ADDR_BUF_SIZE 512
+	char addrstr[ADDR_BUF_SIZE];
+	socket_t* sock;
+	
+	int lpc_fd = sp[0].u.number;
+	
+	sock = get_socket_entry(lpc_fd);
+	
+	free_svalue(sp--);
+	
+	if(sock == 0)
+	{
+		push_string(sp, new_mstring(""));
+		return sp;
+	}
+
+	snprintf(addrstr, 511, "%s:%d",
+			 inet_ntoa(sock->remote_addr.sin_addr),
+			 ntohs(sock->remote_addr.sin_port));
+	addrstr[ADDR_BUF_SIZE-1] = 0;
+
+	push_string(sp, new_mstring(addrstr));
+
+	return sp;
+#undef ADDR_BUF_SIZE
+}
+
+void socket_flush_send_buffer(socket_t* sock)
+{
+	if(sock->state == STATE_CONNECTED && sock->send_buffer_used > 0)
+	{
+		int len, myerrno;
+
+		len = send(sock->fd, sock->send_buffer, sock->send_buffer_used, 0);
+		myerrno = errno;
+	
+		if(myerrno == EAGAIN || myerrno == EWOULDBLOCK)
+		{
+			len = 0;
+		}
+	
+		if(len == -1)
+		{
+			fprintf(stderr, "socket_flush_send_buffer: send failed %s\n", strerror(myerrno));
+
+			/* error -> clear buffer */
+			free(sock->send_buffer);
+			sock->send_buffer = 0;
+			sock->send_buffer_size = 0;
+			sock->send_buffer_used = 0;
+		}
+		else if(len < sock->send_buffer_used)
+		{
+			memmove(sock->send_buffer, sock->send_buffer + len,
+					sock->send_buffer_used - len);
+		
+			sock->send_buffer_used = sock->send_buffer_used - len;
+/*
+  printf("socket: sent %d of %d. %d in buffer\n", len, sock->send_buffer_used+len,
+  sock->send_buffer_used);
+*/
+		}
+		else
+		{
+/*		
+	printf("socket: sent %d of %d. %d in buffer\n", len, sock->send_buffer_used,
+	0);
+*/
+			free(sock->send_buffer);
+			sock->send_buffer = 0;
+			sock->send_buffer_size = 0;
+			sock->send_buffer_used = 0;
+		}
+	}
+}
+
+svalue_t* f_socket_send(svalue_t* sp)
+{
+	int lpc_fd, len, myerrno;
+	socket_t* sock;
+	char* buf;
+	int buflen;
+	
+	lpc_fd = sp[-1].u.number;
+	
+	sock = get_socket_entry(lpc_fd);
+
+	if(sock == 0)
+	{
+		free_svalue(sp--);
+		free_svalue(sp--);
+		push_number(sp, SE_BADFD);
+		return sp;
+	}
+
+	buf =  get_txt(sp[0].u.str);
+	buflen = mstrsize(sp[0].u.str);
+
+	socket_flush_send_buffer(sock);
+	
+	if(sock->send_buffer_used == 0)
+	{
+		len = send(sock->fd, buf, buflen, 0);
+		myerrno = errno;
+	}
+	else
+	{
+		myerrno = 0;
+		len = 0;
+	}
+
+	if(len == -1 && (myerrno == EAGAIN || myerrno == EWOULDBLOCK))
+	{
+		len = 0;
+	}
+	
+	if(len == -1)
+	{
+		fprintf(stderr, "socket_send: send failed %s\n", strerror(myerrno));
+
+		free_svalue(sp--);
+		free_svalue(sp--);
+	
+		push_number(sp, socket_conv_errno(myerrno));
+	}
+	else if(len < buflen)
+	{
+		if(buflen - len > sock->send_buffer_size - sock->send_buffer_used)
+		{
+			sock->send_buffer = realloc(sock->send_buffer,
+										sock->send_buffer_used + buflen - len);
+
+			if(!sock->send_buffer)
+			{
+				error("out of memory");
+			}
+			else
+			{
+				sock->send_buffer_size = sock->send_buffer_used + buflen - len;
+			}
+		}
+
+		memcpy(sock->send_buffer + sock->send_buffer_used,
+			   buf + len,
+			   buflen - len);
+
+		sock->send_buffer_used += buflen - len;
+/*
+  printf("socket: sent %d of %d. %d in buffer\n", len, buflen, sock->send_buffer_used);
+*/
+		free_svalue(sp--);
+		free_svalue(sp--);
+	
+		push_number(sp, len);
+	}
+	else
+	{
+		free_svalue(sp--);
+		free_svalue(sp--);
+	
+		push_number(sp, SE_SUCCESS);
+	}
+	
+	return sp;
+}
+
+void socket_poll()
+{
+	int ret, i, myerrno;
+
+	object_t* hide_current_object = current_object;
+	program_t* hide_current_prog = current_prog;
+	object_t* hide_interactive = current_interactive;
+	object_t* hide_command_giver = command_giver;
+
+	current_interactive = 0;
+	command_giver = 0;
+	
+	for(i = 0; i < MAX_LPC_FD; i++)
+	{
+		socket_t* sock = &g_socket_table[i];
+
+		if(sock->state == STATE_CLOSING)
+		{
+			free_socket_entry(sock);
+		}	
+	}
+	
+	ret = poll(g_poll_table, MAX_LPC_FD, 0);
+	myerrno = errno;
+	
+	if(ret == -1)
+	{
+		perror("socket_poll: poll");
+	}
+	else
+	{
+		for(i = 0; i < MAX_LPC_FD; i++)
+		{
+			if(g_poll_table[i].revents)
+			{
+				socket_t* sock = get_socket_entry(i);
+		
+				if(sock == 0)
+				{
+					error("Internal error in socket_poll()");
+				}
+		
+				socket_process_events(sock, g_poll_table[i].revents);
+			}
+		}
+	}
+	
+	current_interactive = hide_interactive;
+	command_giver = hide_command_giver;
+	current_object = hide_current_object;
+	current_prog = hide_current_prog;
+}
+
+
+void socket_process_events(socket_t* sock, short events)
+{
+	int err, errlen;
+	int myerrno;
+	
+	if(events & POLLERR)
+	{
+//	printf("ERR %d\n", sock->lpc_fd);
+	
+		errlen = sizeof(err);
+		if(getsockopt(sock->fd, SOL_SOCKET, SO_ERROR, &err, &errlen) == -1)
+		{
+			perror("socket_process_events: getsockopt");
+		}
+	
+		if(sock->state == STATE_CONNECTING)
+		{
+			push_number(inter_sp, sock->lpc_fd);
+			push_number(inter_sp, socket_conv_errno(err));
+			secure_callback_lambda(&sock->connect_callback, 2);
+		}
+
+		/* callback may have closed the socket */
+		if(sock->state != STATE_CLOSING)
+		{
+			push_number(inter_sp, sock->lpc_fd);
+			push_number(inter_sp, socket_conv_errno(err));
+	
+			secure_callback_lambda(&sock->close_callback, 2);
+		
+			/* callback may have closed the socket */
+			if(sock->state != STATE_CLOSING)
+				free_socket_entry(sock);
+		}
+	
+		return;
+	}
+		
+	if(events & POLLHUP)
+	{
+//	printf("HUP %d\n", sock->lpc_fd);
+	
+		errlen = sizeof(err);
+		if(getsockopt(sock->fd, SOL_SOCKET, SO_ERROR, &err, &errlen) == -1)
+		{
+			perror("socket_process_events: getsockopt");
+		}
+
+		if(sock->state == STATE_CONNECTING)
+		{
+			push_number(inter_sp, sock->lpc_fd);
+			push_number(inter_sp, err);
+			secure_callback_lambda(&sock->connect_callback, 2);
+		}
+
+		/* callback may have closed the socket */
+		if(sock->state != STATE_CLOSING)
+		{
+			push_number(inter_sp, sock->lpc_fd);
+			push_number(inter_sp, err);
+		
+			secure_callback_lambda(&sock->close_callback, 2);
+		
+			/* callback may have closed the socket */
+			if(sock->state != STATE_CLOSING)
+				free_socket_entry(sock);
+		}
+	
+		return;
+	}
+
+	if(events & POLLOUT)
+	{
+//	printf("OUT %d\n", sock->lpc_fd);
+
+		if(sock->state == STATE_CONNECTING)
+		{
+			push_number(inter_sp, sock->lpc_fd);
+			push_number(inter_sp, 0);
+			secure_callback_lambda(&sock->connect_callback, 2);
+
+			sock->state = STATE_CONNECTED;
+		}
+		else
+		{
+			socket_flush_send_buffer(sock);
+		}
+	}
+	
+	if(events & POLLIN)
+	{
+//	printf("IN %d\n", sock->lpc_fd);
+		
+		if(sock->state == STATE_CONNECTED)
+		{
+			int len;
+			
+			len = recv(sock->fd, &g_read_buffer, READ_BUFFER_SIZE, 0);
+			myerrno = errno;
+
+			if(len == -1)
+			{
+				perror("socket_process_events: recv");
+		
+				push_number(inter_sp, sock->lpc_fd);
+				push_number(inter_sp, 0);
+				push_number(inter_sp, socket_conv_errno(myerrno));
+		
+				secure_callback_lambda(&sock->read_callback, 3);
+			}
+			else if(len == 0)
+			{
+				push_number(inter_sp, sock->lpc_fd);
+				push_number(inter_sp, 0);
+		
+				secure_callback_lambda(&sock->close_callback, 2);
+		
+				free_socket_entry(sock);
+				return;
+			}
+			else
+			{
+				push_number(inter_sp, sock->lpc_fd);
+				push_string(inter_sp, mstring_new_n_string(g_read_buffer, len));
+				push_number(inter_sp, len);
+		
+				secure_callback_lambda(&sock->read_callback, 3);
+			}
+		}
+		else if(sock->state == STATE_LISTEN)
+		{
+			push_number(inter_sp, sock->lpc_fd);
+		
+			secure_callback_lambda(&sock->listen_callback, 1);
+		}
+		else
+		{
+			fprintf(stderr, "socket_poll: read event in unknown state %d\n", sock->state);
+		}
+	}
+}
diff -burN 3-3/src/sockets.h 3-3.sock/src/sockets.h
--- 3-3/src/sockets.h	Thu Jan  1 02:00:00 1970
+++ 3-3.sock/src/sockets.h	Thu Sep 12 13:35:17 2002
@@ -0,0 +1,6 @@
+
+void socket_init();
+
+void socket_object_destructed(object_t* ob);
+
+void socket_poll();
----------------- End of Diff -------------------

----------------- socket.c -------------------
/*
 * Sockets for ldmud 3.3
 *
 * 5-July-2002 - Tomba @ Batmud (tomba@bat.org)
 *   * First version
 *
 * 20-July-2002 - Tomba @ Batmud (tomba@bat.org)
 *   * Added ECONNRESET
 *   * Fixed socket_address to return empty string on error
 *   * sock->send_buffer was not always cleared when freed
 *   * removed a few debug printfs
 *
 * 26-September-2002 - Tomba @ Batmud (tomba@bat.org)
 *   * Sockets now send all the data in their buffer before actually
 *     closing
 *   * Some minor fixes
 *
 */

#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/poll.h>
#include <netdb.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>

#include "main.h"
#include "svalue.h"
#include "interpret.h"
#include "simulate.h"
#include "object.h"
#include "mstrings.h"
#include "comm.h"
#include "actions.h"

#define SE_SUCCESS 0
#define SE_UNKNOWN -1
#define SE_CONNREFUSED -2
#define SE_HOSTDOWN -3
#define SE_HOSTUNREACH -4
#define SE_NOMORESOCKETS -5
#define SE_CREATESOCKET -6
#define SE_SETNONBLOCKING -7
#define SE_SETSOCKOPT -8
#define SE_BADFD -9
#define SE_ILLEGALADDR -10
#define SE_INVALIDPORT -11
#define SE_PIPE -12
#define SE_ADDRINUSE -13
#define SE_INVAL -14
#define SE_CONNRESET -15
#define SE_ILLEGAL_SOCKET_STATE -16
#define SE_ALREADYBOUND -17

static char *socket_errors[] = {
	"Success",
	"Unknown error",
	"Connection refused",
	"Host is down",
	"No route to host",
	"No more mud sockets available",
	"Failed to create socket",
	"Failed to set socket to non-blocking mode",
	"Failed to set socket options",
	"Bad socket descriptor",
	"Illegal address",
	"Invalid port",
	"Broken pipe",
	"Address already in use",
	"Invalid argument",
	"Connection reset by peer",
	"Illegal socket state",
	"Socket is already bound"
};

enum socket_state
{
	STATE_UNUSED = 0,
	STATE_ALLOCATED,
	STATE_READY,
	STATE_LISTENING,
	STATE_CONNECTING,
	STATE_CONNECTED,
	STATE_CLOSING
};

static char* socket_state_strs[] = { "unused", "allocated", "ready",
									 "listening", "connecting", "connected",
									 "closing" };

struct socket_struct
{
	int fd;
	int lpc_fd;				   /* "fd" shown to lpc */
	enum socket_state state;
	object_t* object;
	int bound;
	struct sockaddr_in local_addr;
	struct sockaddr_in remote_addr;

	svalue_t read_callback;
	svalue_t close_callback;
	svalue_t connect_callback;
	svalue_t listen_callback;

	char* send_buffer;
	int send_buffer_size;
	int send_buffer_used;
} typedef socket_t;


#define MAX_LPC_FD 100

static socket_t g_socket_table[MAX_LPC_FD];
static struct pollfd g_poll_table[MAX_LPC_FD];

#define READ_BUFFER_SIZE 10240
static char g_read_buffer[READ_BUFFER_SIZE];

void socket_process_events(socket_t* sock, short events);



void socket_init()
{
	int i;
	for(i = 0; i < MAX_LPC_FD; i++)
	{
		struct socket_struct* sock = &g_socket_table[i];
	
		sock->state = STATE_UNUSED;
		sock->lpc_fd = i;

		g_poll_table[i].fd = -1;
		g_poll_table[i].events = POLLIN | POLLOUT;
		g_poll_table[i].revents = 0;

	}
}

int socket_conv_errno(int err)
{
	switch(err)
	{
		case ECONNREFUSED:
			return SE_CONNREFUSED;

		case EHOSTDOWN:
			return SE_HOSTDOWN;

		case EHOSTUNREACH:
			return SE_HOSTUNREACH;

		case EPIPE:
			return SE_PIPE;

		case EADDRINUSE:
			return SE_ADDRINUSE;

		case EINVAL:
			return SE_INVAL;

		case ECONNRESET:
			return SE_CONNRESET;

		default:
			fprintf(stderr, "socket_conv_errno: unknown system error %d, %s\n", err, strerror(err));
			return SE_UNKNOWN;
	}
}

struct socket_struct* new_socket_entry()
{
	int i;
	
	for (i = 0; i < MAX_LPC_FD; i++)
	{
		struct socket_struct* sock = &g_socket_table[i];
		
		if (sock->state == STATE_UNUSED)
		{
			sock->fd = -1;
			sock->state = STATE_ALLOCATED;
			sock->object = 0;
			sock->bound = 0;
			memset(&sock->local_addr, 0, sizeof(sock->local_addr));
			memset(&sock->remote_addr, 0, sizeof(sock->remote_addr));
			
			put_number(&sock->read_callback, 0);
			put_number(&sock->close_callback, 0);
			put_number(&sock->connect_callback, 0);
			put_number(&sock->listen_callback, 0);
			
			sock->send_buffer = 0;
			sock->send_buffer_size = 0;
			sock->send_buffer_used = 0;
			
			return sock;
		}
	}
	
	return 0;
}

void free_socket_entry(socket_t* sock)
{
	if(sock != 0)
	{
//		printf("Freeing fd %d, lpcfd %d\n", sock->fd, sock->lpc_fd);
		if(sock->fd != -1)
		{
			if(close(sock->fd) == -1)
			{
				perror("free_socket_entry: close");
			}

			sock->fd = -1;
		}
	
		sock->state = STATE_UNUSED;
		sock->object = 0;
		
		free_svalue(&sock->read_callback);
		free_svalue(&sock->close_callback);
		free_svalue(&sock->connect_callback);
		free_svalue(&sock->listen_callback);
		put_number(&sock->read_callback, 0);
		put_number(&sock->close_callback, 0);
		put_number(&sock->connect_callback, 0);
		put_number(&sock->listen_callback, 0);
		
		if(sock->send_buffer)
		{
			free(sock->send_buffer);
			sock->send_buffer = 0;
			sock->send_buffer_size = 0;
			sock->send_buffer_used = 0;
		}

		g_poll_table[sock->lpc_fd].fd = -1;
	}
}

socket_t* get_socket_entry(int lpc_fd)
{
	if(lpc_fd < 0 || lpc_fd >= MAX_LPC_FD ||
	   g_socket_table[lpc_fd].state == STATE_UNUSED)
	{
		return 0;
	}

	return &g_socket_table[lpc_fd];
}

void socket_object_destructed(object_t* ob)
{
	int i;
	
	for(i = 0; i < MAX_LPC_FD; i++)
	{
		struct socket_struct* sock = &g_socket_table[i];

		if(sock->state != STATE_UNUSED)
		{
			if(sock->object == ob)
			{
				free_socket_entry(sock);
			}
		}
	}
}






svalue_t* f_socket_strerror(svalue_t* sp)
{
	int err = 0 - sp->u.number;
	sp--;

	if(err < 0 || err > sizeof(socket_errors))
	{
		push_string(sp, new_mstring("Unspecified error"));
	}
	else
	{	
		push_string(sp, new_mstring(socket_errors[err]));
	}
	
	return sp;
}

svalue_t* f_socket_print_stats(svalue_t* sp)
{
	int i;
	
	add_message("-- Socket stats --\n");
		
	for(i = 0; i < MAX_LPC_FD; i++)
	{
		socket_t* sock = &g_socket_table[i];
	
		if(sock->state != STATE_UNUSED)
		{
			add_message("lpc_fd %d, fd %d, state %s, buf %d, ob %s\n",
						i, sock->fd,
						socket_state_strs[sock->state],
						sock->send_buffer ? sock->send_buffer_used : 0,
						get_txt(sock->object->name));
		}
	}

	return sp;
}

int socket_init_socket(socket_t* sock, int fd, svalue_t* close_callback,
					   object_t* object)
{
	int tmp;
	
	if(fcntl(fd, F_SETFL, O_NDELAY) == -1)
	{
		perror("socket_init_socket: fcntl O_NDELAY");

		return SE_SETNONBLOCKING;
	}

	tmp = 1;
	if(setsockopt(fd, SOL_SOCKET, SO_REUSEADDR,
				  (char *)&tmp, sizeof(tmp)) == -1)
	{
		perror("socket_init_socket: setsockopt SO_REUSEADDR");

		return SE_SETSOCKOPT;
	}

	sock->fd = fd;
	sock->state = STATE_READY;
	sock->object = object;

	assign_svalue(&sock->close_callback, close_callback);
	
	g_poll_table[sock->lpc_fd].fd = sock->fd;

	return 0;
}

int socket_string_to_sockaddr(char* name, struct sockaddr_in* saddr)
{
#define ADDR_BUF_SIZE 512
	int port;
	char *cp, addr[ADDR_BUF_SIZE];

	strncpy(addr, name, ADDR_BUF_SIZE);

	cp = strchr(addr, ':');
	if (cp == NULL)
	{
		return 0;
	}
	
	*cp = '\0';
	port = atoi(cp + 1);

	saddr->sin_family = AF_INET;
	saddr->sin_port = htons((unsigned short)port);
	if(inet_aton(addr, &saddr->sin_addr) == 0)
	{
		return 0;
	}

	return 1;
#undef ADDR_BUF_SIZE
}



svalue_t* f_socket_create(svalue_t* sp)
{
	int err, fd;
	socket_t* sock;
	
	if(current_object->flags & O_DESTRUCTED)
	{
		error("socket_create: this_object has been destructed");
	}

	sock = new_socket_entry();
	
	if(sock == 0)
	{
		free_svalue(sp--);
		push_number(sp, SE_NOMORESOCKETS);
		return sp;
	}

	fd = socket(AF_INET, SOCK_STREAM, 0);

	if(fd == -1)
	{
		perror("socket_create: socket");
	
		free_socket_entry(sock);
	
		free_svalue(sp--);
		push_number(sp, SE_CREATESOCKET);
		return sp;
	}
	
	err = socket_init_socket(sock, fd, &sp[0], current_object);
	
	free_svalue(sp--);
	
	if(err != 0)
	{
		close(fd);
	
		free_socket_entry(sock);

		push_number(sp, err);

		return sp;
	}
	
	push_number(sp, sock->lpc_fd);

	return sp;
}

svalue_t* f_socket_close(svalue_t* sp)
{
	int lpc_fd = (sp--)->u.number;
	
	socket_t* sock = get_socket_entry(lpc_fd);

	if(sock == 0)
	{
		push_number(sp, SE_BADFD);
		return sp;
	}

	if(shutdown(sock->fd, 0) == -1)
	{
		fprintf(stderr, "socket_close: shutdown failed: %s\n", strerror(errno));
	}
	
	sock->state = STATE_CLOSING;

	push_number(sp, SE_SUCCESS);
		
	return sp;
}

svalue_t* f_socket_connect(svalue_t* sp)
{
	int lpc_fd, myerrno;
	int ret;
	socket_t* sock;

	lpc_fd = sp[-3].u.number;

	sock = get_socket_entry(lpc_fd);
	if(sock == 0)
	{
		free_svalue(sp--);
		free_svalue(sp--);
		free_svalue(sp--);
		free_svalue(sp--);
	
		push_number(sp, SE_BADFD);
		return sp;
	}

	if(sock->state != STATE_READY)
	{
		free_svalue(sp--);
		free_svalue(sp--);
		free_svalue(sp--);
		free_svalue(sp--);
	
		push_number(sp, SE_ILLEGAL_SOCKET_STATE);
		return sp;
	}

	if(socket_string_to_sockaddr(get_txt(sp[-2].u.str), &sock->remote_addr) == 0)
	{
		free_svalue(sp--);
		free_svalue(sp--);
		free_svalue(sp--);
		free_svalue(sp--);
	
		push_number(sp, SE_ILLEGALADDR);
		return sp;
	}
	
	ret = connect(sock->fd, (struct sockaddr *)&sock->remote_addr,
				  sizeof(sock->remote_addr));
	myerrno = errno;
	
	if(ret == -1 && myerrno != EINPROGRESS)
	{
		fprintf(stderr, "socket_connect: connect failed: %s\n", strerror(myerrno));

		free_svalue(sp--);
		free_svalue(sp--);
		free_svalue(sp--);
		free_svalue(sp--);
		
		push_number(sp, socket_conv_errno(myerrno));
	}
	else
	{
		sock->state = STATE_CONNECTING;

		transfer_svalue(&sock->connect_callback, &sp[-1]);
		transfer_svalue(&sock->read_callback, &sp[0]);

		sp--;
		sp--;
		free_svalue(sp--);
		free_svalue(sp--);

		push_number(sp, SE_SUCCESS);
	}
	
	return sp;
}

svalue_t* f_socket_bind(svalue_t* sp)
{
	socket_t* sock;
	int myerrno;
   
	int port = (sp--)->u.number;
	int lpc_fd = (sp--)->u.number;

	sock = get_socket_entry(lpc_fd);
	if(sock == 0)
	{
		push_number(sp, SE_BADFD);
		return sp;
	}

	if(sock->state != STATE_READY)
	{
		push_number(sp, SE_ILLEGAL_SOCKET_STATE);
		return sp;
	}

	if(sock->bound)
	{
		push_number(sp, SE_ALREADYBOUND);
		return sp;
	}

	if(port < 0)
	{
		push_number(sp, SE_INVALIDPORT);
		return sp;
	}

	sock->local_addr.sin_port = htons((unsigned short)port);
	sock->local_addr.sin_addr.s_addr = INADDR_ANY;
	sock->local_addr.sin_family = AF_INET;

	if(bind(sock->fd, (struct sockaddr*)&sock->local_addr,
			sizeof(sock->local_addr)) == -1)
	{
		myerrno = errno;
	
		perror("socket_bind: bind");
		push_number(sp, socket_conv_errno(myerrno));
	}
	else
	{
		sock->bound = 1;
		push_number(sp, SE_SUCCESS);
	}
	 
	return sp;
}

svalue_t* f_socket_listen(svalue_t* sp)
{
	socket_t* sock;
	int myerrno;
   
	int lpc_fd = sp[-1].u.number;

	sock = get_socket_entry(lpc_fd);
	if(sock == 0)
	{
		free_svalue(sp--);
		free_svalue(sp--);
		push_number(sp, SE_BADFD);
		return sp;
	}

	if(sock->state != STATE_READY || !sock->bound)
	{
		free_svalue(sp--);
		free_svalue(sp--);
		push_number(sp, SE_ILLEGAL_SOCKET_STATE);
		return sp;
	}
	
	if(listen(sock->fd, 10) == -1)
	{
		myerrno = errno;
		perror("socket_listen: listen");
		
		free_svalue(sp--);
		free_svalue(sp--);
		push_number(sp, socket_conv_errno(myerrno));
		return sp;
	}
	
	assign_svalue(&sock->listen_callback, &sp[0]);
	
	free_svalue(sp--);
	free_svalue(sp--);

	sock->state = STATE_LISTENING;
	push_number(sp, SE_SUCCESS);
	
	return sp;
}

svalue_t* f_socket_accept(svalue_t* sp)
{
	socket_t* sock;
	socket_t* new_sock;
	int new_fd;
	struct sockaddr_in addr;
	int addr_size = sizeof(addr);
	int err;
	int myerrno;
   
	int lpc_fd = sp[-2].u.number;
	
	sock = get_socket_entry(lpc_fd);
	if(sock == 0)
	{
		free_svalue(sp--);
		free_svalue(sp--);
		free_svalue(sp--);
	
		push_number(sp, SE_BADFD);
		return sp;
	}

	if(sock->state != STATE_LISTENING)
	{
		free_svalue(sp--);
		free_svalue(sp--);
		free_svalue(sp--);
		push_number(sp, SE_ILLEGAL_SOCKET_STATE);
		return sp;
	}
	
	new_fd = accept(sock->fd, (struct sockaddr*)&addr, &addr_size);
	myerrno = errno;
	if(new_fd < 0)
	{
		perror("socket_accept: accept");
	
		free_svalue(sp--);
		free_svalue(sp--);
		free_svalue(sp--);

		push_number(sp, socket_conv_errno(myerrno));
		return sp;
	}

	new_sock = new_socket_entry();
	
	if(new_sock == 0)
	{
		close(new_fd);
	
		free_svalue(sp--);
		free_svalue(sp--);
		free_svalue(sp--);
	
		push_number(sp, SE_NOMORESOCKETS);
		return sp;
	}

	err = socket_init_socket(new_sock, new_fd, &sp[0], current_object);

	if(err != 0)
	{
		close(new_fd);

		free_socket_entry(new_sock);
	
		free_svalue(sp--);
		free_svalue(sp--);
		free_svalue(sp--);

		push_number(sp, err);
	}

	assign_svalue(&new_sock->read_callback, &sp[-1]);

	memcpy(&new_sock->remote_addr, &addr, sizeof(addr));
	
	free_svalue(sp--);
	free_svalue(sp--);
	free_svalue(sp--);

	new_sock->state = STATE_CONNECTED;
	
	push_number(sp, new_sock->lpc_fd);

	return sp;
}

svalue_t* f_socket_address(svalue_t* sp)
{
#define ADDR_BUF_SIZE 512
	char addrstr[ADDR_BUF_SIZE];
	socket_t* sock;
	
	int lpc_fd = sp[0].u.number;
	
	sock = get_socket_entry(lpc_fd);
	
	free_svalue(sp--);
	
	if(sock == 0)
	{
		push_string(sp, new_mstring(""));
		return sp;
	}

	if(sock->state != STATE_CONNECTED)
	{
		push_string(sp, new_mstring(""));
		return sp;
	}
	
	snprintf(addrstr, 511, "%s:%d",
			 inet_ntoa(sock->remote_addr.sin_addr),
			 ntohs(sock->remote_addr.sin_port));
	addrstr[ADDR_BUF_SIZE-1] = 0;

	push_string(sp, new_mstring(addrstr));

	return sp;
#undef ADDR_BUF_SIZE
}

void socket_flush_send_buffer(socket_t* sock)
{
	if( (sock->state == STATE_CONNECTED || sock->state == STATE_CLOSING) &&
		sock->send_buffer_used > 0)
	{
		int len, myerrno;

		len = send(sock->fd, sock->send_buffer, sock->send_buffer_used, 0);
		myerrno = errno;

		if(len == -1)
		{
			fprintf(stderr, "socket_flush_send_buffer: send failed %s\n", strerror(myerrno));

			/* error -> clear buffer */
			free(sock->send_buffer);
			sock->send_buffer = 0;
			sock->send_buffer_size = 0;
			sock->send_buffer_used = 0;
		}
		else if(len < sock->send_buffer_used)
		{
			memmove(sock->send_buffer, sock->send_buffer + len,
					sock->send_buffer_used - len);
		
			sock->send_buffer_used = sock->send_buffer_used - len;
/*
			printf("socket: sent %d of %d. %d in buffer\n", len, sock->send_buffer_used+len,
				   sock->send_buffer_used);
*/		
		}
		else
		{
/*
			printf("socket: sent %d of %d. %d in buffer\n", len, sock->send_buffer_used,
				   0);
*/		
			free(sock->send_buffer);
			sock->send_buffer = 0;
			sock->send_buffer_size = 0;
			sock->send_buffer_used = 0;
		}
	}
}

svalue_t* f_socket_send(svalue_t* sp)
{
	int lpc_fd, len, myerrno;
	socket_t* sock;
	char* buf;
	int buflen;
	
	lpc_fd = sp[-1].u.number;
	
	sock = get_socket_entry(lpc_fd);

	if(sock == 0)
	{
		free_svalue(sp--);
		free_svalue(sp--);
		push_number(sp, SE_BADFD);
		return sp;
	}

	if(sock->state != STATE_CONNECTED)
	{
		free_svalue(sp--);
		free_svalue(sp--);
		push_number(sp, SE_ILLEGAL_SOCKET_STATE);
		return sp;
	}
	
	buf =  get_txt(sp[0].u.str);
	buflen = mstrsize(sp[0].u.str);

	socket_flush_send_buffer(sock);
	
	if(sock->send_buffer_used == 0)
	{
		len = send(sock->fd, buf, buflen, 0);
		myerrno = errno;
	}
	else
	{
		myerrno = 0;
		len = 0;
	}

	if(len == -1 && (myerrno == EAGAIN || myerrno == EWOULDBLOCK))
	{
		len = 0;
	}
	
	if(len == -1)
	{
		fprintf(stderr, "socket_send: send failed %s\n", strerror(myerrno));

		free_svalue(sp--);
		free_svalue(sp--);
	
		push_number(sp, socket_conv_errno(myerrno));
	}
	else if(len < buflen)
	{
		if(buflen - len > sock->send_buffer_size - sock->send_buffer_used)
		{
			sock->send_buffer = realloc(sock->send_buffer,
										sock->send_buffer_used + buflen - len);

			if(!sock->send_buffer)
			{
				error("out of memory");
			}
			else
			{
				sock->send_buffer_size = sock->send_buffer_used + buflen - len;
			}
		}

		memcpy(sock->send_buffer + sock->send_buffer_used,
			   buf + len,
			   buflen - len);

		sock->send_buffer_used += buflen - len;

//		printf("socket_send(): sent %d of %d. %d in buffer\n", len, buflen, sock->send_buffer_used);

		free_svalue(sp--);
		free_svalue(sp--);
	
		push_number(sp, len);
	}
	else
	{
		free_svalue(sp--);
		free_svalue(sp--);
	
		push_number(sp, SE_SUCCESS);
	}
	
	return sp;
}

void socket_poll()
{
	int ret, i, myerrno;

	object_t* hide_current_object = current_object;
	program_t* hide_current_prog = current_prog;
	object_t* hide_interactive = current_interactive;
	object_t* hide_command_giver = command_giver;

	current_interactive = 0;
	command_giver = 0;

	for(i = 0; i < MAX_LPC_FD; i++)
	{
		socket_t* sock = &g_socket_table[i];

		/* if socket is closing and we have sent all the stuff out,
		   free it */
		if(sock->state == STATE_CLOSING && sock->send_buffer == 0)
		{
			free_socket_entry(sock);
		}
	}

	ret = poll(g_poll_table, MAX_LPC_FD, 0);
	myerrno = errno;
	
	if(ret == -1)
	{
		perror("socket_poll: poll");
	}
	else
	{
		for(i = 0; i < MAX_LPC_FD; i++)
		{
			if(g_poll_table[i].revents)
			{
				socket_t* sock = get_socket_entry(i);
		
				if(sock == 0)
				{
					error("Internal error in socket_poll()");
				}
		
				socket_process_events(sock, g_poll_table[i].revents);
			}
		}
	}
	
	current_interactive = hide_interactive;
	command_giver = hide_command_giver;
	current_object = hide_current_object;
	current_prog = hide_current_prog;
}


void socket_process_events(socket_t* sock, short events)
{
	int err, errlen;
	int myerrno;

	if(events & POLLNVAL)
	{
//		printf("NVAL %d\n", sock->lpc_fd);
		
		push_number(inter_sp, sock->lpc_fd);
		push_number(inter_sp, SE_UNKNOWN);
		secure_callback_lambda(&sock->close_callback, 2);
		free_socket_entry(sock);
		return;
	}
	
	if(events & POLLERR)
	{
//		printf("ERR %d\n", sock->lpc_fd);
	
		errlen = sizeof(err);
		if(getsockopt(sock->fd, SOL_SOCKET, SO_ERROR, &err, &errlen) == -1)
		{
			perror("socket_process_events: getsockopt");
		}
	
		if(sock->state == STATE_CONNECTING)
		{
			push_number(inter_sp, sock->lpc_fd);
			push_number(inter_sp, socket_conv_errno(err));
			secure_callback_lambda(&sock->connect_callback, 2);
		}
		else
		{
			push_number(inter_sp, sock->lpc_fd);
			push_number(inter_sp, socket_conv_errno(err));
			secure_callback_lambda(&sock->close_callback, 2);
			free_socket_entry(sock);
		}
		
		return;
	}
		
	if(events & POLLHUP)
	{
//		printf("HUP %d\n", sock->lpc_fd);
	
		errlen = sizeof(err);
		if(getsockopt(sock->fd, SOL_SOCKET, SO_ERROR, &err, &errlen) == -1)
		{
			perror("socket_process_events: getsockopt");
		}

		if(sock->state == STATE_CONNECTING)
		{
			push_number(inter_sp, sock->lpc_fd);
			push_number(inter_sp, err);
			secure_callback_lambda(&sock->connect_callback, 2);
		}
		else
		{
			push_number(inter_sp, sock->lpc_fd);
			push_number(inter_sp, err);
			secure_callback_lambda(&sock->close_callback, 2);
			free_socket_entry(sock);
		}
				
		return;
	}

	if(events & POLLOUT)
	{
//		printf("OUT %d\n", sock->lpc_fd);

		if(sock->state == STATE_CONNECTING)
		{
			push_number(inter_sp, sock->lpc_fd);
			push_number(inter_sp, 0);
			secure_callback_lambda(&sock->connect_callback, 2);

			sock->state = STATE_CONNECTED;
		}
		else if(sock->state == STATE_CONNECTED ||
				sock->state == STATE_CLOSING)
		{
			socket_flush_send_buffer(sock);
		}
		else
		{
			fprintf(stderr, "Illegal socket state in POLLOUT. fd: %d, state %d\n", sock->fd, sock->state);
		}
	}
	
	if(events & POLLIN)
	{
//		printf("IN %d\n", sock->lpc_fd);
		
		if(sock->state == STATE_CONNECTED)
		{
			int len;
			
			len = recv(sock->fd, &g_read_buffer, READ_BUFFER_SIZE, 0);
			myerrno = errno;

			if(len == -1)
			{
				perror("socket_process_events: recv");
		
				push_number(inter_sp, sock->lpc_fd);
				push_number(inter_sp, 0);
				push_number(inter_sp, socket_conv_errno(myerrno));
		
				secure_callback_lambda(&sock->read_callback, 3);
			}
			else if(len == 0)
			{
				push_number(inter_sp, sock->lpc_fd);
				push_number(inter_sp, 0);
		
				secure_callback_lambda(&sock->close_callback, 2);
		
				free_socket_entry(sock);
			}
			else
			{
				push_number(inter_sp, sock->lpc_fd);
				push_string(inter_sp, mstring_new_n_string(g_read_buffer, len));
				push_number(inter_sp, len);
		
				secure_callback_lambda(&sock->read_callback, 3);
			}
		}
		else if(sock->state == STATE_LISTENING)
		{
			push_number(inter_sp, sock->lpc_fd);
		
			secure_callback_lambda(&sock->listen_callback, 1);
		}
		else if(sock->state == STATE_CLOSING)
		{
			
		}
		else
		{
			fprintf(stderr, "socket_poll: read event in unknown state. fd: %d, state %d\n", sock->fd, sock->state);
		}
	}
}
----------------- End of socket.c -------------------
p-020912 (49,928 bytes)   

lars

2004-11-26 21:04

reporter   ~0000213

p-021021: Socket support by Ctx:

Ich habe ein bisschen mit deinem MUD-Driver herumgespielt, er ist
wirklich super ;)
Ich wollte ihn auch zu Verbindungen mit externen Services wie SMTP,
IMAP, POP3, NNTP, IRC usw. usf. einsetzen und hab festgestellt, dass das
mit dem ERQ-Daemon eigentlich nur sehr unsauber und hakelig geht. Also
dachte ich mir, waere es doch sinnvoll, wenn man den remote service als
"Player" sieht, der einem interaktiven objekt Kommandos schickt.
Bei einer Verbindung zu einem SMTP-Server z.B. wuerde das Kommando
'220' bedeuten, schick mir jetzt deine Befehle.
Bei einem IRC-Server mit festem Prefix (:irc-server.de 376) koennte man
mit modify_command() die Eingabe in ein entsprechendes Format bringen
(Zahlencode zuerst, dann Sender) und hier auch mit add_action arbeiten.
Gerade bei nicht-binaeren (Plaintext) Protokollen wie die oben genannten
finde ich diese Sichtweise elegant.

Ich habe fuer mich den Driver ein wenig geaendert, um eine efun
"connect_external" erweitert.
connect_external("127.0.0.1", 25, smtp_object); wuerde eine Verbindung
zum lokalen SMTP-server herstellen und die Verbindung an das smtp_object
binden. Nach gelungenem Verbindungsaufbau wird dort, wie bei
player-objekten, logon() aufgerufen. Bei Fehlschlag, bzw.
Verbindungsabbruch, wuerde "connerr" mit der entsprechenden errno als
Argument aufgerufen. Ansonsten verhaelt sich das objekt wie jedes andere
interaktive.

Fuer mich ist das ziemlich nuetzlich, vielleicht ja auch fuer dich oder
andere. Vermutlich sind noch Bugs darin (Eine Stelle, bei der ich mir
ziemlich sicher bin, habe ich mit FIXME markiert), ich hatte leider
nicht die Zeit mich in jedes Detail des Drivers einzuarbeiten.

Falls der Patch sinnlos/schlecht/falsch ist, schmeiss die Mail einfach
weg ;) Ueber kurzes Feedback wuerde ich mich trotzdem freuen.

Gruss

Florian Heinz (AKA Ctx)

2004-11-26 21:05

 

p-021021 (9,634 bytes)   
Short: Socket support
From: Ctx@amail.derefence.de
Date: 2002-10-21
Type: Patch
State: New
See also: f-981226-1, f-991104-0, p-020912

Hi Lars ;)

Ich habe ein bisschen mit deinem MUD-Driver herumgespielt, er ist
wirklich super ;)
Ich wollte ihn auch zu Verbindungen mit externen Services wie SMTP,
IMAP, POP3, NNTP, IRC usw. usf. einsetzen und hab festgestellt, dass das
mit dem ERQ-Daemon eigentlich nur sehr unsauber und hakelig geht. Also
dachte ich mir, waere es doch sinnvoll, wenn man den remote service als
"Player" sieht, der einem interaktiven objekt Kommandos schickt.
Bei einer Verbindung zu einem SMTP-Server z.B. wuerde das Kommando
'220' bedeuten, schick mir jetzt deine Befehle.
Bei einem IRC-Server mit festem Prefix (:irc-server.de 376) koennte man
mit modify_command() die Eingabe in ein entsprechendes Format bringen
(Zahlencode zuerst, dann Sender) und hier auch mit add_action arbeiten.
Gerade bei nicht-binaeren (Plaintext) Protokollen wie die oben genannten
finde ich diese Sichtweise elegant.

Ich habe fuer mich den Driver ein wenig geaendert, um eine efun
"connect_external" erweitert.
connect_external("127.0.0.1", 25, smtp_object); wuerde eine Verbindung
zum lokalen SMTP-server herstellen und die Verbindung an das smtp_object
binden. Nach gelungenem Verbindungsaufbau wird dort, wie bei
player-objekten, logon() aufgerufen. Bei Fehlschlag, bzw.
Verbindungsabbruch, wuerde "connerr" mit der entsprechenden errno als
Argument aufgerufen. Ansonsten verhaelt sich das objekt wie jedes andere
interaktive.

Fuer mich ist das ziemlich nuetzlich, vielleicht ja auch fuer dich oder
andere. Vermutlich sind noch Bugs darin (Eine Stelle, bei der ich mir
ziemlich sicher bin, habe ich mit FIXME markiert), ich hatte leider
nicht die Zeit mich in jedes Detail des Drivers einzuarbeiten.

Falls der Patch sinnlos/schlecht/falsch ist, schmeiss die Mail einfach
weg ;) Ueber kurzes Feedback wuerde ich mich trotzdem freuen.

Gruss

Florian Heinz (AKA Ctx)


diff -ru 3-3/src/comm.c 3-3.ctx/src/comm.c
--- 3-3/src/comm.c	2002-10-21 13:06:37.000000000 +0200
+++ 3-3.ctx/src/comm.c	2002-10-21 13:04:56.000000000 +0200
@@ -318,8 +318,15 @@
 /* --- Communication sockets --- */
 
 static SOCKET_T sos[MAXNUMPORTS];
+
+static SOCKET_T soo[MAXNUMPORTS];
+
   /* The login sockets.
    */
+struct soc_item {
+   int fd;
+   svalue_t *ob;
+} soc[MAXNUMPORTS];
 
 static SOCKET_T udp_s = -1;
   /* The UDP socket */
@@ -444,7 +451,8 @@
 static void send_do(int);
 static void send_dont(int);
 static void remove_flush_entry(interactive_t *ip);
-static void new_player(SOCKET_T new_socket, struct sockaddr_in *addr, size_t len, int login_port);
+static void new_player(SOCKET_T new_socket, struct sockaddr_in *addr,
+		       size_t len, int login_port, svalue_t *n_ob);
 
 #ifdef ERQ_DEMON
 
@@ -1198,6 +1206,11 @@
         if (socket_number(sos[i]) >= min_nfds)
             min_nfds = socket_number(sos[i])+1;
     } /* for(i = 0..numports) */
+   
+    for (i = 0; i < MAXNUMPORTS; i++) {
+       soc[i].fd = -1;
+       soc[i].ob = NULL;
+    }
 
     /* We handle SIGPIPEs ourself */
 #if defined(__linux__)
@@ -1214,6 +1227,46 @@
 #endif
 } /* prepare_ipc() */
 
+int
+connect_ext (char *ip, unsigned short int port, svalue_t *n_ob)
+{
+   struct sockaddr_in addr;
+   int fd, st, i;
+   struct soc_item *sptr = NULL;
+   
+   for (i = 0; i < MAXNUMPORTS; i++) {
+      if (soc[i].fd < 0) {
+	 sptr = &soc[i];
+	 break;
+      }
+   }
+   
+   if (!sptr) {
+      errno = ENOMEM;
+      return -1;
+   }
+      
+   
+   addr.sin_family = AF_INET;
+   addr.sin_port = htons(port);
+   addr.sin_addr.s_addr = inet_addr(ip);
+   
+   fd = socket(AF_INET, SOCK_STREAM, 0);
+   if (fd < 0)
+     return -1;
+   
+   sptr->fd = fd;
+   sptr->ob = n_ob;
+   
+   set_socket_nonblocking(fd);
+   set_close_on_exec(fd);
+   st = connect(fd, (struct sockaddr *)&addr, sizeof(struct sockaddr_in));
+   if (st < 0)
+     return -1;
+   return 0;
+}
+
+
 /*-------------------------------------------------------------------------*/
 void
 ipc_remove (void)
@@ -2168,7 +2221,7 @@
 
 {
     /* State information: */
-    static fd_set readfds;
+    static fd_set readfds, writefds;
       /* List of sockets with pending data.
        * You can ignore a 'could be used uninitialized' warning.
        */
@@ -2212,13 +2265,20 @@
                * of the sockets, but don't wait.
                */
 
-            /* Set up readfds */
+            /* Set up readfds and writefds */
 
             FD_ZERO(&readfds);
+            FD_ZERO(&writefds);
             for (i = 0; i < numports; i++) {
                 FD_SET(sos[i], &readfds);
             } /* for */
             nfds = min_nfds;
+	    for (i = 0; i < MAXNUMPORTS; i++) {
+	        if (soc[i].fd >= 0)
+		   FD_SET(soc[i].fd, &writefds);
+	        if (soc[i].fd >= nfds)
+		   nfds = soc[i].fd + 1;
+	    }
             for (i = max_player + 1; --i >= 0;)
             {
                 ip = all_players[i];
@@ -2262,7 +2322,7 @@
                 check_alarm();
                 timeout.tv_sec = twait;
                 timeout.tv_usec = 0;
-                res = socket_select(nfds, &readfds, 0, 0, &timeout);
+                res = socket_select(nfds, &readfds, &writefds, 0, &timeout);
                 if (res == -1)
                 {
                     /* BeOS <= PR2 returns errno -1 instead of EINTR :-( */
@@ -2287,6 +2347,7 @@
                      * commands.
                      */
                     FD_ZERO(&readfds);
+                    FD_ZERO(&writefds);
                 }
                 break;
             } /* for (retries) */
@@ -2573,7 +2634,7 @@
                                               , &length);
                     if ((int)new_socket != -1)
                         new_player(new_socket, &addr, (size_t)length
-                                  , port_numbers[i]);
+                                  , port_numbers[i], NULL);
                     else if ((int)new_socket == -1
                       && errno != EWOULDBLOCK && errno != EINTR
                       && errno != EAGAIN && errno != EPROTO )
@@ -2600,6 +2661,27 @@
                     }
                 }
             } /* for */
+	    for (i = 0; i < MAXNUMPORTS; i++)
+	    {
+	       if ((soc[i].fd >= 0) && (FD_ISSET(soc[i].fd, &writefds))) {
+		  int err, errlen = sizeof(err);
+		  getsockopt(soc[i].fd, SOL_SOCKET, SO_ERROR, &err, &errlen);
+		  if (err) {
+		     close(soc[i].fd);
+		     soc[i].fd = -1;
+		     push_number(inter_sp, err);
+		     /* Notify object here */
+		     apply(STR_CONNERR, soc[i].ob->u.ob, 1);
+		  } else {
+		     struct sockaddr_in addr;
+		     size_t length = sizeof(addr);
+		     
+		     getpeername(soc[i].fd, (struct sockaddr *) &addr, &length);
+		     new_player(soc[i].fd, &addr, length, 0, soc[i].ob);
+		  }
+	       }
+	    }
+				
             /* check for alarm signal (heart beat) */
             if (comm_time_to_call_heart_beat)
             {
@@ -3227,6 +3309,7 @@
 #else
            , int login_port
 #endif
+	   , svalue_t *n_ob
            )
 
 /* Accept (or reject) a new connection on <new_socket> from <addr> (length
@@ -3419,7 +3502,10 @@
 
     /* Call master->connect() and evaluate the result.
      */
-    ret = callback_master(STR_CONNECT, 0);
+
+    ret = callback_master(STR_CONNECT, 0); /* FIXME (leak?) */
+    if (n_ob)
+        ret = n_ob;
     if (new_interactive != O_GET_INTERACTIVE(master_ob))
         return;
     if (ret == NULL
@@ -3439,7 +3525,6 @@
     O_GET_INTERACTIVE(master_ob) = NULL;
     master_ob->flags &= ~O_ONCE_INTERACTIVE;
     check_shadow_sent(master_ob);
-
     assert_shadow_sent(ob);
     O_GET_INTERACTIVE(ob) = new_interactive;
     new_interactive->ob = ob;
Only in 3-3.ctx/src: comm.c.orig
diff -ru 3-3/src/efuns.c 3-3.ctx/src/efuns.c
--- 3-3/src/efuns.c	2002-10-11 06:09:44.000000000 +0200
+++ 3-3.ctx/src/efuns.c	2002-10-21 13:06:10.000000000 +0200
@@ -7306,5 +7306,31 @@
     return sp;
 } /* f_utime() */
 
+/* EFUN connect_external()
+ *
+ *   void connect_external(string ip, int port, object ob)
+ * 
+ * Establishs a TCP-Connection to a remote service.
+ * If the connection succeeds, ob is set interactive, the remote
+ * service is the commandgiver and logon() is called in the object.
+ * If the connection fails or aborts, connerr(int errno) is called.
+ * 
+ */
+
+svalue_t * f_connect_external (svalue_t *sp)
+{
+   if (O_IS_INTERACTIVE(sp->u.ob))
+     error("Bad arg 3 to connect_external(): Object is already interactive.\n");
+   
+   connect_ext(get_txt(sp[-2].u.str), sp[-1].u.number, sp);
+   
+   free_svalue(sp);
+   free_svalue(sp - 1);
+   free_svalue(sp - 2);
+   return sp - 3;
+}
+
+   
+
 /***************************************************************************/
 
Only in 3-3.ctx/src: efuns.c.orig
diff -ru 3-3/src/func_spec 3-3.ctx/src/func_spec
--- 3-3/src/func_spec	2002-10-05 21:02:39.000000000 +0200
+++ 3-3.ctx/src/func_spec	2002-10-21 12:58:46.000000000 +0200
@@ -672,4 +672,6 @@
 #endif /* USE_DEPRECATED */
 
 
+void connect_external(string, int, object);
+
 /***************************************************************************/
Only in 3-3.ctx/src: func_spec.orig
diff -ru 3-3/src/string_spec 3-3.ctx/src/string_spec
--- 3-3/src/string_spec	2002-10-07 08:04:04.000000000 +0200
+++ 3-3.ctx/src/string_spec	2002-10-21 12:58:46.000000000 +0200
@@ -198,6 +198,8 @@
 PC_CHILDREN     "children"
 PC_SHEEP        "sheep"
 
+CONNERR         "connerr"
+
 #endif /* SUPPLY_PARSE_COMMAND */
 
 /***************************************************************************/
Only in 3-3.ctx/src: string_spec.orig
p-021021 (9,634 bytes)   

Issue History

Date Modified Username Field Change
2004-11-26 20:54 lars New Issue
2004-11-26 20:57 lars Note Added: 0000212
2004-11-26 21:03 lars File Added: p-020912
2004-11-26 21:04 lars Note Added: 0000213
2004-11-26 21:05 lars File Added: p-021021