The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
#ifdef __cplusplus
extern "C" {
#endif
#include "EXTERN.h"
#include "perl.h"
#include "XSUB.h"
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <ws2bth.h>
#include <BluetoothAPIs.h>
#ifdef __cplusplus
}
#endif

typedef PerlIO * InOutStream;

#define UUID_BASE_128 "00000000-0000-1000-8000-00805F9B34FB"

// Code from PyBlueZ
static void
ba2str( BTH_ADDR ba, char *addr )
{
    int i;
    unsigned char bytes[6];
    for( i=0; i<6; i++ ) {
        bytes[5-i] = (unsigned char) ((ba >> (i*8)) & 0xff);
    }
    sprintf(addr, "%02X:%02X:%02X:%02X:%02X:%02X", 
            bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5] );
}


// Code from PyBlueZ
static int 
str2uuid( const char *uuid_str, GUID *uuid ) 
{
    // Parse uuid128 standard format: 12345678-9012-3456-7890-123456789012
    int i;
    char buf[20] = { 0 };

    strncpy(buf, uuid_str, 8);
    uuid->Data1 = strtoul( buf, NULL, 16 );
    memset(buf, 0, sizeof(buf));

    strncpy(buf, uuid_str+9, 4);
    uuid->Data2 = (unsigned short) strtoul( buf, NULL, 16 );
    memset(buf, 0, sizeof(buf));
    
    strncpy(buf, uuid_str+14, 4);
    uuid->Data3 = (unsigned short) strtoul( buf, NULL, 16 );
    memset(buf, 0, sizeof(buf));

    strncpy(buf, uuid_str+19, 4);
    strncpy(buf+4, uuid_str+24, 12);
    for( i=0; i<8; i++ ) {
        char buf2[3] = { buf[2*i], buf[2*i+1], 0 };
        uuid->Data4[i] = (unsigned char)strtoul( buf2, NULL, 16 );
    }

    return 0;
}


static int
build_uuid(GUID *uuid, char *service)
{
char service_buf[37];


	if(service == NULL || strcmp(service, "0") == 0 || strlen(service) == 0) {
		// Use public browse group
		strcpy(service_buf, UUID_BASE_128);
		service_buf[4] = '1';
		service_buf[5] = '0';
		service_buf[6] = '0';
		service_buf[7] = '2';
		str2uuid(service_buf, uuid);
	}

	// 128 bit
	else if(strlen(service) == 36) {
		if(service[8] != '-' || service[13] != '-' ||
		   service[18] != '-' || service[23] != '-' ) {
			return(-1);
		}
		str2uuid(service, uuid);
	}

	// they left 0x on?
	else if(strlen(service) == 6){
		if(service[0] == '0' && service[1] == 'x' || service[1] == 'X') {
			strcpy(service_buf, UUID_BASE_128);
			service_buf[4] = service[2];
			service_buf[5] = service[3];
			service_buf[6] = service[4];
			service_buf[7] = service[5];
			str2uuid(service_buf, uuid);
		}

		else {
			return(-1);
		}
	}

	// 16 bit
	else if(strlen(service) == 4) {
		strcpy(service_buf, UUID_BASE_128);
		service_buf[4] = service[0];
		service_buf[5] = service[1];
		service_buf[6] = service[2];
		service_buf[7] = service[3];
		str2uuid(service_buf, uuid);
	}

	else {
		return(-1);
	}

	return(0);
}


MODULE = Net::Bluetooth	PACKAGE = Net::Bluetooth

void
_init()
	CODE:
	WORD wVersionRequested;
    WSADATA wsaData;

    wVersionRequested = MAKEWORD(2, 0);
    if(WSAStartup(wVersionRequested, &wsaData) != 0) {
		croak("Could not init Winsock!");
    }


void
_deinit()
	CODE:
	WSACleanup();


void
_close(sock)
	int sock
	PPCODE:
	closesocket(sock);


unsigned int
_use_service_handle()
	CODE:
	// We dont use the service handle on Windows
	RETVAL = 0;

	OUTPUT:
	RETVAL 


