//
// $Id: Socket.m,v 1.16 2005/07/09 19:01:56 will_mason Exp $
//
// vi: set ft=objc:

/*
 * ObjectiveLib - a library of containers and algorithms for Objective-C
 *
 * Copyright (c) 2004
 * Will Mason
 *
 * Portions:
 *
 * Copyright (c) 1994
 * Hewlett-Packard Company
 *
 * Copyright (c) 1996,1997
 * Silicon Graphics Computer Systems, Inc.
 *
 * Copyright (c) 1997
 * Moscow Center for SPARC Technology
 *
 * Copyright (c) 1999 
 * Boris Fomitchev
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 * You may contact the author at will_mason@users.sourceforge.net.
 */

#import "Socket.h"
#import "FileInStream.h"
#import "FileOutStream.h"
#import "SocketAddressPackage.h"
#import "Macros.h"
#import "ConfigPrivate.h"
#import <Foundation/NSString.h>
#import <Foundation/NSException.h>
#import <sys/types.h>
#import <sys/time.h>
#import <unistd.h>
#import <errno.h>
#import <string.h>
#import <limits.h>

#ifndef WIN32
#import <sys/socket.h>
#import <netinet/in.h>
#if defined(OL_HAVE_UNIX_SOCKETS)
#import <sys/un.h>
#endif
#else
#import <winsock2.h>
#import <ws2tcpip.h>
#define SHUT_RD 0
#define SHUT_WR 1
#define MAXHOSTNAMELEN 256
#endif

typedef union _OLSocketFamilyReunion
{
    struct sockaddr     untyped;
    struct sockaddr_in  inet4;
#if defined(OL_HAVE_INET6_SOCKETS)
    struct sockaddr_in6 inet6;
#endif
#if defined(OL_HAVE_UNIX_SOCKETS)
    struct sockaddr_un  unx;
#endif
} OLSocketFamilyReunion;

@interface OLSocketInStream : OLFileInStream
{
}

- (void) close;

@end

@interface OLSocketOutStream : OLFileOutStream
{
}

- (void) close;

@end

@interface OLSocket (ProtectedMethods)

- (id) initWithFamily: (int)family type: (int)tp allocator: (OLAllocator*)alloc;
- (id) initWithFD: (int)fileDesc allocator: (OLAllocator*)alloc;
- (int) addressFamily;
- (BOOL) getBoolSocketOption: (int)opt;
- (void) getSocketOptionImpl: (int)opt value: (void*)val valueLength: (socklen_t*)len;
- (unsigned) getUnsignedSocketOption: (int)opt;
- (void) setBoolSocketOption: (int)opt value: (BOOL)state;
- (void) setSocketOptionImpl: (int)opt value: (void*)val valueLength: (socklen_t)len;
- (void) setUnsignedSocketOption: (int)opt value: (unsigned)val;

@end

@interface OLInternetServerSocket (ProtectedMethods)

- (int) addressFamily;

@end

@interface OLInternetClientSocket (ProtectedMethods)

- (int) addressFamily;

@end

#if defined(OL_HAVE_UNIX_SOCKETS)

@interface OLUnixServerSocket (ProtectedMethods)

- (int) addressFamily;

@end

@interface OLUnixClientSocket (ProtectedMethods)

- (int) addressFamily;

@end

#endif

@implementation OLSocket

- (void) dealloc
{
    [self close];
    [allocator deallocateObject: inStream];
    [allocator deallocateObject: outStream];
    OBJ_RELEASE(allocator);
    [super dealloc];
}

- (BOOL) allowsBroadcast
{
    return [self getBoolSocketOption: SO_BROADCAST];
}

- (void) close
{
    if (fd != -1)
    {
#ifndef WIN32
        if (close(fd) != 0)
#else
        if (closesocket(fd) != 0)
#endif
        {
            fd = -1;
            [NSException raise: OLSocketException
                format: @"Error closing socket - %s", strerror(errno)];
        }
    }
    fd = -1;
}

- (BOOL) dontRoute
{
    return [self getBoolSocketOption: SO_DONTROUTE];
}

