grendel-1.0.0a7/backup/
grendel-1.0.0a7/bin/
grendel-1.0.0a7/boards/
grendel-1.0.0a7/clans/
grendel-1.0.0a7/documentation/todo/
grendel-1.0.0a7/help/
grendel-1.0.0a7/logs/
grendel-1.0.0a7/players/
grendel-1.0.0a7/progs/
grendel-1.0.0a7/races/
grendel-1.0.0a7/src/contrib/
grendel-1.0.0a7/src/modules/speller/
grendel-1.0.0a7/src/modules/status/
grendel-1.0.0a7/src/tests/
grendel-1.0.0a7/src/tests/dunit/
{
  Summary:
  	Wrappers for IPv4 and IPv6 socket operations
  	
  ## $Id: socket.pas,v 1.9 2004/03/30 12:22:42 druid Exp $
}

unit socket;

interface


uses
{$IFDEF WIN32}
	Winsock2;
{$ENDIF}
{$IFDEF LINUX}
	KernelIoctl,
	Libc;
{$ENDIF}


type
{$IFDEF LINUX}
	TSockAddr = sockaddr;
	TSockAddr6 = sockaddr_in6;
	TSockAddr_Storage = sockaddr_storage;
{$ENDIF}

	GSocketTypes = (SOCKTYPE_IPV4, SOCKTYPE_IPV6);
	
	GSocket = class
	private
		af : integer;
		fd : TSocket;
		addr : TSockAddr_Storage;
		rw_set, ex_set : TFDSet;
		time : TTimeVal;
		ip_string : string;
		host_string : string;

	public
		procedure resolve(lookup_hosts : boolean); 
		procedure disconnect();
  
		procedure openPort(port : integer); virtual;
    
		procedure setNonBlocking();

		function isValid() : boolean;

		function canRead() : boolean;
		function canWrite() : boolean;
		function read(var Buf; len : integer) : integer;
		function send(s : string) : integer; overload;
		function send(var s; len : integer) : integer; overload;
    
		function acceptConnection(lookup_hosts : boolean) : GSocket;
    
		function connect(const remoteName : string; port : integer) : boolean;
    
		constructor Create(_af : integer; _fd : TSocket = -1);
		destructor Destroy; override;
    
		property getDescriptor : TSocket read fd;
		property getAddressFamily : integer read af;
		property socketAddress : TSockAddr_Storage read addr write addr;
		property hostString : string read host_string;
		property ipString : string read ip_string;
	end;
  
	GSocket4 = class(GSocket)
	private
		addrv4 : TSockAddrIn;
    
	public
		constructor Create(); overload;
		constructor Create(fd : TSocket); overload;

		procedure openPort(port : integer); override;
	end;
  
	GSocket6 = class(GSocket)
	private
		addrv6 : TSockAddr6;
		ssv6 : TSockAddr_Storage;
		addrv6p : PSockAddr;
    
	public
		constructor Create(); overload;
		constructor Create(fd : TSocket); overload;

		procedure openPort(port : integer); override;
	end;


function isSupported(socketType : GSocketTypes) : boolean; overload;
function isSupported(af : integer) : boolean; overload;

function createSocket(socketType : GSocketTypes; fd : TSocket = -1) : GSocket; overload;
function createSocket(af : integer; fd : TSocket = -1) : GSocket; overload;


implementation


uses
	SysUtils;


{$IFDEF WIN32}
var
	hWSAData : TWSAData;
	ver : integer;
{$ENDIF}


{ Convert a symbolic socketType to address family }
function socketTypeToFamily(socketType : GSocketTypes) : integer;
begin
	case socketType of
		SOCKTYPE_IPV4: Result := AF_INET;
		SOCKTYPE_IPV6: Result := AF_INET6;
	end;
end;

{ Check wether socketType is a supported address family }
function isSupported(socketType : GSocketTypes) : boolean;
begin
	Result := isSupported(socketTypeToFamily(socketType));
