// 32feet.NET - Personal Area Networking for .NET
//
// InTheHand.Net.Sockets.BluetoothClient
//
// Copyright (c) 2003-2008 In The Hand Ltd, All rights reserved.
// This source code is licensed under the In The Hand Community License - see License.txt
//#define WIN32_READ_BTH_DEVICE_INFO
using System;
using System.Collections;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Runtime.InteropServices;
using InTheHand.Net.Bluetooth;
using Microsoft.Win32;
using InTheHand.Runtime.InteropServices;
#if !V1
using List_IBluetoothDeviceInfoSystem.Collections.Generic.ListInTheHand.Net.Bluetooth.Factory.IBluetoothDeviceInfo;
#else
using List_IBluetoothDeviceInfoSystem.Collections.ArrayList;
#endif
#if !V1
using AsyncResult_BeginDiscoverDevicesInTheHand.Net.AsyncResultInTheHand.Net.Bluetooth.Factory.IBluetoothDeviceInfoInTheHand.Net.Bluetooth.Factory.DiscoDevsParams;
using System.Diagnostics.CodeAnalysis;
#endif
using System.Diagnostics;
using InTheHand.Net.Bluetooth.Factory;
namespace InTheHand.Net.Sockets{
/// <summary>
/// Provides client connections for Bluetooth network services.
/// </summary>
/// <!-- SocketBluetoothClient --> <remarks>This class currently only supports devices which use the Microsoft Bluetooth stack, devices which use the WidComm stack will not work.</remarks>
class SocketBluetoothClient : IBluetoothClient
{
private bool cleanedUp = false;
private SocketOptionHelper 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
/// <summary>
/// Creates a new instance of <see cref="BluetoothClient"/>.
/// </summary>
public SocketBluetoothClient()
{
try
{
this.Client = new Socket(AddressFamily32.Bluetooth, SocketType.Stream, BluetoothProtocolType.RFComm);
}
catch (SocketException se)
{
throw new PlatformNotSupportedException("32feet.NET does not support the Bluetooth stack on this device.", se);
}
m_optionHelper = new SocketOptionHelper(this.Client);
}
/// <summary>
/// Initializes a new instance of the <see cref="BluetoothClient"/> class and binds it to the specified local endpoint.
/// </summary>
/// <param name="localEP">The <see cref="BluetoothEndPoint"/> to which you bind the Bluetooth Socket.
/// Only necessary on multi-radio system where you want to select the local radio to use.</param>
public SocketBluetoothClient(BluetoothEndPoint localEP)
: this()
{
if (localEP == null)
{
throw new ArgumentNullException("localEP");
}
//bind to specific local endpoint
this.Client.Bind(localEP);
}
internal SocketBluetoothClient(Socket acceptedSocket)
{
this.Client = acceptedSocket;
active = true;
m_optionHelper = new SocketOptionHelper(this.Client);
}
#endregion
#region InquiryAccessCode
private int iac = BluetoothAddress.Giac; //0x9E8B33;
#if NETCF
/// <summary>
///
/// </summary>
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);
/// <summary>
/// Amount of time allowed to perform the query.
/// </summary>
/// <remarks>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.</remarks>
public TimeSpan InquiryLength
{
get
{
return inquiryLength;
}
set
{
if((value.TotalSeconds > 0) && (value.TotalSeconds <= 60))
{
inquiryLength = value;
}
else
{
throw new ArgumentOutOfRangeException(
#if !V1
"value",
#endif
"QueryLength must be a positive timespan between 0 and 60 seconds.");
}
}
}
#endregion
#region Discover Devices
/// <summary>
/// Discovers accessible Bluetooth devices and returns their names and addresses.
/// </summary>
/// <param name="maxDevices">The maximum number of devices to get information about.</param>
/// <param name="authenticated">True to return previously authenticated/paired devices.</param>
/// <param name="remembered">True to return remembered devices.</param>
/// <param name="unknown">True to return previously unknown devices.</param>
/// <param name="discoverableOnly">True to return only discoverable devices
/// (where both in range and in discoverable mode).
/// When <see langword="true"/> all other flags are ignored.
/// <strong>Note: Does NOT work on Win32 with the Microsoft stack.</strong>
/// </param>
/// <returns>An array of BluetoothDeviceInfo objects describing the devices discovered.</returns>
/// -
/// <remarks>
/// <para>The <see paramref="discoverableOnly"/> 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.
/// </para>
/// <para>
/// 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
/// <see cref="InTheHand.Net.Sockets.BluetoothDeviceInfo.LastSeen">BluetoothDeviceInfo.LastSeen</see>
/// property is of no use there: on XP and Vista at least the value provided
/// is always simply the current time.
/// </para>
/// </remarks>
public IBluetoothDeviceInfo[] DiscoverDevices(int maxDevices,
bool authenticated, bool remembered, bool unknown, bool discoverableOnly)
{
WqsOffset.AssertCheckLayout();
CsaddrInfoOffsets.AssertCheckLayout();
//
#if WinXP
const bool Win32DiscoverableOnlyIncludesAllDevices = false;
#endif
int discoveredDevices = 0;
List_IBluetoothDeviceInfo al = null;
int handle = 0;
int lookupresult = 0;
#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.ToInt16(inquiryLength.TotalSeconds / 1.28);
bib.num_responses = Convert.ToInt16(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
lookupresult = NativeMethods.WSALookupServiceBegin(buffer, flags, out handle);
hBlob.Free();
hBib.Free();
while(discoveredDevices < maxDevices && lookupresult != -1)
{
#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
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);
}
BluetoothEndPoint bep = new BluetoothEndPoint(null, BluetoothService.Empty);
bep = (BluetoothEndPoint)bep.Create(btsa);
//new deviceinfo
IBluetoothDeviceInfo newdevice;
#if NETCF
newdevice = new WindowsBluetoothDeviceInfo(bep.Address, bir.cod);
#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 NETCF
}
#endif
//stop looking
if(handle!=0)
{
lookupresult = NativeMethods.WSALookupServiceEnd(handle);
}
#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));
BDIF flags = bdi.flags;
Console.WriteLine("[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
private static List_IBluetoothDeviceInfo WinCEReadKnownDevicesFromRegistry()
{
List_IBluetoothDeviceInfo known = new List_IBluetoothDeviceInfo();
//open bluetooth device key
RegistryKey devkey = Registry.LocalMachine.OpenSubKey("Software\\Microsoft\\Bluetooth\\Device");
//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("name", "").ToString();
uint classOfDevice = Convert.ToUInt32(thisdevkey.GetValue("class", 0));
thisdevkey.Close();
//add to collection
IBluetoothDeviceInfo thisdevice = new WindowsBluetoothDeviceInfo(address, name, classOfDevice, true);
known.Add(thisdevice);
/*
int devindex = al.IndexOf(thisdevice);
if (devindex == -1)
{
//if we intended to search for authenticated devices add this one to the collection
if (addFromRegistry)
{
al.Add(thisdevice);
}
}
else
{
if (addFromRegistry)
{
//set authenticated flag on existing discovered device
((IBluetoothDeviceInfo)al[devindex]).SetAuthenticated();
}
else
{
//we want to exclude already authenticated devices so remove it from the collection
al.RemoveAt(devindex);
}
}
*/
}
}
devkey.Close();
}
return known;
}
#endif
#if !V1
IAsyncResult IBluetoothClient.BeginDiscoverDevices(int maxDevices,
bool authenticated, bool remembered, bool unknown, bool discoverableOnly,
AsyncCallback callback, object state)
{
var args = new DiscoDevsParams(maxDevices,
authenticated, remembered, unknown, discoverableOnly,
DateTime.MinValue);
AsyncResult_BeginDiscoverDevices ar = new AsyncResult_BeginDiscoverDevices(
callback, state, args);
System.Threading.ThreadPool.QueueUserWorkItem(BeginDiscoverDevices_Runner, ar);
return ar;
}
[SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
void BeginDiscoverDevices_Runner(object state)
{
AsyncResult_BeginDiscoverDevices ar = (AsyncResult_BeginDiscoverDevices)state;
try {
IBluetoothDeviceInfo[] result = DiscoverDevices(ar.BeginParameters.maxDevices,
ar.BeginParameters.authenticated, ar.BeginParameters.remembered,
ar.BeginParameters.unknown, ar.BeginParameters.discoverableOnly);
ar.SetAsCompleted(result, false);
} catch (Exception ex) {
ar.SetAsCompleted(ex, false);
}
}
IBluetoothDeviceInfo[] IBluetoothClient.EndDiscoverDevices(IAsyncResult asyncResult)
{
AsyncResult_BeginDiscoverDevices ar = (AsyncResult_BeginDiscoverDevices)asyncResult;
return ar.EndInvoke();
}
#endif
#endregion
#region Active
private bool active = false;
/// <summary>
/// Gets or set a value that indicates whether a connection has been made.
/// </summary>
protected bool Active
{
get
{
return active;
}
set
{
active = value;
}
}
#endregion
#region Available
/// <summary>
/// Gets the amount of data that has been received from the network and is available to be read.
/// </summary>
/// <value>The number of bytes of data received from the network and available to be read.</value>
/// <exception cref="ObjectDisposedException">The <see cref="Socket"/> has been closed.</exception>
public int Available
{
get
{
EnsureNotDisposed();
return clientSocket.Available;
}
}
#endregion
#region Client
private Socket clientSocket;
/// <summary>
/// Gets or sets the underlying <see cref="Socket"/>.
/// </summary>
public Socket Client
{
get
{
return clientSocket;
}
set
{
this.clientSocket = value;
}
}
#endregion
#region Connect
/// <summary>
/// Connects a client to a specified endpoint.
/// </summary>
/// <param name="remoteEP">A <see cref="BluetoothEndPoint"/> that represents the remote device.</param>
public void Connect(BluetoothEndPoint remoteEP)
{
EnsureNotDisposed();
if (remoteEP == null)
{
throw new ArgumentNullException("remoteEP");
}
Connect_StartAuthenticator(remoteEP);
try {
clientSocket.Connect(remoteEP);
active = true;
} finally {
Connect_StopAuthenticator();
}
}
#region Begin Connect
/// <summary>
/// Begins an asynchronous request for a remote host connection.
/// The remote host is specified by a <see cref="BluetoothEndPoint"/>.
/// </summary>
/// <param name="remoteEP">A <see cref="BluetoothEndPoint"/> containing the
/// address and UUID of the remote service.</param>
/// <param name="requestCallback">An AsyncCallback delegate that references the method to invoke when the operation is complete.</param>
/// <param name="state">A user-defined object that contains information about the connect operation.
/// This object is passed to the requestCallback delegate when the operation is complete.</param>
/// <returns></returns>
public IAsyncResult BeginConnect(BluetoothEndPoint remoteEP, AsyncCallback requestCallback, object state)
{
EnsureNotDisposed();
Connect_StartAuthenticator(remoteEP);
return this.Client.BeginConnect(remoteEP, requestCallback, state);
}
#endregion
#region End Connect
/// <summary>
/// Asynchronously accepts an incoming connection attempt.
/// </summary>
/// <param name="asyncResult">An <see cref="IAsyncResult"/> object returned by a call to
/// <see cref="M:BeginConnect(InTheHand.Net.Sockets.BluetoothEndPoint,System.AsyncCallback,System.Object)"/>
/// / <see cref="M:BeginConnect(InTheHand.Net.Sockets.BluetoothAddress,System.Guid,System.AsyncCallback,System.Object)"/>.
/// </param>
public 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
/// <summary>
/// Gets a value indicating whether the underlying <see cref="Socket"/> for a <see cref="BluetoothClient"/> is connected to a remote host.
/// </summary>
/// <value>true if the <see cref="Client"/> socket was connected to a remote resource as of the most recent operation; otherwise, false.</value>
public bool Connected
{
get
{
if (clientSocket == null)
return false;
return clientSocket.Connected;
}
}
#endregion
#region Get Stream
private NetworkStream dataStream;
/// <summary>
/// Gets the underlying stream of data.
/// </summary>
/// <returns>The underlying <see cref="NetworkStream"/>.</returns>
/// <remarks><see cref="GetStream"/> returns a <see cref="NetworkStream"/> that you can use to send and receive data.
/// The <see cref="NetworkStream"/> class inherits from the <see cref="Stream"/> class, which provides a rich collection of methods and properties used to facilitate network communications.
/// <para>You must call the <see cref="Connect(InTheHand.Net.BluetoothEndPoint)"/> / <see cref="M:Connect(InTheHand.Net.BluetoothAddress,System.Guid)"/>
/// method first, or the <see cref="GetStream"/> method will throw an <see cref="InvalidOperationException"/>.
/// After you have obtained the <see cref="NetworkStream"/>, call the <see cref="NetworkStream.Write"/> method to send data to the remote host.
/// Call the <see cref="NetworkStream.Read"/> method to receive data arriving from the remote host.
/// Both of these methods block until the specified operation is performed.
/// You can avoid blocking on a read operation by checking the <see cref="NetworkStream.DataAvailable"/> property.
/// A true value means that data has arrived from the remote host and is available for reading.
/// In this case, <see cref="NetworkStream.Read"/> is guaranteed to complete immediately.
/// If the remote host has shutdown its connection, <see cref="NetworkStream.Read"/> will immediately return with zero bytes.</para></remarks>
/// <exception cref="InvalidOperationException">The <see cref="BluetoothClient"/> is not connected to a remote host.</exception>
/// <exception cref="ObjectDisposedException">The <see cref="BluetoothClient"/> has been closed.</exception>
public NetworkStream GetStream()
{
EnsureNotDisposed();
if (!this.Client.Connected)
{
throw new InvalidOperationException("The operation is not allowed on non-connected sockets.");
}
if (dataStream == null)
{
dataStream = new NetworkStream(this.Client, true);
}
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
/// <summary>
/// Gets or sets the authentication state of the current connect or behaviour to use when connection is established.
/// </summary>
/// <remarks>
/// 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.
/// </remarks>
public bool Authenticate
{
get { return m_optionHelper.Authenticate; }
set { m_optionHelper.Authenticate = value; }
}
#endregion
#region Encrypt
/// <summary>
/// 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.
/// </summary>
public bool Encrypt
{
get { return m_optionHelper.Encrypt; }
set { m_optionHelper.Encrypt = value; }
}
#endregion
#region Link Key
/// <summary>
/// Returns link key associated with peer Bluetooth device.
/// </summary>
public Guid LinkKey
{
get
{
EnsureNotDisposed();
byte[] link = clientSocket.GetSocketOption(BluetoothSocketOptionLevel.RFComm, BluetoothSocketOptionName.GetLink, 32);
byte[] bytes = new byte[16];
Buffer.BlockCopy(link, 16, bytes, 0, 16);
return new Guid(bytes);
}
}
#endregion
#region Link Policy
/// <summary>
/// Returns the Link Policy of the current connection.
/// </summary>
public LinkPolicy LinkPolicy
{
get
{
EnsureNotDisposed();
byte[] policy = clientSocket.GetSocketOption(BluetoothSocketOptionLevel.RFComm, BluetoothSocketOptionName.GetLinkPolicy, 4);
return (LinkPolicy)BitConverter.ToInt32(policy, 0);
}
}
#endregion
#region Set PIN
/// <summary>
/// Sets the PIN associated with the currently connected device.
/// </summary>
/// <param name="pin">PIN which must be composed of 1 to 16 ASCII characters.</param>
/// <remarks>Assigning null (Nothing in VB) or an empty String will revoke the PIN.</remarks>
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);
}
}
/// <summary>
/// Set or change the PIN to be used with a specific remote device.
/// </summary>
/// <param name="device">Address of Bluetooth device.</param>
/// <param name="pin">PIN string consisting of 1 to 16 ASCII characters.</param>
/// <remarks>Assigning null (Nothing in VB) or an empty String will revoke the PIN.</remarks>
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;
}
}
/// <summary>
/// Gets the name of the remote device.
/// </summary>
public string RemoteMachineName
{
get
{
EnsureNotDisposed();
return GetRemoteMachineName(clientSocket);
}
}
/// <summary>
/// Gets the name of the specified remote device.
/// </summary>
/// <param name="a">Address of remote device.</param>
/// <returns>Friendly name of specified device.</returns>
public string GetRemoteMachineName(BluetoothAddress a)
{
#if WinXP
IBluetoothDeviceInfo bdi = new WindowsBluetoothDeviceInfo(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
}
/// <summary>
/// Gets the name of a device by a specified socket.
/// </summary>
/// <param name="s"> A <see cref="Socket"/>.</param>
/// <returns>Returns a string value of the computer or device name.</returns>
public static string GetRemoteMachineName(Socket s)
{
#if WinXP
IBluetoothDeviceInfo bdi = new WindowsBluetoothDeviceInfo(((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
/// <summary>
/// Releases the unmanaged resources used by the BluetoothClient and optionally releases the managed resources.
/// </summary>
/// <param name="disposing">true to release both managed and unmanaged resources; false to release only unmanaged resources.</param>
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");
}
/// <summary>
/// Closes the <see cref="BluetoothClient"/> and the underlying connection.
/// </summary>
/// -
/// <seealso cref="M:InTheHand.Net.Sockets.BluetoothClient.Close"/>
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
/// <summary>
/// Frees resources used by the <see cref="BluetoothClient"/> class.
/// </summary>
~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 SocketOptionHelper
{
readonly Socket m_socket;
#if ! WinCE
private bool authenticate = false;
private BluetoothWin32Authentication m_authenticator;
#endif
private bool encrypt = false;
internal SocketOptionHelper(Socket socket)
{
m_socket = socket;
}
#region Authenticate
/// <summary>
/// Gets or sets the authentication state of the current connect or behaviour to use when connection is established.
/// </summary>
/// <remarks>
/// 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.
/// </remarks>
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.XPAuthenticate, value);
authenticate = value;
#endif
}
}
#endregion
#region Encrypt
/// <summary>
/// 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.
/// </summary>
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
}
|