// 32feet.NET - Personal Area Networking for .NET
//
// InTheHand.Net.Sockets.WidcommBluetoothClient
//
// Copyright (c) 2008-2010 In The Hand Ltd, All rights reserved.
// This source code is licensed under the In The Hand Community License - see License.txt
using System;
using InTheHand.Net.Bluetooth;
using InTheHand.Net.Sockets;
using System.Diagnostics;
using System.Net.Sockets;
using System.Collections.Generic;
using List_IBluetoothDeviceInfoSystem.Collections.Generic.ListInTheHand.Net.Bluetooth.Factory.IBluetoothDeviceInfo;
using AsyncResultDDInTheHand.Net.AsyncResultSystem.Collections.Generic.ListInTheHand.Net.Bluetooth.Factory.IBluetoothDeviceInfoInTheHand.Net.Bluetooth.Factory.DiscoDevsParams;
using System.Threading;
using InTheHand.Net.Bluetooth.Factory;
using System.Diagnostics.CodeAnalysis;
namespace InTheHand.Net.Bluetooth.Widcomm{
/// <summary>
/// Provides client connections for Bluetooth network services with Widcomm stack.
/// </summary>
internal sealed class WidcommBluetoothClient : IBluetoothClient
{
[SuppressMessage("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields", Justification = "Is used in the DEBUG build.")]
bool m_disposed;
readonly WidcommBluetoothFactoryBase m_factory;
TimeSpan _inquiryLength = TimeSpan.FromSeconds(15);
NetworkStream m_netStrm;
readonly WidcommRfcommStream m_conn;
readonly WidcommBtInterface m_btIf;
//TEST_EARLY: public WidcommBtInterface m_btIf___HACK;
AsyncResultNoResult m_arConnect;
//
string m_passcode;
//BluetoothEndPoint m_localEP;
int? _remotePort; // Only known in the client case, not in the BtLsnr->BtCli case.
private BluetoothEndPoint m_remoteEndPoint;
//--------------
internal WidcommBluetoothClient(WidcommBluetoothFactoryBase factory)
{
Debug.Assert(factory != null, "factory must not be null; is used by GetRemoteMachineName etc.");
m_factory = factory;
m_conn = factory.GetWidcommRfcommStream();
m_btIf = factory.GetWidcommBtInterface();
//TEST_EARLY: m_btIf___HACK = m_btIf;
}
internal WidcommBluetoothClient(BluetoothEndPoint localEP, WidcommBluetoothFactoryBase factory)
: this(factory)
{
// All we could do is fail if the specified local address isn't the
// (sole) Widcomm Radio's address. We also can't tell Widcomm to
// bind to a specified local port so all we could do is fail if the
// specified port is not 'unspecified' (i.e. 0/-1).
throw new NotSupportedException("Don't support binding to a particular local address/port.");
}
/// <summary>
/// Used by WidcommBluetoothListener to return the newly accepted connection.
/// </summary>
/// -
/// <param name="strm">The WidcommRfcommStream containing the newly connected
/// RfCommPort.
/// </param>
/// <param name="factory">Factory to use in GetRemoteMachineName etc.
/// </param>
internal WidcommBluetoothClient(WidcommRfcommStream strm, WidcommBluetoothFactoryBase factory)
{
Debug.Assert(factory != null, "factory must not be null; is used by GetRemoteMachineName etc.");
m_factory = factory;
m_conn = strm;
}
//--------------
public System.Net.Sockets.NetworkStream GetStream()
{
GetStream2(); // Check validity (but a sub-type, so hard to use here).
if (m_netStrm == null)
m_netStrm = new WidcommDecoratorNetworkStream(m_conn);
return m_netStrm;
}
public System.IO.Stream GetStream2()
{
//if (cleanedUp) {
// throw new ObjectDisposedException(base.GetType().FullName);
//}
if (!Connected) {
throw new InvalidOperationException("The operation is not allowed on non-connected sockets.");
}
return m_conn;
}
public LingerOption LingerState
{
get { return m_conn.LingerState; }
set { m_conn.LingerState = value; }
}
//--------------------------------------------------------------
public bool Connected
{
get { return m_conn.Connected; }
}
public void Dispose()
{
m_disposed = true;
m_conn.Close();
//
// Abort the Connect (usually the SDP lookup) if we're still in that phase
Connect_SetAsCompleted_CompletedSyncFalse(null, new ObjectDisposedException("BluetoothClient"));
}
void Connect_SetAsCompleted_CompletedSyncFalse(AsyncResultNoResult arConnect_Debug, Exception ex)
{
// Read state to check not already completed (is null). And set it to null always.
AsyncResultNoResult arOrig = Interlocked.Exchange(ref m_arConnect, null);
if (arOrig == null) {
// We use m_arConnect being null as an indication that it is already SetAsCompleted.
Debug.Assert(m_disposed, "arConnect is already IsCompleted but NOT m_cancelled");
Debug.Assert(arConnect_Debug == null || arConnect_Debug.IsCompleted, "NOT arConnect.IsCompleted: How!? Different instances?");
} else {
Debug.Assert(arConnect_Debug == null || arConnect_Debug == arOrig, "arConnect != m_arConnect: should only be one instance!");
// Set!
var args = new RaiseConnectParams { arOrig = arOrig, ex = ex };
ThreadPool.QueueUserWorkItem(RaiseConnect, args);
}
}
class RaiseConnectParams
{
internal Exception ex { get; set; }
internal AsyncResultNoResult arOrig { get; set; }
}
private static void RaiseConnect(object state)
{
var args = (RaiseConnectParams)state;
args.arOrig.SetAsCompleted(args.ex, false);
}
public void Connect(BluetoothEndPoint remoteEP)
{
IAsyncResult ar = BeginConnect(remoteEP, null, null);
EndConnect(ar);
}
public IAsyncResult BeginConnect(BluetoothEndPoint remoteEP, AsyncCallback requestCallback, object state)
{
// Just in case the user modifies the original endpoint or address!!!
BluetoothEndPoint rep2 = (BluetoothEndPoint)remoteEP.Clone();
//
AsyncResultNoResult arConnect = new AsyncResultNoResult(requestCallback, state);
AsyncResultNoResult origArConnect = System.Threading.Interlocked.CompareExchange(
ref m_arConnect, arConnect, null);
if (origArConnect != null)
throw new InvalidOperationException("Another Connect operation is already in progress.");
BeginFillInPort(rep2, Connect_FillInPortCallback,
new BeginConnectState(rep2, arConnect));
return arConnect;
}
public void EndConnect(IAsyncResult asyncResult)
{
AsyncResultNoResult ar2 = (AsyncResultNoResult)asyncResult;
ar2.EndInvoke();
Debug.Assert(m_arConnect == null, "NOT m_arConnect == null");
}
sealed class BeginConnectState
{
//Unused: internal BluetoothEndPoint inputEP;
internal AsyncResultNoResult arCliConnect;
public BeginConnectState(BluetoothEndPoint inputEP, AsyncResultNoResult arCliConnect)
{
//this.inputEP = inputEP;
this.arCliConnect = arCliConnect;
}
}
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes",
Justification = "Exception is rethrown in EndXxxx.")]
void Connect_FillInPortCallback(IAsyncResult ar)
{
BeginConnectState bcState = (BeginConnectState)ar.AsyncState;
AsyncResultNoResult arConnect = bcState.arCliConnect;
try {
BluetoothEndPoint remoteEpWithPort = EndFillInPort(ar);
if (arConnect.IsCompleted) {
// User called Close/Dispose when we were in (slow!) SDP lookup.
Debug.Assert(m_disposed, "arConnect.IsCompleted but NOT m_cancelled");
return;
}
_remotePort = remoteEpWithPort.Port;
Debug.Assert(_remotePort != -1 && _remotePort != 0, "port is 'empty' is: " + _remotePort);
m_conn.BeginConnect(remoteEpWithPort, m_passcode, Connect_ConnCallback, bcState);
} catch (Exception ex) {
Connect_SetAsCompleted_CompletedSyncFalse(arConnect, ex);
}
}
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes",
Justification = "Exception is rethrown in EndXxxx.")]
void Connect_ConnCallback(IAsyncResult ar)
{
BeginConnectState bsState = (BeginConnectState)ar.AsyncState;
AsyncResultNoResult arCliConnect = bsState.arCliConnect;
try {
m_conn.EndConnect(ar);
Connect_SetAsCompleted_CompletedSyncFalse(arCliConnect, null);
} catch (Exception ex) {
Connect_SetAsCompleted_CompletedSyncFalse(arCliConnect, ex);
}
}
//--------------------------------------------------------------
private IAsyncResult BeginFillInPort(BluetoothEndPoint bep, AsyncCallback asyncCallback, Object state)
{
WidcommUtils.Trace_WriteLine("BeginFillInPortState");
AsyncResult<BluetoothEndPoint> arFIP = new AsyncResult<BluetoothEndPoint>(asyncCallback, state);
if (bep.Port != 0 && bep.Port != -1) { // Thus, no modification required.
WidcommUtils.Trace_WriteLine("BeginFillInPort, has port -> Completed Syncronously");
Debug.Assert(bep.Port >= BluetoothEndPoint.MinScn && bep.Port <= BluetoothEndPoint.MaxScn, "!!Port=" + bep.Port);
arFIP.SetAsCompleted(bep, true);
return arFIP;
}
IAsyncResult ar2 = m_btIf.BeginServiceDiscovery(bep.Address, bep.Service, SdpSearchScope.ServiceClassOnly,
FillInPort_ServiceDiscoveryCallback, new BeginFillInPortState(bep, arFIP));
return arFIP;
}
private BluetoothEndPoint EndFillInPort(IAsyncResult ar)
{
AsyncResult<BluetoothEndPoint> arFIP = (AsyncResult<BluetoothEndPoint>)ar;
return arFIP.EndInvoke();
}
sealed class BeginFillInPortState
{
internal BluetoothEndPoint inputEP;
internal AsyncResult<BluetoothEndPoint> arFillInPort;
public BeginFillInPortState(BluetoothEndPoint inputEP, AsyncResult<BluetoothEndPoint> arFillInPort)
{
this.inputEP = inputEP;
this.arFillInPort = arFillInPort;
}
}
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes",
Justification = "Exception is rethrown in EndXxxx.")]
void FillInPort_ServiceDiscoveryCallback(IAsyncResult ar)
{
BeginFillInPortState fipState = (BeginFillInPortState)ar.AsyncState;
AsyncResult<BluetoothEndPoint> arFIP = fipState.arFillInPort;
try {
using (ISdpDiscoveryRecordsBuffer recBuf = m_btIf.EndServiceDiscovery(ar)) {
if (recBuf.RecordCount == 0) {
WidcommUtils.Trace_WriteLine("FillInPort_ServiceDiscoveryCallback, zero records!");
arFIP.SetAsCompleted(WidcommSocketExceptions.Create_NoResultCode(
WidcommSocketExceptions.SocketError_NoSuchService, "PortLookup_Zero"),
false);
} else {
int[] ports = recBuf.Hack_GetPorts();
WidcommUtils.Trace_WriteLine("FillInPort_ServiceDiscoveryCallback, got {0} records.", recBuf.RecordCount);
// Do this in reverse order, as Widcomm appears to keep old
// (out of date!!) service records around, so we want to
// use the newests ones in preference.
Debug.Assert(ports.Length >= 1, "NOT ports.Length>=1, is: " + ports.Length);
for (int i = ports.Length - 1; i >= 0; --i) {
int cur = ports[i];
if (cur != -1) {
WidcommUtils.Trace_WriteLine("FillInPort_ServiceDiscoveryCallback, got port: {0}", cur);
BluetoothEndPoint epWithPort = new BluetoothEndPoint(
fipState.inputEP.Address, fipState.inputEP.Service, (byte)cur);
arFIP.SetAsCompleted(epWithPort, false);
return;
}
}//for
WidcommUtils.Trace_WriteLine("FillInPort_ServiceDiscoveryCallback, no scn found");
// -> Error. No Rfcomm SCN!
arFIP.SetAsCompleted(WidcommSocketExceptions.Create_NoResultCode(
WidcommSocketExceptions.SocketError_ServiceNoneRfcommScn, "PortLookup_NoneRfcomm"),
false);
}
}
} catch (Exception ex) {
arFIP.SetAsCompleted(ex, false);
}
}
//--------------------------------------------------------------
public int Available
{
get { return m_conn.AmountInReadBuffers; }
}
//--------------------------------------------------------------
#if NETCF
// No support for CBtIf::GetRemoteDeviceInfo on my iPAQ.
static bool s_useRegistryForGetKnownRemoteDevice = true;
#else
// No way to lookup *all* known devices, need specific CoD value!
static bool s_useRegistryForGetKnownRemoteDevice = true;
#endif
/// <summary>
/// ... Allow the tests to disable the Registry lookup.
/// </summary>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode",
Justification = "Used by unit-tests.")]
internal static bool ReadKnownDeviceFromTheRegistry
{
set { s_useRegistryForGetKnownRemoteDevice = value; }
get { return s_useRegistryForGetKnownRemoteDevice; }
}
public IBluetoothDeviceInfo[] DiscoverDevices(int maxDevices,
bool authenticated, bool remembered, bool unknown, bool discoverableOnly)
{
IAsyncResult ar = BeginDiscoverDevices(maxDevices,
authenticated, remembered, unknown, discoverableOnly,
null, null);
return EndDiscoverDevices(ar);
}
public IAsyncResult BeginDiscoverDevices(int maxDevices,
bool authenticated, bool remembered, bool unknown, bool discoverableOnly,
AsyncCallback callback, object state)
{
DateTime discoTime = DateTime.UtcNow;
DiscoDevsParams args = new DiscoDevsParams(maxDevices, authenticated, remembered, unknown, discoverableOnly, discoTime);
AsyncResultDD arDD = new AsyncResultDD(callback, state, args);
//
if (unknown || discoverableOnly) { // No need to do SLOW Inquiry when just want known remembered.
IAsyncResult ar = m_btIf.BeginInquiry(maxDevices, authenticated, remembered, unknown,
_inquiryLength,
DiscoDevs_InquiryCallback, arDD);
} else {
arDD.SetAsCompleted(null, true);
}
return arDD;
}
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
void DiscoDevs_InquiryCallback(IAsyncResult ar)
{
AsyncResultDD arDD = (AsyncResultDD)ar.AsyncState;
try {
List_IBluetoothDeviceInfo discoverableDevices = m_btIf.EndInquiry(ar);
arDD.SetAsCompleted(discoverableDevices, false);
} catch (Exception ex) {
arDD.SetAsCompleted(ex, false);
}
}
public IBluetoothDeviceInfo[] EndDiscoverDevices(IAsyncResult asyncResult)
{
AsyncResultDD arDD = (AsyncResultDD)asyncResult;
List_IBluetoothDeviceInfo discoverableDevices = arDD.EndInvoke();
DiscoDevsParams args = arDD.BeginParameters;
// DEBUG Iff 'known' devices only: we complete immediately (sync'ly), and with null result.
if (args.unknown || args.discoverableOnly) {
// Result from BeginInquiry callback expected
Debug.Assert(discoverableDevices != null, "a1");
Debug.Assert(!arDD.CompletedSynchronously, "a2"); // don't really care however
} else {
// Null result from BeginDD method expected.
Debug.Assert(discoverableDevices == null, "b1");
Debug.Assert(arDD.CompletedSynchronously, "b2");
}
//
List_IBluetoothDeviceInfo knownDevices;
if (s_useRegistryForGetKnownRemoteDevice)
knownDevices = m_btIf.ReadKnownDevicesFromRegistry();
else
knownDevices = m_btIf.GetKnownRemoteDeviceEntries();
//
List_IBluetoothDeviceInfo mergedDevices = BluetoothClient.DiscoverDevicesMerge(
args.authenticated, args.remembered, args.unknown, knownDevices, discoverableDevices,
args.discoverableOnly, args.discoTime);
return mergedDevices.ToArray();
}
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.");
}
}
}
public int InquiryAccessCode
{
get { throw new NotSupportedException(); }
set { throw new NotSupportedException(); }
}
//--------------------------------------------------------------
public System.Net.Sockets.Socket Client
{
get { throw new NotSupportedException("The Widcomm stack does not use Sockets."); }
set { throw new NotSupportedException("The Widcomm stack does not use Sockets."); }
}
//--------------------------------------------------------------
public bool Authenticate
{
get { return false; }
set { throw new NotImplementedException("The method or operation is not implemented."); }
}
public bool Encrypt
{
get { return false; }
set { throw new NotImplementedException("The method or operation is not implemented."); }
}
public BluetoothEndPoint RemoteEndPoint
{
get
{
if (m_remoteEndPoint == null) {
BluetoothAddress addr = m_conn.RemoteAddress;
Debug.Assert(addr != null, "port.IsConnected should have returned the remote address!");
// Don't know the remote port unfortunately so just use 0/-1.
if (_remotePort.HasValue)
m_remoteEndPoint = new BluetoothEndPoint(addr, BluetoothService.Empty, _remotePort.Value);
else
m_remoteEndPoint = new BluetoothEndPoint(addr, BluetoothService.Empty);
}
return m_remoteEndPoint;
}
}
public string GetRemoteMachineName(BluetoothAddress device)
{
// This is what we do on Win32. Good enough??
// Is there a Widcomm function to do the name lookup?
// Leave it internal to WidcommBluetoothDeviceInfo anyway!
IBluetoothDeviceInfo bdi
= WidcommBluetoothDeviceInfo.CreateFromGivenAddress(device, m_factory);
return bdi.DeviceName;
}
public Guid LinkKey
{
get { throw new NotImplementedException("The method or operation is not implemented."); }
}
public LinkPolicy LinkPolicy
{
get { throw new NotImplementedException("The method or operation is not implemented."); }
}
public string RemoteMachineName
{
get { return GetRemoteMachineName(RemoteEndPoint.Address); }
}
public void SetPin(string pin)
{
m_passcode = pin;
}
public void SetPin(BluetoothAddress device, string pin)
{
throw new NotImplementedException("Use this.SetPin or BluetoothSecurity.PairRequest...");
}
}
}
|