end;

{ Check wether af is a supported address family }
function isSupported(af : integer) : boolean;
{$IFDEF WIN32}
var
	x, ret, size : integer;
	lpprot : array[0..1] of integer;
	lpinfo, pinfo : LPWSAProtocol_Info;
begin
	Result := false;

	size := SizeOf(TWSAProtocol_Info) * 10;
	
	GetMem(lpinfo, size);

	lpprot[0] := IPPROTO_TCP;
	lpprot[1] := 0;

	x := size;
	ret := WSAEnumProtocols(@lpprot, lpinfo, x);
	
	if (ret = SOCKET_ERROR) then
		begin
		raise Exception.Create('WSAEnumProtocols failed: ' + IntToStr(WSAGetLastError()));
		end
	else
		begin
		for x := 0 to ret - 1 do
			begin
			pinfo := pointer(integer(lpinfo) + (x * sizeof(TWSAProtocol_Info)));

			if (pinfo^.iAddressFamily = af) then
				Result := true;
			end;
		end;
		
	FreeMem(lpinfo, size);
end;
{$ELSE}
var
	fd : TSocket;
begin
	fd := Libc.socket(af, SOCK_STREAM, IPPROTO_TCP);

	if (fd = INVALID_SOCKET) then
		Result := false
	else
		Result := true;
end;
{$ENDIF}

{ Create a socket of type socketType }
function createSocket(socketType : GSocketTypes; fd : TSocket = -1) : GSocket;
begin
	Result := createSocket(socketTypeToFamily(socketType), fd);
end;

{ Create a socket of type af }
function createSocket(af : integer; fd : TSocket = -1) : GSocket;
begin
	if (af = AF_INET) then
		Result := GSocket4.Create(fd)
	else
	if (af = AF_INET6) then
		Result := GSocket6.Create(fd)
	else
		raise Exception.Create('Unsupported address family');
end;


// GSocket
constructor GSocket.Create(_af : integer; _fd : TSocket = -1);
begin
  inherited Create();
  
  af := _af;

  if (_fd = -1) then
    begin
    {$IFDEF WIN32}
    fd := Winsock2.socket(af, SOCK_STREAM, IPPROTO_TCP);
    {$ENDIF}
    {$IFDEF LINUX}
    fd := Libc.socket(af, SOCK_STREAM, IPPROTO_TCP);
    {$ENDIF}

    if (fd = INVALID_SOCKET) then
      raise Exception.Create('Could not create socket.');
    end
  else
    fd := _fd;
end;

destructor GSocket.Destroy;
begin
  disconnect();

  inherited Destroy();
end;

procedure GSocket.disconnect();
begin
{$IFDEF LINUX}
  __close(fd);
{$ENDIF}
{$IFDEF WIN32}
  closesocket(fd);
{$ENDIF}
end;

procedure GSocket.resolve(lookup_hosts : boolean);
var
  h : PHostEnt;
  l, p : integer;
  v6 : TSockAddr6;
  v4 : TSockAddr;
begin
{$IFDEF LINUX}
  if (addr.__ss__family = AF_INET) then
{$ENDIF}
{$IFDEF WIN32}
  if (addr.ss_family = AF_INET) then
{$ENDIF}
    begin
    move(addr, v4, sizeof(v4));

    ip_string := inet_ntoa(v4.sin_addr);

    if (lookup_hosts) then
      begin
      h := gethostbyaddr(@v4.sin_addr.s_addr, 4, AF_INET);

      if (h <> nil) then
        host_string := h.h_name
      else
        host_string := ip_string;
      end
    else
      host_string := ip_string;
    end
  else
{$IFDEF LINUX}
  if (addr.__ss__family = AF_INET6) then
{$ENDIF}
{$IFDEF WIN32}
  if (addr.ss_family = AF_INET6) then
{$ENDIF}
    begin
    move(addr, v6, sizeof(v6));

    l := 0;

    while (l < 16) do
      begin
      p := (byte(v6.sin6_addr.s6_addr[l]) shl 8) + byte(v6.sin6_addr.s6_addr[l + 1]);

      if (p = 0) then
        begin
        ip_string := ip_string + ':';

        while (p = 0) do
          begin
          p := (byte(v6.sin6_addr.s6_addr[l]) shl 8) + byte(v6.sin6_addr.s6_addr[l + 1]);

          inc(l, 2);
          end;
        end
      else
        inc(l, 2);

      if (ip_string <> '') then
        ip_string := ip_string + ':';

      ip_string := ip_string + lowercase(inttohex(p, 1));
      end;

    host_string := ip_string;
    end;