- (BOOL) hasReusePortSupport
{
#if defined(OL_HAVE_SO_REUSEPORT)
    return YES;
#else
    return NO;
#endif
}

- (OLInStream*) inStream
{
    if (inStream == nil)
    {
        inStream = [[allocator allocateObject: [OLSocketInStream class]]
            initWithFD: fd];
    }
    return inStream;
}

- (BOOL) keepAlive
{
    return [self getBoolSocketOption: SO_KEEPALIVE];
}

- (unsigned) linger
{
    struct linger val;
    socklen_t len = sizeof(struct linger);

    [self getSocketOptionImpl: SO_LINGER value: &val valueLength: &len];
    return val.l_onoff ? val.l_linger : UINT_MAX;
}

- (OLSocketAddress*) localAddress
{
    OLSocketFamilyReunion sockaddrs;
    socklen_t len = sizeof(sockaddrs);
    OLSocketAddress* oladdr = nil;

    if (getsockname(fd, &sockaddrs.untyped, &len) != 0)
    {
        [NSException raise: OLSocketException
            format: @"Could not retrieve socket name - %s", strerror(errno)];
    }
    if (len == 0 || (
#if defined(OL_HAVE_INET6_SOCKETS)
        sockaddrs.untyped.sa_family != AF_INET6 &&
#endif
        sockaddrs.untyped.sa_family != AF_INET
        ) )
    {
        [NSException raise: OLSocketException
            format: @"Local address info unavailable for the socket"];
    }
    if (sockaddrs.untyped.sa_family == AF_INET)
    {
        oladdr = [[allocator allocateObject: [OLInternet4Address class]]
            initWithSockaddr: &sockaddrs.inet4 socketType: [self socketType]
            allocator: allocator];
    }
#if defined(OL_HAVE_INET6_SOCKETS)
    else
    {
        oladdr = [[allocator allocateObject: [OLInternet6Address class]]
            initWithSockaddr6: &sockaddrs.inet6 socketType: [self socketType]
            allocator: allocator];
    }
#endif
    return OBJ_COND_AUTORELEASE(oladdr);
}

- (BOOL) outOfBandInline
{
    return [self getBoolSocketOption: SO_OOBINLINE];
}

- (OLOutStream*) outStream
{
    if (outStream == nil)
    {
        outStream = [[allocator allocateObject: [OLSocketOutStream class]]
            initWithFD: fd];
    }
    return outStream;
}

- (unsigned) receiveBufferSize
{
    return [self getUnsignedSocketOption: SO_RCVBUF];
}

- (unsigned) receiveLowWaterMark
{
    return [self getUnsignedSocketOption: SO_RCVLOWAT];
}

- (unsigned) receiveTimeOut
{
    struct timeval val;
    socklen_t len = sizeof(val);

    [self getSocketOptionImpl: SO_RCVTIMEO value: &val valueLength: &len];
    return (val.tv_sec * 1000) + (val.tv_usec / 1000);
}

- (OLSocketAddress*) remoteAddress
{
    OLSocketFamilyReunion sockaddrs;
    socklen_t len = sizeof(sockaddrs);
    OLSocketAddress* oladdr = nil;

    if (getpeername(fd, &sockaddrs.untyped, &len) != 0)
    {
        [NSException raise: OLSocketException
            format: @"Could not retrieve peer name - %s", strerror(errno)];
    }
    if (len == 0 ||
        (sockaddrs.untyped.sa_family != AF_INET 
#if defined(OL_HAVE_INET6_SOCKETS)
         && sockaddrs.untyped.sa_family != AF_INET6
#endif
#if defined(OL_HAVE_UNIX_SOCKETS)
         && sockaddrs.untyped.sa_family != AF_UNIX
#endif
        ) )
    {
        [NSException raise: OLSocketException
            format: @"Remote address info unavailable for the socket"];
    }
    if (sockaddrs.untyped.sa_family == AF_INET)
    {
        oladdr = [[allocator allocateObject: [OLInternet4Address class]]
            initWithSockaddr: &sockaddrs.inet4 socketType: [self socketType]
            allocator: allocator];
    }
#if defined(OL_HAVE_INET6_SOCKETS)
    else if (sockaddrs.untyped.sa_family == AF_INET6)
    {
        oladdr = [[allocator allocateObject: [OLInternet6Address class]]
            initWithSockaddr6: &sockaddrs.inet6 socketType: [self socketType]
            allocator: allocator];
    }
#endif
#if defined(OL_HAVE_UNIX_SOCKETS)
    else // it's AF_UNIX
    {
        oladdr = [[allocator allocateObject: [OLUnixAddress class]]
            initWithSockaddrUnix: &sockaddrs.unx allocator: allocator];
    }
#endif
    return OBJ_COND_AUTORELEASE(oladdr);
}