void
get_remote_devices()
	PPCODE:
	int done = 0;
	int iRet;
	int error;
	DWORD flags = 0;
	DWORD qs_len;
	HANDLE hLookup;
	char addr_buf[64];
	WSAQUERYSET *qs;
	BTH_ADDR result;
	HV *return_hash = NULL;


	qs_len = sizeof(WSAQUERYSET);
	qs = (WSAQUERYSET*) malloc(qs_len);
	ZeroMemory(qs, sizeof(WSAQUERYSET));
	qs->dwSize = sizeof(WSAQUERYSET);
	qs->dwNameSpace = NS_BTH;
	qs->lpcsaBuffer = NULL;
	

	flags |= LUP_FLUSHCACHE | LUP_RETURN_NAME | LUP_RETURN_ADDR | LUP_CONTAINERS;

	iRet = WSALookupServiceBegin(qs, flags, &hLookup);

	// return undef if error and empty hash if no devices found?
	if(iRet == SOCKET_ERROR) {
		error = WSAGetLastError();
		if(error == WSASERVICE_NOT_FOUND) {
			// No device
			WSALookupServiceEnd(hLookup);
			free(qs); 
		} 
		
		else {
			free(qs);
		}
	}

	else {
		EXTEND(sp, 1);

		while(! done) {

			if(WSALookupServiceNext(hLookup, flags, &qs_len, qs) == NO_ERROR) {
				result = ((SOCKADDR_BTH*)qs->lpcsaBuffer->RemoteAddr.lpSockaddr)->btAddr;
				ba2str(result, addr_buf);
				if(return_hash == NULL) 
					return_hash = newHV();

				if(qs->lpszServiceInstanceName == NULL || strlen(qs->lpszServiceInstanceName) == 0) {
					hv_store(return_hash, addr_buf, strlen(addr_buf), newSVpv("[unknown]", 0), 0);
				}

				else {
					hv_store(return_hash, addr_buf, strlen(addr_buf), newSVpv(qs->lpszServiceInstanceName, 0), 0);
				}
			} 
			
			else {
				error = WSAGetLastError();
			
				if(error == WSAEFAULT) {
					free(qs);
					qs = (WSAQUERYSET*) malloc(qs_len);
					ZeroMemory(qs, qs_len);
				} 

				else if(error == WSA_E_NO_MORE) {
					done = 1;
				}

				else {
					done = 1;
				}
			}
		}

		// only return if has values
		if(return_hash != NULL)
			PUSHs(sv_2mortal(newRV_inc((SV*) return_hash)));

		WSALookupServiceEnd(hLookup);
	}