end;

function GSocket.isValid() : boolean;
begin
	Result := (fd <> INVALID_SOCKET);
end;

function GSocket.canRead() : boolean;
begin
	Result := false;
	
	if (not isValid) then
		exit;
  
	FD_ZERO(rw_set);
	FD_SET(fd, rw_set);
	FD_ZERO(ex_set);
	FD_SET(fd, ex_set);

	time.tv_sec := 0;
	time.tv_usec := 0;

	if (select(fd + 1, @rw_set, nil, @ex_set, @time) = SOCKET_ERROR) or (FD_ISSET(fd, ex_set)) then
		begin
		fd := INVALID_SOCKET;
		raise Exception.Create('Connection reset by peer');
		end;

	if (FD_ISSET(fd, rw_set)) then
		Result := true;
end;

function GSocket.canWrite() : boolean;
begin
	Result := false;

	if (not isValid) then
		exit;
  
	FD_ZERO(rw_set);
	FD_SET(fd, rw_set);
	FD_ZERO(ex_set);
	FD_SET(fd, ex_set);

	time.tv_sec := 0;
	time.tv_usec := 0;

	if (select(fd + 1, nil, @rw_set, @ex_set, @time) = SOCKET_ERROR) or (FD_ISSET(fd, ex_set)) then
		begin
		fd := INVALID_SOCKET;
		raise Exception.Create('Connection reset by peer');
		end;

	if (FD_ISSET(fd, rw_set)) then
		Result := true;
end;

function GSocket.send(s : string) : integer;
begin
	if (not isValid) then
		begin
		Result := -1;
		exit;
		end;
		
	Result := send(s[1], length(s));
end;

function GSocket.send(var s; len : integer) : integer;
var
	res : integer;
begin
	if (not isValid) then
		begin
		Result := -1;
		exit;
		end;

	res := 0;
  
	if (len > 0) then	
{$IFDEF WIN32}
		res := Winsock2.send(fd, s, len, 0);
{$ENDIF}
{$IFDEF LINUX}
		res := Libc.send(fd, s, len, 0);
{$ENDIF}

	if (res = SOCKET_ERROR) then
		begin
		fd := INVALID_SOCKET;
		raise Exception.Create('Connection reset by peer');
		end;
    
	Result := res;
end;

function GSocket.read(var buf; len : integer) : integer;
var
	res : integer;
begin
	res := recv(fd, buf, len, 0);
  
	if (res = SOCKET_ERROR) then
		begin
		fd := INVALID_SOCKET;
		raise Exception.Create('Connection reset by peer');
		end;
    
	Result := res;
end;

procedure GSocket.setNonBlocking();
var
	x : integer;
begin
	if (not isValid) then
		exit;

{$IFDEF WIN32}
	x := 1;
	ioctlsocket(fd, FIONBIO, x);
{$ENDIF}
{$IFDEF LINUX}
 	x := fcntl(fd, F_GETFL);
	fcntl(fd, F_SETFL, x or O_NONBLOCK);
{$ENDIF}
end;

function GSocket.acceptConnection(lookup_hosts : boolean) : GSocket;
var
	ac_fd : TSocket;
	client_addr : TSockAddr_Storage;
	len : integer;
	sk : GSocket;
begin
	len := 128;