- (BOOL) reuseAddress
{
    return [self getBoolSocketOption: SO_REUSEADDR];
}

- (BOOL) reusePort
{
#if defined(OL_HAVE_SO_REUSEPORT)
    return [self getBoolSocketOption: SO_REUSEPORT];
#else
    return NO;
#endif
}

- (unsigned) sendBufferSize
{
    return [self getUnsignedSocketOption: SO_SNDBUF];
}

- (unsigned) sendLowWaterMark
{
    return [self getUnsignedSocketOption: SO_SNDLOWAT];
}

- (unsigned) sendTimeOut
{
    struct timeval val;
    socklen_t len = sizeof(val);

    [self getSocketOptionImpl: SO_SNDTIMEO value: &val valueLength: &len];
    return (val.tv_sec * 1000) + (val.tv_usec / 1000);
}

- (void) setAllowsBroadcast: (BOOL)state
{
    [self setBoolSocketOption: SO_BROADCAST value: state];
}

- (void) setDontRoute: (BOOL)state
{
    [self setBoolSocketOption: SO_DONTROUTE value: state];
}

- (void) setKeepAlive: (BOOL)state
{
    [self setBoolSocketOption: SO_KEEPALIVE value: state];
}

- (void) setLinger: (unsigned)value
{
    struct linger val;
    socklen_t len = sizeof(val);

    if (value == UINT_MAX)
    {
        val.l_onoff = 0;
        val.l_linger = 0;
    }
    else
    {
        val.l_onoff = 1;
        val.l_linger = value;
    }
    [self setSocketOptionImpl: SO_LINGER value: &val valueLength: len];
}

- (void) setOutOfBandInline: (BOOL)state
{
    [self setBoolSocketOption: SO_OOBINLINE value: state];
}

- (void) setReceiveBufferSize: (unsigned)size
{
    [self setUnsignedSocketOption: SO_RCVBUF value: size];
}

- (void) setReceiveLowWaterMark: (unsigned)value
{
    [self setUnsignedSocketOption: SO_RCVLOWAT value: value];
}

- (void) setReceiveTimeOut: (unsigned)value
{
    struct timeval val;
    socklen_t len = sizeof(val);

    val.tv_sec = value / 1000;
    val.tv_usec = (value % 1000) * 1000;
    [self setSocketOptionImpl: SO_RCVTIMEO value: &val valueLength: len];
}

- (void) setReuseAddress: (BOOL)state
{
    [self setBoolSocketOption: SO_REUSEADDR value: state];
}

- (void) setReusePort: (BOOL)state
{
#if defined(OL_HAVE_SO_REUSEPORT)
    [self setBoolSocketOption: SO_REUSEPORT value: state];
#endif
}

- (void) setSendBufferSize: (unsigned)size
{
    [self setUnsignedSocketOption: SO_SNDBUF value: size];
}

- (void) setSendLowWaterMark: (unsigned)value
{
    [self setUnsignedSocketOption: SO_SNDLOWAT value: value];
}

- (void) setSendTimeOut: (unsigned)value
{
    struct timeval val;
    socklen_t len = sizeof(val);

    val.tv_sec = value / 1000;
    val.tv_usec = (value % 1000) * 1000;
    [self setSocketOptionImpl: SO_SNDTIMEO value: &val valueLength: len];
}

