Redline/source/maclocaltracker.cpp
2016-04-02 15:51:03 +02:00

491 lines
16 KiB
C++

#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include "error.h"
#include "tracker.h"
#include "gamemem.h"
#define assert(condition) ((condition) ? ((void) 0) : PrintConsoleString("%s@%s:%s",#condition,__FILE__, __LINE__))
#define kMaxLocalGames 32
int gNumLocalGames=0;
int gLocalGameListUpdate=0;
tGameListEntry gLocalGames[kMaxLocalGames];
static void
MyResolveCallback(CFNetServiceRef service, CFStreamError* error, void* info);
#define kServiceType CFSTR("_redline._udp.")
#define kMyDefaultDomain CFSTR("local.")
CFMutableArrayRef gServiceArrayRef;
CFMutableDictionaryRef gServiceDictionary;
CFNetServiceBrowserRef gServiceBrowserRef = NULL;
CFNetServiceRef gRegisteredService = NULL;
CFNetServiceRef gServiceBeingResolved = NULL;
UInt32 gNextNameIndexToResolve = 0;
CFStringRef gTextRecord;
Boolean gDone;
Boolean gContinue;
Boolean gResolve;
Boolean gBrowserTaskActive = FALSE;
Boolean gResolveProcessActive = FALSE;
Boolean gTaskRegistered = FALSE;
void InitLocalTracker()
{
OSStatus err = noErr;
gServiceDictionary = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFCopyStringDictionaryKeyCallBacks, NULL);
if (gServiceDictionary == NULL)
err = coreFoundationUnknownErr;
if (err == noErr)
{
gServiceArrayRef = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks);
if (gServiceDictionary == NULL)
{
err = coreFoundationUnknownErr;
}
}
//if (err == noErr)
// err = LoadNetServicesForCFM();
//return err;
}
static void
MyCancelRegistration()
{
if(gRegisteredService != NULL)
{
CFNetServiceUnscheduleFromRunLoop(gRegisteredService, CFRunLoopGetCurrent(), kCFRunLoopCommonModes);
CFNetServiceSetClient(gRegisteredService, NULL, NULL);
CFNetServiceCancel(gRegisteredService);
CFRelease(gRegisteredService);
gRegisteredService = NULL;
}
}
static void
MyRegisterCallback(CFNetServiceRef theService, CFStreamError* error, void* info)
{
if (error->domain == kCFStreamErrorDomainNetServices)
{
switch(error->error)
{
case kCFNetServicesErrorCollision:
/* Somebody else on the network has registered a service with the same name and type
as the service we tried to register
*/
PrintConsoleString("A kCFNetServicesErrorCollision occured - will quit now\n");
break;
default:
/*
some other error occurred
*/
PrintConsoleString("Some other kCFNetServicesError occurred %d will quit now\n", error->error);
break;
}
// as an error occurred, clean up the CFNetServiceRef object
MyCancelRegistration();
// you don't really need to quit, but the following boolean will cause the main to quit.
gDone = true;
}
return;
}
static void
MyCancelResolve()
{
assert(gServiceBeingResolved != NULL);
CFNetServiceUnscheduleFromRunLoop(gServiceBeingResolved, CFRunLoopGetCurrent(), kCFRunLoopCommonModes);
CFNetServiceSetClient(gServiceBeingResolved, NULL, NULL);
CFNetServiceCancel(gServiceBeingResolved);
CFRelease(gServiceBeingResolved);
gServiceBeingResolved = NULL;
return;
}
static void
MyResolveService(CFStringRef name, CFStringRef type, CFStringRef domain)
{
CFNetServiceClientContext clientContext = { 0, NULL, NULL, NULL, NULL };
CFStreamError error;
assert(name != NULL);
assert(type != NULL);
assert(domain != NULL);
if (gServiceBeingResolved)
{
/* This app only allows one resolve at a time, but CFNetServices places no restrictions on the number of
simultaneous resolves. Because most resolves will happen instantaneously after calling CFNetServiceResolve,
if we end up canceling a previous resolve, it's probably because the previous service became unreachable. */
PrintConsoleString("Resolve canceled\n");
MyCancelResolve();
}
gServiceBeingResolved = CFNetServiceCreate(kCFAllocatorDefault, domain, type, name, 0);
assert(gServiceBeingResolved != NULL);
CFNetServiceSetClient(gServiceBeingResolved, MyResolveCallback, &clientContext);
CFNetServiceScheduleWithRunLoop(gServiceBeingResolved, CFRunLoopGetCurrent(), kCFRunLoopCommonModes);
if (CFNetServiceResolve(gServiceBeingResolved, &error) == false)
{
// Something went wrong so lets clean up.
CFNetServiceUnscheduleFromRunLoop(gServiceBeingResolved, CFRunLoopGetCurrent(), kCFRunLoopCommonModes);
CFNetServiceSetClient(gServiceBeingResolved, NULL, NULL);
CFRelease(gServiceBeingResolved);
gServiceBeingResolved = NULL;
PrintConsoleString("CFNetServiceResolve returned (domain = %d, error = %ld)\n", error.domain, error.error);
}
return;
}
static void ContinueResolveProcess(void)
{
CFStringRef serviceNameRef;
if (gDone == true)
{
gResolveProcessActive = FALSE;
}
else
{
if (CFArrayGetCount(gServiceArrayRef) > gNextNameIndexToResolve)
{
serviceNameRef = (CFStringRef)CFArrayGetValueAtIndex(gServiceArrayRef, gNextNameIndexToResolve++);
MyResolveService(serviceNameRef, kServiceType, kMyDefaultDomain);
}
else
gResolveProcessActive = FALSE;
}
}
#define U32Swap(value) ((value)=EndianU32_BtoN(value))
#define U16Swap(value) ((value)=EndianU16_BtoN(value))
static void
MyResolveCallback(CFNetServiceRef service, CFStreamError* error, void* info)
{
CFArrayRef addresses;
CFStringRef addressString;
CFStringRef serviceNameRef;
sockaddr *socketAddress;
sockaddr_in *socketAddress_in;
UInt32 inaddr;
UInt16 port;
char servicename[64];
CFIndex namelen = sizeof(servicename);
UInt32 index;
/* In situations where the service you're resolving is advertising on multiple interfaces,
like Ethernet and AirPort, your Resolve callback may be called twice, once for each IP address.
Chances are that both of these IP addresses represent the same service running on the same machine,
so we cancel the Resolve after getting the first callback because you only need one address to
connect to the service. However, it would also be possible that two different machines are
advertising the same service name, with one being on Ethernet and one on AirPort. In that
situation, one of the machines will be unreachable, or more likly, everytime we call Resolve,
we may connect to a different machine. The odds of this happening are extremely small. */
assert(service != NULL);
addresses = CFNetServiceGetAddressing(service);
assert(addresses != NULL);
serviceNameRef = CFNetServiceGetName(service);
assert(serviceNameRef != NULL);
if (CFStringGetCString(serviceNameRef, servicename, namelen, kCFStringEncodingMacRoman))
{
servicename[namelen]='\0';
for(int i=0;i<gNumLocalGames;i++)
if(_stricmp(servicename,gLocalGames[i].name)==0)
{
// Iterate through addresses until we find an IPv4 address
for (index = 0; index < CFArrayGetCount(addresses); index++)
{
socketAddress = (struct sockaddr *)CFDataGetBytePtr((__CFData*)CFArrayGetValueAtIndex(addresses, index));
if (socketAddress && socketAddress->sa_family == AF_INET)
break;
}
if (socketAddress)
{
switch(socketAddress->sa_family)
{
case AF_INET:
CFStringGetCString(serviceNameRef, servicename, namelen, kCFStringEncodingMacRoman);
socketAddress_in = (struct sockaddr_in *)socketAddress;
inaddr = *((UInt32*)(&socketAddress_in->sin_addr));
port = *((UInt16*)(&socketAddress_in->sin_port));
U32Swap(inaddr);
U16Swap(port);
sprintf(gLocalGames[i].host, "%d.%d.%d.%d:%d",inaddr>>24,(inaddr>>16)&0xFF,(inaddr>>8)&0xFF,inaddr&0xFF,port);
gLocalGames[i].loaded=true;
//PrintConsoleString("Got Host Info: %s",gLocalGames[i].host);
// Since we got an IPv4 address, this would be a good place to cancel the resolve.
gLocalGameListUpdate++;
MyCancelResolve();
return;
case AF_INET6:
PrintConsoleString("Resolver called with IPV6 address\n");
/* If we got here, it probably means that the "addresses" array only had one sockaddr in it and it
was IPv6. We don't cancel the resolve just yet since this sample expects to print out the IPV4 address.
Just continue and the resolver will call back with the IPV4 address instance. */
break;
}
}
}
}
// see if there are more entities to resolve
ContinueResolveProcess();
return;
}
static Boolean
MyRegisterService(CFStringRef name, CFStringRef type, CFStringRef domain, UInt32 port, CFStringRef txtRecord)
{
CFNetServiceClientContext context = {0, NULL, NULL, NULL, NULL };
CFStreamError error;
assert(name != NULL);
assert(type != NULL);
assert(domain != NULL);
/* As an alternative to specifying a "name" to register, you could use an empty string like
CFSTR(""), and that would cause the system to automatically substitute the "Computer Name"
from the Sharing preference panel. Another benefit of using an empty string is that the system
will automatically handle name collisions for us by appending a digit to the end of the name,
thus our Callback would never be called in the event of a name collision.
Beginning with Mac OS X 10.2.4, using an empty string will even handle situations
where the user changes the "Computer Name" in the preference panel. */
gRegisteredService = CFNetServiceCreate(kCFAllocatorDefault, domain, type, name, port);
assert(gRegisteredService != NULL);
// register the service asynchronously.
CFNetServiceSetClient(gRegisteredService, MyRegisterCallback, &context);
CFNetServiceScheduleWithRunLoop(gRegisteredService, CFRunLoopGetCurrent(), kCFRunLoopCommonModes);
gTaskRegistered = CFNetServiceRegister(gRegisteredService, &error);
if (gTaskRegistered == FALSE)
{
// a problem happened with calling CFNetServiceRegister so unschedule the service from
// the runloop
CFNetServiceUnscheduleFromRunLoop(gRegisteredService, CFRunLoopGetCurrent(), kCFRunLoopCommonModes);
CFNetServiceSetClient(gRegisteredService, NULL, NULL);
CFRelease(gRegisteredService);
gRegisteredService = NULL;
}
return gTaskRegistered;
}
static void StartResolveProcess(void)
{
CFStringRef serviceNameRef;
// Make sure that this process can't be entered multiple times
if (gResolveProcessActive == FALSE)
{
gResolveProcessActive = TRUE;
gNextNameIndexToResolve = 0;
if (CFArrayGetCount(gServiceArrayRef) > gNextNameIndexToResolve)
{
serviceNameRef = (CFStringRef)CFArrayGetValueAtIndex(gServiceArrayRef, gNextNameIndexToResolve++);
MyResolveService(serviceNameRef, kServiceType, kMyDefaultDomain);
}
else
gResolveProcessActive = FALSE;
}
}
static void
MyAddService(CFNetServiceRef service, CFOptionFlags flags)
{
int referenceCount;
CFStringRef serviceNameRef;
char servicename[256];
CFIndex namelen = sizeof(servicename);
Boolean addName = FALSE;
/* You should do reference counting of each service because if the computer has two network
interfaces set up, like Ethernet and AirPort, you may get notified about the same service
twice, once from each interface. You probably don't want both items to be shown to the user. */
assert(service != NULL);
serviceNameRef = CFNetServiceGetName(service);
assert(serviceNameRef != NULL);
assert(gServiceDictionary != NULL);
if (CFStringGetCString(serviceNameRef, servicename, namelen, kCFStringEncodingMacRoman))
{
servicename[namelen]='\0';
for(int i=0;i<gNumLocalGames;i++)
if(_stricmp(servicename,gLocalGames[i].name)==0)
return;
strcpy(gLocalGames[gNumLocalGames].name,servicename);
gLocalGames[gNumLocalGames].numPlayers=-1;
gLocalGames[gNumLocalGames].loaded=false;
gLocalGames[gNumLocalGames].mapRef=kFileErr;
MyResolveService(serviceNameRef,kServiceType,kMyDefaultDomain);
gNumLocalGames++;
gLocalGameListUpdate++;
}
}
static void
MyRemoveService(CFNetServiceRef service, CFOptionFlags flags)
{
int referenceCount;
CFRange range;
CFIndex index;
CFStringRef serviceNameRef;
char servicename[256];
CFIndex namelen = sizeof servicename;
CFIndex result;
assert(service != NULL);
serviceNameRef = CFNetServiceGetName(service);
assert(serviceNameRef != NULL);
if (CFStringGetCString(serviceNameRef, servicename, namelen, kCFStringEncodingMacRoman))
{
servicename[namelen]='\0';
for(int i=0;i<gNumLocalGames;i++)
if(_stricmp(servicename,gLocalGames[i].name)==0)
{
MemoryMove(gLocalGames+i,gLocalGames+i+1,(gNumLocalGames-i-1)*sizeof(tGameListEntry));
gNumLocalGames--;
gLocalGameListUpdate++;
return;
}
}
}
static void
MyBrowserCallback(CFNetServiceBrowserRef browser, CFOptionFlags flags, CFTypeRef domainOrService, CFStreamError* error, void* info)
{
if (flags & kCFNetServiceFlagIsDomain)
return;
/* You should do as little work as possible while in the Browser Callback because the mDNSResponder will timeout
if you don't extract items out of the Mach message queue fast enough. If a timeout occurs, you will see
a message in the Console that looks like... "mDNSResponder[260]: 10531: Browser(_afpovertcp._tcp.local.)
stopped accepting Mach messages (browse)" and from that point, your browsing will no longer return any results. */
if (flags & kCFNetServiceFlagRemove)
{
MyRemoveService((CFNetServiceRef)domainOrService, flags);
}
else
{
MyAddService((CFNetServiceRef)domainOrService, flags);
}
return;
}
static OSStatus
MyStartBrowsingForServices(CFStringRef type, CFStringRef domain)
{
CFNetServiceClientContext clientContext = { 0, NULL, NULL, NULL, NULL };
CFStreamError error;
Boolean result;
OSStatus err = noErr;
assert(type != NULL);
assert(domain != NULL);
gServiceBrowserRef = CFNetServiceBrowserCreate(kCFAllocatorDefault, MyBrowserCallback, &clientContext);
if (gServiceBrowserRef == NULL)
{
err = memFullErr;
}
else
{
CFNetServiceBrowserScheduleWithRunLoop(gServiceBrowserRef, CFRunLoopGetCurrent(), kCFRunLoopCommonModes);
result = CFNetServiceBrowserSearchForServices(gServiceBrowserRef, domain, type, &error);
if (result == FALSE)
{
// Something went wrong so lets clean up.
CFNetServiceBrowserUnscheduleFromRunLoop(gServiceBrowserRef, CFRunLoopGetCurrent(), kCFRunLoopCommonModes);
CFRelease(gServiceBrowserRef);
gServiceBrowserRef = NULL;
PrintConsoleString("CFNetServiceBrowserSearchForServices returned (domain = %d, error = %ld)\n", error.domain, error.error);
err = error.error;
}
else
// indicate that the browser task is active
gBrowserTaskActive = TRUE;
}
return err;
}
static void
MyStopBrowsingForServices()
{
CFStreamError streamerror;
assert(gServiceBrowserRef != NULL);
CFNetServiceBrowserStopSearch(gServiceBrowserRef, &streamerror);
CFNetServiceBrowserUnscheduleFromRunLoop(gServiceBrowserRef, CFRunLoopGetCurrent(), kCFRunLoopCommonModes);
CFNetServiceBrowserInvalidate(gServiceBrowserRef);
CFRelease(gServiceBrowserRef);
gServiceBrowserRef = NULL;
assert(gServiceDictionary != NULL);
CFDictionaryRemoveAllValues(gServiceDictionary);
return;
}
void LocalTrackerStartAdvertising(char *gameName)
{
MyRegisterService(CFStringCreateWithCString(NULL,gameName,kCFStringEncodingMacRoman),kServiceType,kMyDefaultDomain,33333,NULL);
}
void LocalTrackerStopAdvertising()
{
MyCancelRegistration();
}
void LocalTrackerSearchGamesInit()
{
//HandleError()
MyStartBrowsingForServices(kServiceType,kMyDefaultDomain);
}
void LocalTrackerSearchGamesDone()
{
MyStopBrowsingForServices();
}