// 32feet.NET - Personal Area Networking for .NET // // InTheHand.Net.Bluetooth.Msft.BluetoothClient // // Copyright (c) 2003-2008 In The Hand Ltd, All rights reserved. // This source code is licensed under the MIT License //#define WIN32_READ_BTH_DEVICE_INFO using System; using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Net; using System.Net.Sockets; using System.Runtime.InteropServices; using Microsoft.Win32; using InTheHand.Net.Bluetooth.Factory; using InTheHand.Net.Sockets; using InTheHand.Runtime.InteropServices; using AsyncResult_BeginDiscoverDevices = InTheHand.Net.AsyncResult< InTheHand.Net.Bluetooth.Factory.IBluetoothDeviceInfo[], InTheHand.Net.Bluetooth.Msft.SocketBluetoothClient.DiscoDevsParamsWithLive>; using List_IBluetoothDeviceInfo = System.Collections.Generic.List; using InTheHand.SystemCore; namespace InTheHand.Net.Bluetooth.Msft { class SocketBluetoothClient : IBluetoothClient { readonly BluetoothFactory _fcty; private bool cleanedUp = false; private ISocketOptionHelper m_optionHelper; #if WinXP // If SetPin(String) is called before connect we need to know the remote // address to start the BluetoothWin32Authenticator for, so store this // so we can start the authenticator at connect-time. string m_pinForConnect; #endif #region Constructor #if NETCF static SocketBluetoothClient() { InTheHand.Net.PlatformVerification.ThrowException(); } #endif internal SocketBluetoothClient(BluetoothFactory fcty) { Debug.Assert(fcty != null, "ArgNull"); _fcty = fcty; try { this.Client = CreateSocket(); } catch (SocketException se) { throw new PlatformNotSupportedException("32feet.NET does not support the Bluetooth stack on this device.", se); } m_optionHelper = CreateSocketOptionHelper(this.Client); } internal SocketBluetoothClient(BluetoothFactory fcty, BluetoothEndPoint localEP) : this(fcty) { if (localEP == null) { throw new ArgumentNullException("localEP"); } //bind to specific local endpoint var bindEP = PrepareBindEndPoint(localEP); this.Client.Bind(bindEP); } internal SocketBluetoothClient(BluetoothFactory fcty, Socket acceptedSocket) { Debug.Assert(fcty != null, "ArgNull"); _fcty = fcty; this.Client = acceptedSocket; active = true; m_optionHelper = CreateSocketOptionHelper(this.Client); } protected virtual Socket CreateSocket() { return new Socket(BluetoothAddressFamily, SocketType.Stream, BluetoothProtocolType.RFComm); } protected virtual AddressFamily BluetoothAddressFamily { get { return AddressFamily32.Bluetooth; } } protected virtual BluetoothEndPoint PrepareConnectEndPoint(BluetoothEndPoint serverEP) { return serverEP; } protected virtual BluetoothEndPoint PrepareBindEndPoint(BluetoothEndPoint serverEP) { return serverEP; } protected virtual ISocketOptionHelper CreateSocketOptionHelper(Socket socket) { return new MsftSocketOptionHelper(socket); } #endregion #region InquiryAccessCode private int iac = BluetoothAddress.Giac; //0x9E8B33; #if NETCF /// /// /// public int InquiryAccessCode { get { return iac; } set { iac = value; } } #else public int InquiryAccessCode { get { throw new NotSupportedException(); } set { throw new NotSupportedException(); } } #endif #endregion #region Query Length //length of time for query private TimeSpan inquiryLength = new TimeSpan(0, 0, 10); /// /// Amount of time allowed to perform the query. /// /// On Windows CE the actual value used is expressed in units of 1.28 seconds, so will be the nearest match for the value supplied. /// The default value is 10 seconds. The maximum is 60 seconds. public TimeSpan InquiryLength { get { return inquiryLength; } set { if ((value.TotalSeconds > 0) && (value.TotalSeconds <= 60)) { inquiryLength = value; } else { throw new ArgumentOutOfRangeException("value", "QueryLength must be a positive timespan between 0 and 60 seconds."); } } } #endregion #region Discover Devices /// /// Discovers accessible Bluetooth devices and returns their names and addresses. /// /// The maximum number of devices to get information about. /// True to return previously authenticated/paired devices. /// True to return remembered devices. /// True to return previously unknown devices. /// True to return only discoverable devices /// (where both in range and in discoverable mode). /// When all other flags are ignored. /// Note: Does NOT work on Win32 with the Microsoft stack. /// /// An array of BluetoothDeviceInfo objects describing the devices discovered. /// - /// /// The flag will discover only /// the devices that are in range and are in discoverable mode. This works /// only on WM/CE with the Microsoft stack, or on any platform with the /// Widcomm stack. /// /// /// It does not work on desktop Windows with the Microsoft /// stack, where the in range and remembered devices are returned already /// merged! There simple all devices will be returned. Even the /// BluetoothDeviceInfo.LastSeen /// property is of no use there: on XP and Vista at least the value provided /// is always simply the current time. /// /// IBluetoothDeviceInfo[] IBluetoothClient.DiscoverDevices(int maxDevices, bool authenticated, bool remembered, bool unknown, bool discoverableOnly) { return DiscoverDevices(maxDevices, authenticated, remembered, unknown, discoverableOnly, null, null); } public virtual IBluetoothDeviceInfo[] DiscoverDevices(int maxDevices, bool authenticated, bool remembered, bool unknown, bool discoverableOnly, InTheHand.Net.Sockets.BluetoothClient.LiveDiscoveryCallback liveDiscoHandler, object liveDiscoState) { #if !NETCF // In the MSFT+Win32 API there's no simple way to get only the // in-range-discoverable devices, and thus it can't provide // 'discoverableOnly' nor live discovery. So, we register for the // native event whilst we run a normal discovery. We use the // native event to raise our live event and to gather the devices // to be included in the discoverableOnly result. // Note the event seems NOT be raised on WinXP in this case. // The event (on Win7 at least) raises lots of apparently duplicate // events, so we need to do some filtering for both results. var realLiveDiscoHandler = liveDiscoHandler; //if (realLiveDiscoHandler != null) Debug.Assert(discoverableOnly || unknown, "You want live events from 'remembered'?!?"); bool keepDiscoOnly = false; if (discoverableOnly) { keepDiscoOnly = true; unknown = authenticated = remembered = true; discoverableOnly = false; } BluetoothWin32Events events; EventHandler radioInRangeHandler; List seenDevices; if (keepDiscoOnly || realLiveDiscoHandler != null) { events = BluetoothWin32Events.GetInstance(); seenDevices = new List(); radioInRangeHandler = delegate(object sender, BluetoothWin32RadioInRangeEventArgs e) { var newdevice = e.DeviceWindows; Debug.WriteLine("Radio in range: " + newdevice.DeviceAddress); bool unique = BluetoothDeviceInfo.AddUniqueDevice(seenDevices, newdevice); if (unique && realLiveDiscoHandler != null) { Debug.WriteLine("Live IS unique."); realLiveDiscoHandler(newdevice, liveDiscoState); } else { //COVERAGE Debug.WriteLine("Live is NOT unique (" + unique + "," + realLiveDiscoHandler != null + ")."); } }; events.InRange += radioInRangeHandler; } else { events = null; radioInRangeHandler = null; seenDevices = null; } // Don't use the WM live-disco functionality. liveDiscoHandler = null; #endif IBluetoothDeviceInfo[] result; try { result = DoDiscoverDevices(maxDevices, authenticated, remembered, unknown, discoverableOnly, liveDiscoHandler, liveDiscoState); } finally { #if !NETCF if (events != null && radioInRangeHandler != null) { events.InRange -= radioInRangeHandler; } #endif } #if !NETCF if (keepDiscoOnly) { Debug.Assert(seenDevices != null, "Should have created list 'seenDevices' above!"); Debug.WriteLine("Disco result: " + result.Length + ", liveDevice: " + seenDevices.Count); result = BluetoothDeviceInfo.Intersect(result, seenDevices); Debug.WriteLine("Result result: " + result.Length); } #endif return result; } IBluetoothDeviceInfo[] DoDiscoverDevices(int maxDevices, bool authenticated, bool remembered, bool unknown, bool discoverableOnly, InTheHand.Net.Sockets.BluetoothClient.LiveDiscoveryCallback liveDiscoHandler, object liveDiscoState) { WqsOffset.AssertCheckLayout(); CsaddrInfoOffsets.AssertCheckLayout(); // #if !NETCF Debug.Assert(liveDiscoHandler == null, "Don't use the NETCF live-disco feature on Win32!"); #endif #if WinXP const bool Win32DiscoverableOnlyIncludesAllDevices = false; #endif var prototype = new BluetoothEndPoint(BluetoothAddress.None, BluetoothService.Empty); int discoveredDevices = 0; List_IBluetoothDeviceInfo al = null; IntPtr handle = IntPtr.Zero; int lookupresult = 0; /* Windows XP SP3 Begin takes the full Inquiry time. 12:09:15.7968750: Begin 10.265s 12:09:26.0625000: Begin complete 12:09:26.0625000: Next 12:09:26.0625000: Next Complete ... 12:09:26.1718750: Next 12:09:26.1718750: Next Complete 12:09:26.1718750: End 12:09:26.1718750: End Complete */ /* WM 6 SP Begin is quick, Next blocks until a device is found. 13:46:47.1760000: Begin 13:46:47.2350000: Begin complete 13:46:47.2360000: Next 10.537s 13:46:57.7730000: Next Complete 13:46:57.8910000: Next 13:46:57.8940000: Next Complete 13:46:57.8950000: Next 13:46:57.8960000: Next Complete 13:46:57.8960000: End 13:46:57.8990000: End Complete */ System.Text.StringBuilder timings = null; #if DEBUG // timings = new System.Text.StringBuilder(); #endif Action markTime = delegate(string name) { if (timings != null) timings.AppendFormat(CultureInfo.InvariantCulture, "{1}: {0}\r\n", name, DateTime.UtcNow.TimeOfDay); }; #if WinXP if (discoverableOnly && !Win32DiscoverableOnlyIncludesAllDevices) { // No way to separate out the devices-in-range on Win32. :-( return new IBluetoothDeviceInfo[0]; } #endif #if NETCF DateTime discoTime = DateTime.UtcNow; if(unknown || discoverableOnly) { #endif al = new List_IBluetoothDeviceInfo(); byte[] buffer = new byte[1024]; BitConverter.GetBytes(WqsOffset.StructLength_60).CopyTo(buffer, WqsOffset.dwSize_0); BitConverter.GetBytes(WqsOffset.NsBth_16).CopyTo(buffer, WqsOffset.dwNameSpace_20); int bufferlen = buffer.Length; BTHNS_INQUIRYBLOB bib = new BTHNS_INQUIRYBLOB(); bib.LAP = iac;// 0x9E8B33; #if NETCF bib.length = Convert.ToByte(inquiryLength.TotalSeconds / 1.28); bib.num_responses = Convert.ToByte(maxDevices); #else bib.length = Convert.ToByte(inquiryLength.TotalSeconds); #endif GCHandle hBib = GCHandle.Alloc(bib, GCHandleType.Pinned); IntPtr pBib = hBib.AddrOfPinnedObject(); BLOB b = new BLOB(8, pBib); GCHandle hBlob = GCHandle.Alloc(b, GCHandleType.Pinned); Marshal32.WriteIntPtr(buffer, WqsOffset.lpBlob_56, hBlob.AddrOfPinnedObject()); //start looking for Bluetooth devices LookupFlags flags = LookupFlags.Containers; #if WinXP //ensure cache is cleared on XP when looking for new devices if (unknown || discoverableOnly) { flags |= LookupFlags.FlushCache; } #endif markTime("Begin"); lookupresult = NativeMethods.WSALookupServiceBegin(buffer, flags, out handle); markTime("Begin complete"); hBlob.Free(); hBib.Free(); // TODO ?Change "while(...maxDevices)" usage on WIN32? while (discoveredDevices < maxDevices && lookupresult != -1) { markTime("Next"); #if NETCF lookupresult = NativeMethods.WSALookupServiceNext(handle, LookupFlags.ReturnAddr | LookupFlags.ReturnBlob , ref bufferlen, buffer); #else LookupFlags flagsNext = LookupFlags.ReturnAddr; #if WIN32_READ_BTH_DEVICE_INFO flagsNext |= LookupFlags.ReturnBlob; #endif lookupresult = NativeMethods.WSALookupServiceNext(handle, flagsNext, ref bufferlen, buffer); #endif markTime("Next Complete"); if (lookupresult != -1) { //increment found count discoveredDevices++; //status #if WinXP BTHNS_RESULT status = (BTHNS_RESULT)BitConverter.ToInt32(buffer, WqsOffset.dwOutputFlags_52); bool devAuthd = ((status & BTHNS_RESULT.Authenticated) == BTHNS_RESULT.Authenticated); bool devRembd = ((status & BTHNS_RESULT.Remembered) == BTHNS_RESULT.Remembered); if (devAuthd && !devRembd) { System.Diagnostics.Debug.WriteLine("Win32 BT disco: Auth'd but NOT Remembered."); } bool devUnkwn = !devRembd && !devAuthd; bool include = (authenticated && devAuthd) || (remembered && devRembd) || (unknown && devUnkwn); Debug.Assert(!discoverableOnly, "Expected short circuit for Win32 unsupported discoverableOnly!"); if (include) #else if(true) #endif { #if NETCF IntPtr lpBlob = (IntPtr)BitConverter.ToInt32(buffer, 56); BLOB ib = (BLOB)Marshal.PtrToStructure(lpBlob, typeof(BLOB)); BthInquiryResult bir = (BthInquiryResult)Marshal.PtrToStructure(ib.pBlobData, typeof(BthInquiryResult)); #endif //struct CSADDR_INFO { // SOCKET_ADDRESS LocalAddr; // SOCKET_ADDRESS RemoteAddr; // INT iSocketType; // INT iProtocol; //} //struct SOCKET_ADDRESS { // LPSOCKADDR lpSockaddr; // INT iSockaddrLength; //} //pointer to outputbuffer IntPtr bufferptr = Marshal32.ReadIntPtr(buffer, WqsOffset.lpcsaBuffer_48); //remote socket address IntPtr sockaddrptr = Marshal32.ReadIntPtr(bufferptr, CsaddrInfoOffsets.OffsetRemoteAddr_lpSockaddr_8); //remote socket len int sockaddrlen = Marshal.ReadInt32(bufferptr, CsaddrInfoOffsets.OffsetRemoteAddr_iSockaddrLength_12); SocketAddress btsa = new SocketAddress(AddressFamily32.Bluetooth, sockaddrlen); for (int sockbyte = 0; sockbyte < sockaddrlen; sockbyte++) { btsa[sockbyte] = Marshal.ReadByte(sockaddrptr, sockbyte); } var bep = (BluetoothEndPoint)prototype.Create(btsa); //new deviceinfo IBluetoothDeviceInfo newdevice; #if NETCF newdevice = new WindowsBluetoothDeviceInfo(bep.Address, bir.cod); // Built-in to Win32 so only do on NETCF newdevice.SetDiscoveryTime(discoTime); #else newdevice = new WindowsBluetoothDeviceInfo(bep.Address); #if WIN32_READ_BTH_DEVICE_INFO ReadBlobBTH_DEVICE_INFO(buffer, newdevice); #endif #endif //add to discovered list al.Add(newdevice); if (liveDiscoHandler != null) { liveDiscoHandler(newdevice, liveDiscoState); } } } }//while #if NETCF } #endif //stop looking if (handle != IntPtr.Zero) { markTime("End"); lookupresult = NativeMethods.WSALookupServiceEnd(handle); markTime("End Complete"); } if (timings != null) { Debug.WriteLine(timings); #if !NETCF Console.WriteLine(timings); #endif } #if NETCF List_IBluetoothDeviceInfo known = WinCEReadKnownDevicesFromRegistry(); al = BluetoothClient.DiscoverDevicesMerge(authenticated, remembered, unknown, known, al, discoverableOnly, discoTime); #endif //return results if (al.Count == 0) { //special case for empty collection return new IBluetoothDeviceInfo[0] { }; } return (IBluetoothDeviceInfo[])al.ToArray( #if V1 typeof(IBluetoothDeviceInfo) #endif ); } #if WinXP private void ReadBlobBTH_DEVICE_INFO(byte[] buffer, IBluetoothDeviceInfo dev) { // XXXX - Testing only, at least delete the "Console.WriteLine" before use. - XXXX IntPtr pBlob = Marshal32.ReadIntPtr(buffer, WqsOffset.lpBlob_56); if (pBlob != IntPtr.Zero) { BLOB blob = (BLOB)Marshal.PtrToStructure(pBlob, typeof(BLOB)); if (blob.pBlobData != IntPtr.Zero) { BTH_DEVICE_INFO bdi = (BTH_DEVICE_INFO)Marshal.PtrToStructure(blob.pBlobData, typeof(BTH_DEVICE_INFO)); BluetoothDeviceInfoProperties flags = bdi.flags; Debug.WriteLine(String.Format(CultureInfo.InvariantCulture, "[BTH_DEVICE_INFO: {0:X12}, '{1}' = 0x{2:X4}]", bdi.address, flags, bdi.flags)); Trace.Assert((flags & ~BDIFMasks.AllOrig) == 0, "*Are* new flags there!"); } } } #endif #if NETCF static class DevicesValueNames { internal const string DeviceName = "name"; internal const string ClassOfDevice = "class"; } private static List_IBluetoothDeviceInfo WinCEReadKnownDevicesFromRegistry() { Func makeDev = (address, name, classOfDevice, authd) => new WindowsBluetoothDeviceInfo(address, name, classOfDevice, authd); var deviceListPath = "Software\\Microsoft\\Bluetooth\\Device"; return ReadKnownDevicesFromRegistry(deviceListPath, makeDev); } internal static List_IBluetoothDeviceInfo ReadKnownDevicesFromRegistry( string deviceListPath, Func makeDev) { List_IBluetoothDeviceInfo known = new List_IBluetoothDeviceInfo(); //open bluetooth device key RegistryKey devkey = Registry.LocalMachine.OpenSubKey(deviceListPath); //bool addFromRegistry = authenticated || remembered; if (devkey != null) { //enumerate the keys foreach (string devid in devkey.GetSubKeyNames()) { BluetoothAddress address; if (BluetoothAddress.TryParse(devid, out address)) { //get friendly name RegistryKey thisdevkey = devkey.OpenSubKey(devid); string name = thisdevkey.GetValue(DevicesValueNames.DeviceName, "").ToString(); uint classOfDevice = Convert.ToUInt32(thisdevkey.GetValue(DevicesValueNames.ClassOfDevice, 0)); thisdevkey.Close(); //add to collection IBluetoothDeviceInfo thisdevice = makeDev(address, name, classOfDevice, true); known.Add(thisdevice); } } devkey.Close(); } return known; } #endif #if !V1 IAsyncResult IBluetoothClient.BeginDiscoverDevices(int maxDevices, bool authenticated, bool remembered, bool unknown, bool discoverableOnly, AsyncCallback callback, object state) { return ((IBluetoothClient)this).BeginDiscoverDevices(maxDevices, authenticated, remembered, unknown, discoverableOnly, callback, state, null, null); } IAsyncResult IBluetoothClient.BeginDiscoverDevices(int maxDevices, bool authenticated, bool remembered, bool unknown, bool discoverableOnly, AsyncCallback callback, object state, InTheHand.Net.Sockets.BluetoothClient.LiveDiscoveryCallback liveDiscoHandler, object liveDiscoState) { var args = new DiscoDevsParamsWithLive(maxDevices, authenticated, remembered, unknown, discoverableOnly, DateTime.MinValue, liveDiscoHandler, liveDiscoState); AsyncResult_BeginDiscoverDevices ar = new AsyncResult_BeginDiscoverDevices( callback, state, args); System.Threading.ThreadPool.QueueUserWorkItem(BeginDiscoverDevices_Runner, ar); return ar; } internal class DiscoDevsParamsWithLive : DiscoDevsParams { internal readonly InTheHand.Net.Sockets.BluetoothClient.LiveDiscoveryCallback _liveDiscoHandler; internal readonly object _liveDiscoState; public DiscoDevsParamsWithLive(int maxDevices, bool authenticated, bool remembered, bool unknown, bool discoverableOnly, DateTime discoTime, InTheHand.Net.Sockets.BluetoothClient.LiveDiscoveryCallback liveDiscoHandler, object liveDiscoState) : base(maxDevices, authenticated, remembered, unknown, discoverableOnly, discoTime) { _liveDiscoHandler = liveDiscoHandler; _liveDiscoState = liveDiscoState; } } [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")] void BeginDiscoverDevices_Runner(object state) { AsyncResult_BeginDiscoverDevices ar = (AsyncResult_BeginDiscoverDevices)state; ar.SetAsCompletedWithResultOf(() => DiscoverDevices(ar.BeginParameters.maxDevices, ar.BeginParameters.authenticated, ar.BeginParameters.remembered, ar.BeginParameters.unknown, ar.BeginParameters.discoverableOnly, ar.BeginParameters._liveDiscoHandler, ar.BeginParameters._liveDiscoState), false); } IBluetoothDeviceInfo[] IBluetoothClient.EndDiscoverDevices(IAsyncResult asyncResult) { AsyncResult_BeginDiscoverDevices ar = (AsyncResult_BeginDiscoverDevices)asyncResult; return ar.EndInvoke(); } #endif #endregion #region Active private bool active = false; /// /// Gets or set a value that indicates whether a connection has been made. /// protected bool Active { get { return active; } set { active = value; } } #endregion #region Available public int Available { get { EnsureNotDisposed(); return clientSocket.Available; } } #endregion #region Client private Socket clientSocket; public Socket Client { get { return clientSocket; } set { this.clientSocket = value; } } #endregion #region Connect /// /// Connects a client to a specified endpoint. /// /// A that represents the remote device. public virtual void Connect(BluetoothEndPoint remoteEP) { EnsureNotDisposed(); if (remoteEP == null) { throw new ArgumentNullException("remoteEP"); } Connect_StartAuthenticator(remoteEP); try { var connEP = PrepareConnectEndPoint(remoteEP); clientSocket.Connect(connEP); active = true; } finally { Connect_StopAuthenticator(); } } #region Begin Connect /// /// Begins an asynchronous request for a remote host connection. /// The remote host is specified by a . /// /// A containing the /// address and UUID of the remote service. /// An AsyncCallback delegate that references the method to invoke when the operation is complete. /// A user-defined object that contains information about the connect operation. /// This object is passed to the requestCallback delegate when the operation is complete. /// public virtual IAsyncResult BeginConnect(BluetoothEndPoint remoteEP, AsyncCallback requestCallback, object state) { EnsureNotDisposed(); Connect_StartAuthenticator(remoteEP); var connEP = PrepareConnectEndPoint(remoteEP); return this.Client.BeginConnect(connEP, requestCallback, state); } #endregion #region End Connect /// /// Asynchronously accepts an incoming connection attempt. /// /// An object returned by a call to /// /// / . /// public virtual void EndConnect(IAsyncResult asyncResult) { try { Socket sock = this.Client; if (sock == null) { Debug.Assert(cleanedUp, "!cleanedUp"); throw new ObjectDisposedException("BluetoothClient"); } else { sock.EndConnect(asyncResult); } this.active = true; } finally { Connect_StopAuthenticator(); } } #endregion #endregion #region Connected public bool Connected { get { if (clientSocket == null) return false; return clientSocket.Connected; } } #endregion #region Get Stream private NetworkStream dataStream; protected virtual NetworkStream MakeStream(Socket sock) { return new NetworkStream(this.Client, true); } public NetworkStream GetStream() { EnsureNotDisposed(); if (!this.Client.Connected) { throw new InvalidOperationException("The operation is not allowed on non-connected sockets."); } if (dataStream == null) { dataStream = MakeStream(this.Client); } return dataStream; } #if TEST_EARLY public Stream GetStream2() { return GetStream(); } #endif #endregion public LingerOption LingerState { #if !NETCF get { return Client.LingerState; } set { Client.LingerState = value; } #else get { return (LingerOption)Client.GetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Linger); } set { Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Linger, value); } #endif } #region Authenticate /// /// Gets or sets the authentication state of the current connect or behaviour to use when connection is established. /// /// /// For disconnected sockets, specifies that authentication is required in order for a connect or accept operation to complete successfully. /// Setting this option actively initiates authentication during connection establishment, if the two Bluetooth devices were not previously authenticated. /// The user interface for passkey exchange, if necessary, is provided by the operating system outside the application context. /// For outgoing connections that require authentication, the connect operation fails with WSAEACCES if authentication is not successful. /// In response, the application may prompt the user to authenticate the two Bluetooth devices before connection. /// For incoming connections, the connection is rejected if authentication cannot be established and returns a WSAEHOSTDOWN error. /// public bool Authenticate { get { return m_optionHelper.Authenticate; } set { m_optionHelper.Authenticate = value; } } #endregion #region Encrypt /// /// On unconnected sockets, enforces encryption to establish a connection. /// Encryption is only available for authenticated connections. /// For incoming connections, a connection for which encryption cannot be established is automatically rejected and returns WSAEHOSTDOWN as the error. /// For outgoing connections, the connect function fails with WSAEACCES if encryption cannot be established. /// In response, the application may prompt the user to authenticate the two Bluetooth devices before connection. /// public bool Encrypt { get { return m_optionHelper.Encrypt; } set { m_optionHelper.Encrypt = value; } } #endregion #region Link Key /// /// Returns link key associated with peer Bluetooth device. /// public Guid LinkKey { get { EnsureNotDisposed(); byte[] bytes = new byte[16]; #if NETCF byte[] link = clientSocket.GetSocketOption(BluetoothSocketOptionLevel.RFComm, BluetoothSocketOptionName.GetLink, 32); Buffer.BlockCopy(link, 16, bytes, 0, 16); #endif return new Guid(bytes); } } #endregion #region Link Policy /// /// Returns the Link Policy of the current connection. /// public LinkPolicy LinkPolicy { get { EnsureNotDisposed(); #if NETCF byte[] policy = clientSocket.GetSocketOption(BluetoothSocketOptionLevel.RFComm, BluetoothSocketOptionName.GetLinkPolicy, 4); return (LinkPolicy)BitConverter.ToInt32(policy, 0); #else return LinkPolicy.Disabled; #endif } } #endregion #region Set PIN /// /// Sets the PIN associated with the currently connected device. /// /// PIN which must be composed of 1 to 16 ASCII characters. /// Assigning null (Nothing in VB) or an empty String will revoke the PIN. public void SetPin(string pin) { if (!Connected) { #if WinXP m_pinForConnect = pin; #else SetPin(null, pin); #endif } else { EndPoint rep = clientSocket.RemoteEndPoint; BluetoothAddress addr = null; if (rep != null) addr = ((BluetoothEndPoint)rep).Address; if (addr == null) throw new InvalidOperationException( "The socket needs to be connected to detect the remote device" + ", use the other SetPin method.."); SetPin(addr, pin); } } /// /// Set or change the PIN to be used with a specific remote device. /// /// Address of Bluetooth device. /// PIN string consisting of 1 to 16 ASCII characters. /// Assigning null (Nothing in VB) or an empty String will revoke the PIN. public void SetPin(BluetoothAddress device, string pin) { m_optionHelper.SetPin(device, pin); } private void Connect_StartAuthenticator(BluetoothEndPoint remoteEP) { #if WinXP if (m_pinForConnect != null) { SetPin(remoteEP.Address, m_pinForConnect); } #endif } private void Connect_StopAuthenticator() { #if WinXP if (m_pinForConnect != null) { SetPin(null, null); } #endif } #endregion #region Remote Machine Name public BluetoothEndPoint RemoteEndPoint { get { EnsureNotDisposed(); return (BluetoothEndPoint)clientSocket.RemoteEndPoint; } } /// /// Gets the name of the remote device. /// public string RemoteMachineName { get { EnsureNotDisposed(); return GetRemoteMachineName(clientSocket); } } /// /// Gets the name of the specified remote device. /// /// Address of remote device. /// Friendly name of specified device. public string GetRemoteMachineName(BluetoothAddress a) { #if WinXP var bdi = _fcty.DoGetBluetoothDeviceInfo(a); return bdi.DeviceName; #else byte[] buffer = new byte[504]; //copy remote device address to buffer Buffer.BlockCopy(a.ToByteArray(), 0, buffer, 0, 6); try { clientSocket.SetSocketOption(BluetoothSocketOptionLevel.RFComm, BluetoothSocketOptionName.ReadRemoteName, buffer); string name = string.Empty; name = System.Text.Encoding.Unicode.GetString(buffer, 8, 496); int offset = name.IndexOf('\0'); if(offset > -1) { name = name.Substring(0, offset); } return name; } catch(SocketException ex) { System.Diagnostics.Debug.WriteLine("BluetoothClient GetRemoteMachineName(addr) ReadRemoteName failed: " + ex.Message); return null; } #endif } /// /// Gets the name of a device by a specified socket. /// /// A . /// Returns a string value of the computer or device name. public static string GetRemoteMachineName(Socket s) { #if WinXP // HACK was new WindowsBluetoothDeviceInfo var bdi = new BluetoothDeviceInfo(((BluetoothEndPoint)s.RemoteEndPoint).Address); return bdi.DeviceName; #else byte[] buffer = new byte[504]; //copy remote device address to buffer Buffer.BlockCopy(((BluetoothEndPoint)s.RemoteEndPoint).Address.ToByteArray(), 0, buffer, 0, 6); string name = ""; try { s.SetSocketOption(BluetoothSocketOptionLevel.RFComm, BluetoothSocketOptionName.ReadRemoteName, buffer); name = System.Text.Encoding.Unicode.GetString(buffer, 8, 496); int offset = name.IndexOf('\0'); if(offset > -1) { name = name.Substring(0, offset); } return name; } catch(SocketException ex) { System.Diagnostics.Debug.WriteLine("BluetoothClient GetRemoteMachineName(socket) ReadRemoteName failed: " + ex.Message); return null; } #endif } #endregion #region IDisposable Members /// /// Releases the unmanaged resources used by the BluetoothClient and optionally releases the managed resources. /// /// true to release both managed and unmanaged resources; false to release only unmanaged resources. protected virtual void Dispose(bool disposing) { if (!cleanedUp) { if (disposing) { IDisposable idStream = dataStream; if (idStream != null) { //dispose the stream which will also close the socket idStream.Dispose(); } else { if (this.Client != null) { this.Client.Close(); this.Client = null; } } // TODO ??? m_optionHelper.Dispose(); } cleanedUp = true; } } private void EnsureNotDisposed() { Debug.Assert(cleanedUp == (clientSocket == null), "always consistent!! (" + cleanedUp + " != " + (clientSocket == null) + ")"); if (cleanedUp || (clientSocket == null)) throw new ObjectDisposedException("BluetoothClient"); } /// /// Closes the and the underlying connection. /// /// - /// public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } /// /// Frees resources used by the class. /// ~SocketBluetoothClient() { Dispose(false); } #endregion #region Throw SocketException For HR internal static void ThrowSocketExceptionForHR(int errorCode) { if (errorCode < 0) { int socketerror = 0; socketerror = Marshal.GetLastWin32Error(); throw new SocketException(socketerror); } } internal static void ThrowSocketExceptionForHrExceptFor(int errorCode, params int[] nonErrorCodes) { if (errorCode < 0) { int socketerror = 0; socketerror = Marshal.GetLastWin32Error(); if (-1 != Array.IndexOf(nonErrorCodes, socketerror, 0, nonErrorCodes.Length)) { return; } throw new SocketException(socketerror); } } #endregion internal class MsftSocketOptionHelper : ISocketOptionHelper { readonly Socket m_socket; #if !WinCE private bool authenticate = false; private BluetoothWin32Authentication m_authenticator; #endif private bool encrypt = false; internal MsftSocketOptionHelper(Socket socket) { m_socket = socket; } #region Authenticate /// /// Gets or sets the authentication state of the current connect or behaviour to use when connection is established. /// /// /// For disconnected sockets, specifies that authentication is required in order for a connect or accept operation to complete successfully. /// Setting this option actively initiates authentication during connection establishment, if the two Bluetooth devices were not previously authenticated. /// The user interface for passkey exchange, if necessary, is provided by the operating system outside the application context. /// For outgoing connections that require authentication, the connect operation fails with WSAEACCES if authentication is not successful. /// In response, the application may prompt the user to authenticate the two Bluetooth devices before connection. /// For incoming connections, the connection is rejected if authentication cannot be established and returns a WSAEHOSTDOWN error. /// public bool Authenticate { get { #if NETCF byte[] authbytes = m_socket.GetSocketOption(BluetoothSocketOptionLevel.RFComm, BluetoothSocketOptionName.GetAuthenticationEnabled, 4); int auth = BitConverter.ToInt32(authbytes, 0); return (auth==0) ? false : true; #else return authenticate; #endif } set { #if NETCF m_socket.SetSocketOption(BluetoothSocketOptionLevel.RFComm, BluetoothSocketOptionName.SetAuthenticationEnabled, (int)(value ? 1 : 0)); #else m_socket.SetSocketOption(BluetoothSocketOptionLevel.RFComm, BluetoothSocketOptionName.Authenticate, value); authenticate = value; #endif } } #endregion #region Encrypt /// /// On unconnected sockets, enforces encryption to establish a connection. /// Encryption is only available for authenticated connections. /// For incoming connections, a connection for which encryption cannot be established is automatically rejected and returns WSAEHOSTDOWN as the error. /// For outgoing connections, the connect function fails with WSAEACCES if encryption cannot be established. /// In response, the application may prompt the user to authenticate the two Bluetooth devices before connection. /// public bool Encrypt { get { return encrypt; } set { m_socket.SetSocketOption(BluetoothSocketOptionLevel.RFComm, BluetoothSocketOptionName.Encrypt, (int)(value ? 1 : 0)); encrypt = value; } } #endregion #region Set Pin public void SetPin(BluetoothAddress device, string pin) { #if WinXP if (pin != null) { m_authenticator = new BluetoothWin32Authentication(device, pin); } else { if (m_authenticator != null) { m_authenticator.Dispose(); } } #else byte[] link = new byte[32]; //copy remote device address if (device != null) { Buffer.BlockCopy(device.ToByteArray(), 0, link, 8, 6); } //copy PIN if (pin != null & pin.Length > 0) { if (pin.Length > 16) { throw new ArgumentOutOfRangeException("PIN must be between 1 and 16 ASCII characters"); } //copy pin bytes byte[] pinbytes = System.Text.Encoding.ASCII.GetBytes(pin); Buffer.BlockCopy(pinbytes, 0, link, 16, pin.Length); BitConverter.GetBytes(pin.Length).CopyTo(link, 0); } m_socket.SetSocketOption(BluetoothSocketOptionLevel.RFComm, BluetoothSocketOptionName.SetPin, link); #endif } #endregion }//class--SocketOptionHelper }//class--BluetoothClient }