- (void) shutdownRead
{
    if (shutdown(fd, SHUT_RD) != 0)
    {
        [NSException raise: OLSocketException
            format: @"Could not shut down read - %s", strerror(errno)];
    }
}

- (void) shutdownWrite
{
    if (shutdown(fd, SHUT_WR) != 0)
    {
        [NSException raise: OLSocketException
            format: @"Could not shut down write - %s", strerror(errno)];
    }
}

- (int) socketType
{
    return [self getUnsignedSocketOption: SO_TYPE];
}

@end

@implementation OLBindableSocket

- (void) bindToAddress: (OLSocketAddress*)address
{
    if (bind(fd, [address hostRepresentation], [address hostRepresentationLength]) != 0)
    {
        [NSException raise: OLSocketException
            format: @"Error binding socket - %s", strerror(errno)];
    }
}

@end

@implementation OLServerSocket

- (id) init
{
    return [self initWithQueueLength: 50 allocator: [OLAllocator defaultAllocator]];
}

- (id) initWithLocalAddress: (OLSocketAddress*)address
{
    return [self initWithLocalAddress: address queueLength: 50];
}

- (id) initWithLocalAddress: (OLSocketAddress*)address queueLength: (unsigned)qLen
{
    return [self initWithLocalAddress: address queueLength: qLen
        allocator: [OLAllocator defaultAllocator]];
}

- (id) initWithLocalAddress: (OLSocketAddress*)address queueLength: (unsigned)qLen allocator: (OLAllocator*)alloc
{
    [self initWithQueueLength: qLen allocator: alloc];
    [self bindToAddress: address];
    return self;
}

- (id) initWithQueueLength: (unsigned)qLen
{
    return [self initWithQueueLength: qLen allocator: [OLAllocator defaultAllocator]];
}

- (id) initWithQueueLength: (unsigned)qLen allocator: (OLAllocator*)alloc
{
    [super initWithFamily: [self addressFamily] type: SOCK_STREAM allocator: alloc];
    queueLength = qLen;
    return self;
}

- (OLSocket*) acceptConnection
{
    int sockFD = accept(fd, NULL, NULL);

    if (sockFD == -1)
    {
        [NSException raise: OLSocketException
            format: @"Error accepting connection - %s", strerror(errno)];
    }
    return OBJ_COND_AUTORELEASE([[allocator allocateObject: [OLSocket class]]
        initWithFD: sockFD allocator: allocator]);
}

- (void) bindToAddress: (OLSocketAddress*)address
{
    [super bindToAddress: address];
    if (listen(fd, queueLength) != 0)
    {
        [NSException raise: OLSocketException
            format: @"Error listening to connection - %s", strerror(errno)];
    }
}

- (unsigned) queueLength
{
    return queueLength;
}

@end

@implementation OLClientSocket

- (id) init
{
    return [self initWithAllocator: [OLAllocator defaultAllocator]];
}

- (id) initWithAllocator: (OLAllocator*)alloc
{
    [super initWithFamily: [self addressFamily] type: SOCK_STREAM allocator: alloc];
    return self;
}

- (id) initWithRemoteAddress: (OLSocketAddress*)remote
{
    return [self initWithRemoteAddress: remote allocator: [OLAllocator defaultAllocator]];
}

- (id) initWithRemoteAddress: (OLSocketAddress*)remote allocator: (OLAllocator*)alloc
{
    [self initWithAllocator: alloc];
    [self connectToAddress: remote];
    return self;
}

- (id) initWithRemoteAddress: (OLSocketAddress*)remote localAddress: (OLSocketAddress*)local
{
    return [self initWithRemoteAddress: remote localAddress: local
        allocator: [OLAllocator defaultAllocator]];
}

- (id) initWithRemoteAddress: (OLSocketAddress*)remote localAddress: (OLSocketAddress*)local allocator: (OLAllocator*)alloc
{
    [self initWithAllocator: alloc];
    [self bindToAddress: local];
    [self connectToAddress: remote];
    return self;
}

