// Copyright 1997-2001 Omni Development, Inc.  All rights reserved.
//
// This software may only be used and reproduced according to the
// terms in the file OmniSourceLicense.html, which should be
// distributed with this project and can also be found at
// http://www.omnigroup.com/DeveloperResources/OmniSourceLicense.html.

#import "ONMulticastSocket.h"

#import <Foundation/Foundation.h>
#import <OmniBase/OmniBase.h>
#import <OmniBase/system.h>

#import "ONInternetSocket-Private.h"
#import "ONHostAddress.h"
#import "ONPortAddress.h"

RCS_ID("$Header: /NetworkDisk/Source/CVS/OmniGroup/Frameworks/OmniNetworking/ONMulticastSocket.m,v 1.14 2001/03/06 01:21:34 kc Exp $")

@interface ONMulticastSocket (Private)
- (void)_changeGroupMembership:(ONHostAddress *)groupAddress localInterface:(ONHostAddress *)localInterface operation:(unsigned int)op;
@end


@implementation ONMulticastSocket

+ (unsigned int)maximumGroupMemberships;
{
#if defined(IP_MAX_MEMBERSHIPS)
    return IP_MAX_MEMBERSHIPS;
#else
    return INT_MAX; // Seems this limitation does not exist in Solaris 2.6
#endif
}

- (void)setSendTimeToLive:(unsigned char)ttl;
{
    if (setsockopt(socketFD, [isa ipProtocol], IP_MULTICAST_TTL, (char *)&ttl, sizeof(ttl)) == -1)
        [NSException raise:ONMulticastSocketSetTimeToLiveFailed format:@"Failed to set time to live to %d on socket %@", ttl, self];
}

- (void)joinReceiveGroup:(ONHostAddress *)groupAddress localInterface:(ONHostAddress *)localInterface;
{
    [self _changeGroupMembership:groupAddress localInterface:localInterface operation:IP_ADD_MEMBERSHIP];
}

- (void)leaveReceiveGroup:(ONHostAddress *)groupAddress localInterface:(ONHostAddress *)localInterface;
{
    [self _changeGroupMembership:groupAddress localInterface:localInterface operation:IP_DROP_MEMBERSHIP];
}

- (void)joinReceiveGroup:(ONHostAddress *)groupAddress;
{
    [self joinReceiveGroup:groupAddress localInterface:nil];
}

- (void)leaveReceiveGroup:(ONHostAddress *)groupAddress;
{
    [self leaveReceiveGroup:groupAddress localInterface:nil];
}

// This is untested since I don't actually have a machine with multiple interfaces yet
- (void)setSendMulticastInterface:(ONHostAddress *)interfaceAddress;
{
    struct in_addr address;

    if (interfaceAddress) {
        address = *[interfaceAddress internetAddress];
    } else
        // Use the default interface
        address.s_addr = INADDR_ANY;

    if (setsockopt(socketFD, [isa ipProtocol], IP_MULTICAST_IF, (char *)&address, sizeof(address)) == -1)
        [NSException raise:ONMulticastSocketSendInterfaceSelectionFailed format:@"Failed to set interface to %@ socket %@", interfaceAddress, self];
}

- (void)setShouldLoopMessagesToMemberGroups:(BOOL)shouldLoopMessages;
{
    if (setsockopt(socketFD, [isa ipProtocol], IP_MULTICAST_LOOP, &shouldLoopMessages, sizeof(shouldLoopMessages)) == -1)
        [NSException raise:ONMulticastSocketFailedToSelectLooping format:@"Failed to set local looping to %d on socket %@", shouldLoopMessages, self];
}

// ONInternetSocket subclass

+ (int)ipProtocol;
{
    return IPPROTO_IP;
}

// ONSocket subclass

- (unsigned int)writeBytes:(unsigned int)byteCount fromBuffer:(const void *)aBuffer toPortAddress:(ONPortAddress *)aPortAddress;
{
    if (![aPortAddress isMulticastAddress])
        [NSException raise:ONMulticastSocketNonMulticastAddress format:@"Cannot send to the address %@ since it is not a multicast address", aPortAddress];

    return [super writeBytes:byteCount fromBuffer:aBuffer toPortAddress:aPortAddress];
}

@end

@implementation ONMulticastSocket (Private)

- (void)_changeGroupMembership:(ONHostAddress *)groupAddress localInterface:(ONHostAddress *)localInterface operation:(unsigned int)op;
{
    struct ip_mreq imr;
    const struct in_addr *address;

    address = [groupAddress internetAddress];

    imr.imr_multiaddr.s_addr = address->s_addr;

    if (localInterface)
        imr.imr_interface.s_addr = [localInterface internetAddress]->s_addr;
    else
        imr.imr_interface.s_addr = INADDR_ANY;

    if (setsockopt(socketFD, [isa ipProtocol], op, (char *)&imr, sizeof(imr)) == -1)
        [NSException raise:ONMulticastSocketGroupMembershipOperationFailed format:@"Multicast socket %@ failed to %@ the address %@ on the interface %@", self, (op == IP_ADD_MEMBERSHIP) ? @"join" : @"leave", groupAddress, localInterface];
}


@end


NSString *ONMulticastSocketNonMulticastAddress = @"ONMulticastSocketNonMulticastAddress";
NSString *ONMulticastSocketSetTimeToLiveFailed = @"ONMulticastSocketSetTimeToLiveFailed";
NSString *ONMulticastSocketGroupMembershipOperationFailed = @"ONMulticastSocketGroupMembershipOperationFailed";
NSString *ONMulticastSocketSendInterfaceSelectionFailed = @"ONMulticastSocketSendInterfaceSelectionFailed";
NSString *ONMulticastSocketFailedToSelectLooping = @"ONMulticastSocketFailedToSelectLooping";