void
sdp_search(addr, service, name)
	char *addr
	char *service
	char *name
	PPCODE:
	char *addrstr = NULL;
	char *uuidstr = "0";
	char localAddressBuf[32];
	DWORD qs_len;
	WSAQUERYSET *qs;
	DWORD flags;
	HANDLE h;
	GUID uuid;
	SOCKADDR_BTH sa;
	int sa_len = sizeof(sa);
	int local_fd = 0;
	int done = 0;
	int proto;
	int port;
	int error;
	HV *return_hash = NULL;

	EXTEND(sp, 1);
	// this prolly doesnt need to be malloced
	// inquiry data structure
	qs_len = sizeof(WSAQUERYSET);
	qs = (WSAQUERYSET*) malloc(qs_len);

	flags = LUP_FLUSHCACHE | LUP_RETURN_ALL;

	ZeroMemory(qs, qs_len);
	qs->dwSize = sizeof(WSAQUERYSET);
	qs->dwNameSpace = NS_BTH;
	// ignored for queries?
	qs->dwNumberOfCsAddrs = 0;

	if(_stricmp(addr, "localhost") == 0 || _stricmp(addr, "local") == 0 ) {
			memset(&sa, 0, sizeof(sa));
			local_fd = socket(AF_BTH, SOCK_STREAM, BTHPROTO_RFCOMM);
			if(local_fd < 1) {
				free(qs);
				XSRETURN_UNDEF;
			}

			sa.addressFamily = AF_BTH;
			sa.port = BT_PORT_ANY;
			if(bind(local_fd,(LPSOCKADDR)&sa,sa_len) != NO_ERROR) {
				free(qs);
				close(local_fd);
				XSRETURN_UNDEF;
			}
                                                                                                                         
			if(getsockname(local_fd, (LPSOCKADDR)&sa, &sa_len) != NO_ERROR) {
				free(qs);
				close(local_fd);
				XSRETURN_UNDEF;
			}

			ba2str(sa.btAddr, localAddressBuf);
			qs->lpszContext = (LPSTR) localAddressBuf;
			close(local_fd);
	}

	else {
		qs->lpszContext = (LPSTR) addr;
	}


	memset(&uuid, 0, sizeof(uuid));
	if(build_uuid(&uuid, service) != 0) {
		free(qs);
		XSRETURN_UNDEF;
	}

	qs->lpServiceClassId = &uuid;

	if(WSALookupServiceBegin(qs, flags, &h) == SOCKET_ERROR) {
		free(qs);
		XSRETURN_UNDEF;
	}

	else {
		// iterate through the inquiry results
		while(! done) {
			if(WSALookupServiceNext(h, flags, &qs_len, qs) == NO_ERROR) {
				return_hash = newHV();

				// If name is valid, then compare names.
				if(name && strlen(name) > 0) {
					if(qs->lpszServiceInstanceName && strlen(qs->lpszServiceInstanceName) > 0) {
						if(_stricmp(name, qs->lpszServiceInstanceName) == 0) {
							hv_store(return_hash, "SERVICE_NAME", strlen("SERVICE_NAME"),
			                         newSVpv(qs->lpszServiceInstanceName, 0), 0);
						}

						else {
							continue;
						}
					}

					else {
						continue;
					}
				}

				else if(qs->lpszServiceInstanceName && strlen(qs->lpszServiceInstanceName) > 0) {
					hv_store(return_hash, "SERVICE_NAME", strlen("SERVICE_NAME"), newSVpv(qs->lpszServiceInstanceName, 0), 0);
				}

				if(qs->lpszComment && strlen(qs->lpszComment) > 0) {
					hv_store(return_hash, "SERVICE_DESC", strlen("SERVICE_DESC"), newSVpv(qs->lpszComment, 0), 0);
				}

				// set protocol and port
				proto = qs->lpcsaBuffer->iProtocol;
				port = ((SOCKADDR_BTH*)qs->lpcsaBuffer->RemoteAddr.lpSockaddr)->port;

				if(proto == BTHPROTO_RFCOMM) {
					if(port) {
						hv_store(return_hash, "RFCOMM", strlen("RFCOMM"), newSViv(port), 0);
					}
				} 
				
				else if(proto == BTHPROTO_L2CAP) {
					if(port) {
						hv_store(return_hash, "L2CAP", strlen("L2CAP"), newSViv(port), 0);
					}
				} 
				
				else {
					if(port) {
						hv_store(return_hash, "UNKNOWN", strlen("UNKNOWN"), newSViv(port), 0);
					}
				}


				// qs->lpBlob->pBlobData and qs->lpBlob->cbSize give access to the raw service records
			
				PUSHs(sv_2mortal(newRV_inc((SV*) return_hash)));
			} 
				
			else {
				error = WSAGetLastError();
						
				if(error == WSAEFAULT) {;
					free(qs);
					qs = (WSAQUERYSET*) malloc(qs_len);
				} 
			
				else if(error == WSA_E_NO_MORE) {
					done = 1;
				} 
			
				else {
					done = 1; 
				}
			}
		}
	}

	WSALookupServiceEnd(h);
	free(qs);

 
void
_register_service(serverfd, proto, port, service_id, name, desc, advertise)
	int serverfd
	char *proto
	int port
	char *service_id
	char *name
	char *desc
	int advertise
	PPCODE:
	WSAQUERYSET qs;
	WSAESETSERVICEOP op;
	SOCKADDR_BTH sa;
	int sa_len = sizeof(sa);
	char *service_name = NULL;
	char *service_desc = NULL;
	char *service_class_id_str = NULL;
	CSADDR_INFO sockInfo;
	GUID uuid;


	EXTEND(sp, 1);

	memset(&qs, 0, sizeof(qs));
	memset(&sa, 0, sizeof(sa));
	memset(&sockInfo, 0, sizeof(sockInfo));
	memset(&uuid, 0, sizeof(uuid));


	op = advertise ? RNRSERVICE_REGISTER : RNRSERVICE_DELETE;

	if(getsockname(serverfd, (SOCKADDR*) &sa, &sa_len) == SOCKET_ERROR) {
		PUSHs(sv_2mortal(newSViv(1)));
		XSRETURN_IV(0);
	}

	if(build_uuid(&uuid, service_id) != 0) {
		XSRETURN_IV(0);
	}

	sockInfo.iProtocol = BTHPROTO_RFCOMM;
	sockInfo.iSocketType = SOCK_STREAM;
	sockInfo.LocalAddr.lpSockaddr = (LPSOCKADDR) &sa;
	sockInfo.LocalAddr.iSockaddrLength = sizeof(sa);
	sockInfo.RemoteAddr.lpSockaddr = (LPSOCKADDR) &sa;
	sockInfo.RemoteAddr.iSockaddrLength = sizeof(sa);

	qs.dwSize = sizeof(qs);
	qs.dwNameSpace = NS_BTH;
	qs.lpcsaBuffer = &sockInfo;
	qs.lpszServiceInstanceName = name;
	qs.lpszComment = name;
	qs.lpServiceClassId = (LPGUID) &uuid;
	qs.dwNumberOfCsAddrs = 1;

	if(WSASetService(&qs, op, 0) ==  SOCKET_ERROR) {
		PUSHs(sv_2mortal(newSViv(0)));
	}

	else {
		PUSHs(sv_2mortal(newSViv(1)));
	}