- (void) connectToAddress: (OLSocketAddress*)address
{
    if (connect(fd, [address hostRepresentation], [address hostRepresentationLength]) != 0)
    {
        [NSException raise: OLSocketException
            format: @"Error creating connection - %s", strerror(errno)];
    }
}

@end

@implementation OLInternetServerSocket

+ (id) socket
{
    OL_BEGIN_AUTO_CTOR(OLInternetServerSocket)
        init
    OL_END_AUTO_CTOR;
}

+ (id) socketWithLocalInternetAddress: (OLInternetAddress*)address
{
    OL_BEGIN_AUTO_CTOR(OLInternetServerSocket)
        initWithLocalAddress: address
    OL_END_AUTO_CTOR;
}

+ (id) socketWithLocalInternetAddress: (OLInternetAddress*)address queueLength: (unsigned)qLen
{
    OL_BEGIN_AUTO_CTOR(OLInternetServerSocket)
        initWithLocalAddress: address queueLength: qLen
    OL_END_AUTO_CTOR;
}

+ (id) socketWithQueueLength: (unsigned)qLen
{
    OL_BEGIN_AUTO_CTOR(OLInternetServerSocket)
        initWithQueueLength: qLen
    OL_END_AUTO_CTOR;
}

@end

@implementation OLInternetClientSocket

+ (id) socket
{
    OL_BEGIN_AUTO_CTOR(OLInternetClientSocket)
        init
    OL_END_AUTO_CTOR;
}

+ (id) socketWithRemoteInternetAddress: (OLInternetAddress*)remote
{
    OL_BEGIN_AUTO_CTOR(OLInternetClientSocket)
        initWithRemoteAddress: remote
    OL_END_AUTO_CTOR;
}

+ (id) socketWithRemoteInternetAddress: (OLInternetAddress*)remote localAddress: (OLInternetAddress*)local
{
    OL_BEGIN_AUTO_CTOR(OLInternetClientSocket)
        initWithRemoteAddress: remote localAddress: local
    OL_END_AUTO_CTOR;
}

@end

#if defined(OL_HAVE_UNIX_SOCKETS)

@implementation OLUnixServerSocket

+ (id) socket
{
    OL_BEGIN_AUTO_CTOR(OLUnixServerSocket)
        init
    OL_END_AUTO_CTOR;
}

+ (id) socketWithLocalUnixAddress: (OLUnixAddress*)address
{
    OL_BEGIN_AUTO_CTOR(OLUnixServerSocket)
        initWithLocalAddress: address
    OL_END_AUTO_CTOR;
}

+ (id) socketWithLocalUnixAddress: (OLUnixAddress*)address queueLength: (unsigned)qLen
{
    OL_BEGIN_AUTO_CTOR(OLUnixServerSocket)
        initWithLocalAddress: address queueLength: qLen
    OL_END_AUTO_CTOR;
}

+ (id) socketWithQueueLength: (unsigned)qLen
{
    OL_BEGIN_AUTO_CTOR(OLUnixServerSocket)
        initWithQueueLength: qLen
    OL_END_AUTO_CTOR;
}

- (void) bindToAddress: (OLSocketAddress*)address
{
    [super bindToAddress: address];
    localAddress = OBJ_RETAIN(address);
}

- (void) close
{
    [super close];
    if (localAddress != nil)
    {
        unlink(((const struct sockaddr_un*)[localAddress hostRepresentation])->sun_path);
        OBJ_RELEASE(localAddress);
        localAddress = nil;
    }
}

- (OLSocketAddress*) localAddress
{
    if (localAddress == nil)
    {
        [NSException raise: OLSocketException
            format: @"The socket is not yet bound to an address"];
    }
    return localAddress;
}

@end

@implementation OLUnixClientSocket

+ (id) socket
{
    OL_BEGIN_AUTO_CTOR(OLUnixClientSocket)
        init
    OL_END_AUTO_CTOR;
}