{$IFDEF LINUX}  
	ac_fd := accept(fd, PSockAddr(@client_addr), @len);
{$ELSE}
	ac_fd := accept(fd, PSockAddr(@client_addr)^, len);
{$ENDIF}

	sk := createSocket(af, ac_fd);
	sk.addr := client_addr;

	sk.resolve(lookup_hosts);

	Result := sk;
end;

function GSocket.connect(const remoteName : string; port : integer) : boolean;
var
	addrLength : integer;
	addrPointer : PChar;
	sockAddr : TSockAddr;
	hostent : PHostEnt;
begin
	hostent := gethostbyname(PChar(remoteName));
	
	if (hostent = nil) then
		raise Exception.Create('Could not resolve hostname ' + remoteName);

	addrLength := hostent^.h_length;
	addrPointer := hostent^.h_addr_list^;

	sockAddr.sin_family := af;
	sockAddr.sin_port := htons(port);
	StrMove (PChar(@sockAddr.sin_addr.s_addr), addrPointer, addrLength);
        
	{$IFDEF WIN32}
	Result := WinSock2.connect(fd, sockAddr, sizeof(sockAddr)) = 0;
	{$ENDIF}

	{$IFDEF LINUX}
	Result := Libc.connect(fd, sockAddr, sizeof(sockAddr)) = 0;
	{$ENDIF}
end;

procedure GSocket.openPort(port : integer);
begin
  raise Exception.Create('Operation not supported');
end;


// GSocket4
constructor GSocket4.Create;
begin 
	inherited Create(AF_INET);
end;

constructor GSocket4.Create(fd : TSocket);
begin 
	inherited Create(AF_INET, fd);
end;

procedure GSocket4.openPort(port : integer);
var
  rc : integer;
begin
  rc := 1;
    
  if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, @rc, sizeof(rc)) < 0) then
    raise Exception.Create('Could not set option on IPv4 socket.');

  addrv4.sin_family := AF_INET;
  addrv4.sin_port := htons(port);
//  addrv4.sin_addr.s_addr := system_info.bind_ip;
  addrv4.sin_addr.s_addr := INADDR_ANY;

  if (bind(fd, TSockaddr(addrv4), sizeof(addrv4)) = -1) then
    begin
{$IFDEF LINUX}
    __close(fd);
{$ELSE}
    closesocket(fd);
{$ENDIF}

    raise Exception.Create('Could not bind on IPv4 port ' + inttostr(port));
    end;

  rc := listen(fd, 15);

  if (rc > 0) then
    raise Exception.Create('Could not listen on IPv4 socket');
end;

// GSocket6
constructor GSocket6.Create;
begin 
  inherited Create(AF_INET6);
end;

constructor GSocket6.Create(fd : TSocket);
begin 
  inherited Create(AF_INET6, fd);
end;

procedure GSocket6.openPort(port : integer);
var
  rc : integer;
begin
  rc := 1;
    
  if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, @rc, sizeof(rc)) < 0) then
    raise Exception.Create('Could not set option on IPv6 socket.');

  addrv6.sin6_family := AF_INET6;
  addrv6.sin6_port := htons(port);

  move(addrv6, ssv6, sizeof(addrv6));

  addrv6p := @ssv6;

  if (bind(fd, addrv6p^, 128) = -1) then
    begin
{$IFDEF LINUX}
    __close(fd);
{$ELSE}
    closesocket(fd);
{$ENDIF}

    raise Exception.Create('Could not bind on IPv6 port ' + inttostr(port));
    end;

  rc := listen(fd, 15);

  if (rc > 0) then
    raise Exception.Create('Could not listen on IPv6 socket');
end;

initialization
{$IFDEF WIN32}
  ver := WINSOCK_VERSION;

  if (WSAStartup(ver, hWSAData) <> 0) then
    raise Exception.Create('Could not perform WSAStartup');
{$ENDIF}

finalization
{$IFDEF WIN32}
  WSACleanup();
{$ENDIF}

end.