int
_stop_service(sdp_addr)
	unsigned int sdp_addr
	CODE:
	// don't do anything here since we don't use handles
	RETVAL = 0;

	OUTPUT:
	RETVAL 



int
_socket(proto)
	char *proto
	CODE:

	if(_stricmp(proto, "RFCOMM") == 0)  {
		RETVAL = socket(AF_BTH, SOCK_STREAM, BTHPROTO_RFCOMM);
	}

	else if(_stricmp(proto, "L2CAP") == 0) {
		RETVAL = socket(AF_BTH, SOCK_STREAM, BTHPROTO_L2CAP);
	}

	else {
		RETVAL = -1;
	}

	OUTPUT:
	RETVAL 


int
_connect(fd, addr, port, proto)
	int fd
	char *addr
	int port
	char *proto
	CODE:
	SOCKADDR_BTH sa;
	int sa_len = sizeof(sa);
	memset(&sa, 0, sizeof(sa));


	if(WSAStringToAddress(addr, AF_BTH, NULL, (LPSOCKADDR)&sa, &sa_len) == SOCKET_ERROR) {
		RETVAL = -1;
	}

	else {
		sa.addressFamily = AF_BTH;
		sa.port = port;
		RETVAL = connect(fd, (LPSOCKADDR)&sa, sizeof(sa));
	}


	OUTPUT:
	RETVAL 


int
_bind(fd, port, proto)
	int fd
	int port
	char *proto
	CODE:
	int status;
	SOCKADDR_BTH sa;
	int sa_len;
	
	sa_len = sizeof(sa);

	memset(&sa, 0, sa_len);

	sa.btAddr = 0;
	sa.addressFamily = AF_BTH;
	sa.port = port;
	status = bind(fd, (LPSOCKADDR)&sa, sa_len);
	if(status == NO_ERROR) {
		RETVAL = 0;
	}

	else {
		RETVAL = -1;
	}


	OUTPUT:
	RETVAL 


int
_listen(fd, backlog)
	int fd
	int backlog
	CODE:
	int status;
	status = listen(fd, backlog);
	if(status == NO_ERROR) {
		RETVAL = 0;
	}

	else {
		RETVAL = -1;
	}

	OUTPUT:
	RETVAL 


void
_accept(fd, proto)
	int fd
	char *proto
	PPCODE:
	int addr_len;
	int res;
	char addr[19];
	SOCKADDR_BTH rcaddr;


	EXTEND(sp, 2);
	addr_len = sizeof(rcaddr);
	res = accept(fd, (LPSOCKADDR)&rcaddr, &addr_len);
	if(res != INVALID_SOCKET) {
		PUSHs(sv_2mortal(newSViv(res)));
		ba2str(rcaddr.btAddr, addr);
		PUSHs(sv_2mortal(newSVpv(addr, 0)));
	}

	else {
		PUSHs(sv_2mortal(newSViv(-1)));
	}


void
_getpeername(fd, proto)
	int fd
	char *proto
	PPCODE:
	EXTEND(sp, 2);
	// not implemented for Windows yet
	PUSHs(sv_2mortal(newSVuv(0)));
	PUSHs(sv_2mortal(newSVuv(0)));