+ (id) socketWithRemoteUnixAddress: (OLUnixAddress*)remote
{
    OL_BEGIN_AUTO_CTOR(OLUnixClientSocket)
        initWithRemoteAddress: remote
    OL_END_AUTO_CTOR;
}

+ (id) socketWithRemoteUnixAddress: (OLUnixAddress*)remote localAddress: (OLUnixAddress*)local
{
    OL_BEGIN_AUTO_CTOR(OLUnixClientSocket)
        initWithRemoteAddress: remote localAddress: local
    OL_END_AUTO_CTOR;
}

- (void) bindToAddress: (OLSocketAddress*)address
{
    // This is a no-op
}

@end

#endif

@implementation OLSocket (ProtectedMethods)

- (id) initWithFamily: (int)family type: (int)tp allocator: (OLAllocator*)alloc
{
    [super init];
    allocator = OBJ_RETAIN(alloc);
    inStream = nil;
    outStream = nil;
    fd = socket(family, tp, 0);
    if (fd == -1)
    {
        [NSException raise: OLSocketException
            format: @"Error creating socket - %s", strerror(errno)];
    }
    return self;
}

- (id) initWithFD: (int)fileDesc allocator: (OLAllocator*)alloc
{
    [super init];
    allocator = OBJ_RETAIN(alloc);
    inStream = nil;
    outStream = nil;
    fd = fileDesc;
    return self;
}

- (int) addressFamily
{
    return AF_UNSPEC;
}

- (BOOL) getBoolSocketOption: (int)opt
{
    int val;
    socklen_t len = sizeof(int);

    [self getSocketOptionImpl: opt value: &val valueLength: &len];
    return val ? YES : NO;
}

- (void) getSocketOptionImpl: (int)opt value: (void*)val valueLength: (socklen_t*)len
{
    if (getsockopt(fd, SOL_SOCKET, opt, val, len) != 0)
    {
        [NSException raise: OLSocketException
            format: @"Could not retrieve socket option - %s", strerror(errno)];
    }
}

 - (unsigned) getUnsignedSocketOption: (int)opt
{
    int val;
    socklen_t len = sizeof(int);

    [self getSocketOptionImpl: opt value: &val valueLength: &len];
    return val;
}

- (void) setBoolSocketOption: (int)opt value: (BOOL)state
{
    int val = state ? 1 : 0;
    socklen_t len = sizeof(int);

    [self setSocketOptionImpl: opt value: &val valueLength: len];
}

- (void) setSocketOptionImpl: (int)opt value: (void*)val valueLength: (socklen_t)len
{
    if (setsockopt(fd, SOL_SOCKET, opt, val, len) != 0)
    {
        [NSException raise: OLSocketException
            format: @"Could not set socket option - %s", strerror(errno)];
    }
}

- (void) setUnsignedSocketOption: (int)opt value: (unsigned)val
{
    socklen_t len = sizeof(unsigned);

    [self setSocketOptionImpl: opt value: &val valueLength: len];
}

@end

@implementation OLInternetServerSocket (ProtectedMethods)

- (int) addressFamily
{
    return AF_INET;
}

@end

@implementation OLInternetClientSocket (ProtectedMethods)

- (int) addressFamily
{
    return AF_INET;
}

@end

#if defined(OL_HAVE_UNIX_SOCKETS)

@implementation OLUnixServerSocket (ProtectedMethods)

- (int) addressFamily
{
    return AF_UNIX;
}

@end

@implementation OLUnixClientSocket (ProtectedMethods)

- (int) addressFamily
{
    return AF_UNIX;
}

@end

#endif

@implementation OLSocketInStream

- (void) close
{
}

- (ssize_t) readImpl: (int)fdesc buffer: (uint8_t*)buffer max: (unsigned)max
{
    return recv(fdesc, buffer, max, 0);
}

@end

@implementation OLSocketOutStream

- (void) close
{
}

- (ssize_t) writeImpl: (int)fdesc buffer: (uint8_t*)buffer count: (unsigned)count
{
    return send(fdesc, buffer, count, 0);
}

@end


