Files

1285 lines
55 KiB
C#

// 32feet.NET - Personal Area Networking for .NET
//
// Copyright (c) 2008-2013 In The Hand Ltd, All rights reserved.
// Copyright (c) 2008-2013 Alan J. McFarlane, All rights reserved.
// This source code is licensed under the MIT License
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Net.Sockets;
using System.Threading;
using InTheHand.Net;
#if NETCF
using InTheHand.Net.Sockets;
#endif
//
using WriteAsyncResult = InTheHand.Net.AsyncNoResult<InTheHand.Net.Bluetooth.Factory.CommonRfcommStream.BeginReadParameters>;
using ReadAsyncResult = InTheHand.Net.AsyncResult<int, InTheHand.Net.Bluetooth.Factory.CommonRfcommStream.BeginReadParameters>;
namespace InTheHand.Net.Bluetooth.Factory
{
/// <exclude/>
/// <summary>
///
/// </summary>
/// -
/// <remarks>
/// <para>Sub-class must call various methods at the following events:
/// <list type="bullet">
/// <item><term>open</term>
/// <description><see cref="HandleCONNECTED"/>
/// or <see cref="HandleCONNECT_ERR"/> on failure</description>
/// </item>
/// <item><term>close</term>
/// <description><see cref="HandleCONNECT_ERR"/></description>
/// </item>
/// <item><term>data arrival</term>
/// <description><see cref="HandlePortReceive"/></description>
/// </item>
/// <item><term>flow control off</term>
/// <description><see cref="FreePendingWrites"/></description>
/// </item>
/// </list>
/// </para>
/// </remarks>
[CLSCompliant(false)] // Due to UInt16 params in: DoWrite(byte[] p_data, UInt16 len_to_write, out UInt16 p_len_written);
public abstract class CommonRfcommStream : Stream
{
// !!!
#pragma warning disable 1591 // Missing XML comment for publicly visible type or member '
const string ObjectDisposedException_ObjectName = "Network";
internal const string WrappingIOExceptionMessage = "IOError on socket.";
//
protected enum State
{
New,
//Connecting,
Connected,
PeerDidClose,
Closed
};
protected State m_state_;
// Socket.get_Connected reports false only after a (failed) user IO operation,
// so we should do the same. So m_state reports the live state, and this
// property reports the user-visible connected state.
bool m_connected;
protected/*TODO?*/ object _lockKey = new object();
BluetoothAddress m_remoteAddress;
// Connect
BluetoothAddress m_addressToConnect;
int m_ocScn;
AsyncResultNoResult m_arConnect, m_arConnectCompleted;
// Receive
int m_receivedDataFirstBlockOffset;
Queue<byte[]> m_receivedData = new Queue<byte[]>();
int m_amountInReadBuffers;
Queue<ReadAsyncResult> m_arReceiveList = new Queue<AsyncResult<int, BeginReadParameters>>();
// Write
Queue<WriteAsyncResult> m_arWriteQueue = new Queue<WriteAsyncResult>();
ManualResetEvent m_writeEmptied;
//
#if DEBUG && ! PocketPC
string m_creationStackTrace;
#endif
//--------
protected CommonRfcommStream()
{
#if DEBUG
#if TRACE_TO_FILE
//m_sbLogging = new StringBuilder();
//StringWriter wtr = new StringWriter(m_sbLogging, System.Globalization.CultureInfo.InvariantCulture);
TextWriter wtr = File.CreateText("RfcommStream.txt");
m_logWtr = TextWriter.Synchronized(wtr);
#else
m_log = new string[LogNumberOfLines];
#endif
#endif
#if DEBUG && ! PocketPC
m_creationStackTrace = Environment.StackTrace;
#endif
}
//----------------
#if DEBUG
#if TRACE_TO_FILE
readonly StringBuilder m_sbLogging;
readonly TextWriter m_logWtr;
#else
const int LogNumberOfLines = 64;
string[] m_log;
int m_logIdx;
#endif
#endif
[Conditional("DEBUG")]
protected void Log(string message)
{
Utils.MiscUtils.Trace_WriteLine(message); // for PC command-line etc
#if DEBUG
#if TRACE_TO_FILE
m_logWtr.WriteLine(message); // for NETCF+debugger
#else
int idx0, idx;
idx = idx0 = Interlocked.Increment(ref m_logIdx);
uint ui = unchecked((uint)idx);
ui %= (uint)m_log.Length;
idx = checked((int)ui);
m_log[idx] = "" + idx0 + "-- " + message; // for NETCF+debugger
#endif
#endif
}
[Conditional("DEBUG")]
protected void LogFormat(string format, params object[] args)
{
Log(string.Format(System.Globalization.CultureInfo.InvariantCulture,
format, args));
}
protected virtual void ResetDebugId()
{
}
internal protected virtual string DebugId { get { return "NULL_DEBUGID"; } }
//----------------
private LingerOption m_lingerState = new LingerOption(true, 0);
internal LingerOption LingerState
{
get { return m_lingerState; }
set
{
if (!value.Enabled)
throw new ArgumentException("No support for non-Linger mode in Widcomm wrapper.");
m_lingerState = value;
}
}
//----------------
/// <summary>
/// Fails if state is not Connected.
/// </summary>
private void EnsureOpenForWrite()
{
EnsureOpenForRead();
if (m_state_ == State.Connected)
return;
//
if (m_state_ == State.PeerDidClose) { // Extra negative condition to EnsureOpenForRead
SetAsDisconnected();
throw new IOException(WrappingIOExceptionMessage,
FooSocketExceptions.ConnectionIsPeerClosed());
}
Debug.Fail("Unknown state: " + m_state_);
throw new InvalidOperationException("Not connected.");
}
/// <summary>
/// Fails if state is not Connected or PeerDidClose.
/// </summary>
private void EnsureOpenForRead()
{
if (m_state_ == State.Closed)
throw new IOException(WrappingIOExceptionMessage,
new ObjectDisposedException(ObjectDisposedException_ObjectName));
if (m_state_ == State.New)
throw new InvalidOperationException("Not connected.");
if (!m_connected)
throw new IOException(WrappingIOExceptionMessage,
FooSocketExceptions.ConnectionIsPeerClosed());
if (m_state_ == State.Connected || m_state_ == State.PeerDidClose)
return;
//
Debug.Fail("Unknown state: " + m_state_);
throw new InvalidOperationException("Not connected.");
}
/// <summary>
/// Used by Client, note from MSDN Socket.Connected:
/// "Gets a value that indicates whether a Socket is connected to a remote host as of the last Send or Receive operation."
/// </summary>
/// -
/// <remarks>
/// <para>From MSDN <see cref="P:System.Net.Sockets.Socket.Connected"/>:
/// "Gets a value that indicates whether a Socket is connected to a remote host as of the last Send or Receive operation."
/// From MSDN <see cref="P:System.Net.Sockets.TcpClient.Connected"/>:
/// "true if the Client socket was connected to a remote resource as of the most recent operation; otherwise, false."
/// </para>
/// </remarks>
internal bool Connected
{
get { return m_connected; }
}
internal bool LiveConnected
{
get { return m_state_ == State.Connected; }
}
void SetAsDisconnected()
{
Debug.Assert(m_connected, "already not m_connected");
SetAsDisconnectedFromDisposal();
}
void SetAsDisconnectedFromDisposal()
{
Debug.Assert(m_state_ == State.Closed || m_state_ == State.PeerDidClose, "m_state open!");
m_connected = false;
}
private void ImplicitPeerClose()
{
m_state_ = State.PeerDidClose;
}
protected override void Dispose(bool disposing)
{
//LogFormat("Dispose({0})", disposing);
if (disposing) {
/*DEBUG*/
} else {
/*DEBUG*/
}
//
State m_prevState = m_state_;
ReadAsyncResult[] readsToAbort;
WriteAsyncResult[] writesToAbort;
AsyncResultNoResult connectToAbort = null;
Exception exToThrow = null;
bool dbgExpectEmptyWriteQueue = false;
try {
if (m_state_ != State.Closed) {
try {
m_state_ = State.Closed;
SetAsDisconnectedFromDisposal();
DisposeLinger(disposing, out exToThrow, out dbgExpectEmptyWriteQueue);
lock (_lockKey) {
//Log("Dispose: in lock");
if (m_prevState == State.PeerDidClose) { // Don't know whether its ok to call twice...
// COVERAGE
}
//Debug.WriteLine(DateTime.Now.TimeOfDay.ToString()
// + ": RemovePort from Close (state: " + m_state_ + ")");
RemovePortRecords();
DoPortClose(disposing);
ClearAllReadRequests_inLock(out readsToAbort);
ClearAllWriteRequests_inLock(out writesToAbort);
if (m_arConnect != null) {
connectToAbort = m_arConnect;
m_arConnectCompleted = m_arConnect;
m_arConnect = null;
}
ManualResetEvent wee = m_writeEmptied;
if (disposing && wee != null) {
m_writeEmptied = null;
wee.Close();
}
}//lock
// Do we need to abort these when being Finalized? XXXXXXXXXXXXXXXXXX
// TODO SetAsC on same thread (only on user/finalizer thread though).
AbortReads_WithEof(readsToAbort);
AbortWrites_AsPeerClose(writesToAbort);
if (connectToAbort != null) {
// (Leave this one as callback on this thread, its not a stack thread).
connectToAbort.SetAsCompleted(new ObjectDisposedException(
ObjectDisposedException_ObjectName), false);
}
} finally {
DoOtherPreDestroy(disposing);
//
// Could there be a race of an event arriving just as we call Close?
// And thus the callback called when the object are being deleted.
// So do we need to delay etc before calling this??
DoPortDestroy(disposing);
}
}
} finally {
base.Dispose(disposing);
#if DEBUG
#if TRACE_TO_FILE
m_logWtr.Flush(); // let it just finalize -- in case we write after this
#endif
#endif
}
Debug.Assert(!dbgExpectEmptyWriteQueue || m_arWriteQueue.Count == 0,
"Told to expect no remaining Send data, but was some...");
if (exToThrow != null) {
throw exToThrow;
}
}
/// <summary>
/// <see cref="DoPortClose"/>
/// </summary>
protected abstract void RemovePortRecords();
/// <summary>
/// <see cref="DoPortClose"/>
/// </summary>
/// <param name="disposing">Disposing</param>
protected abstract void DoOtherPreDestroy(bool disposing);
/// <summary>
/// Called from CloseInternal and Dispose;
/// RemovePortRecords is called before from both places.
/// Dispose then calls DoOtherPreDestroy and DoPortDestroy in that order.
/// </summary>
/// <param name="disposing">Disposing</param>
protected abstract void DoPortClose(bool disposing);
/// <summary>
/// <see cref="DoPortClose"/>
/// </summary>
/// <param name="disposing">Disposing</param>
protected abstract void DoPortDestroy(bool disposing);
//----
private void DisposeLinger(bool disposing, out Exception exToThrow, out bool dbgExpectEmptyWriteQueue)
{
if (disposing) {
if (!LingerState.Enabled) {
throw new NotSupportedException("Background non-linger Close not supported.");
} else {
if (LingerState.LingerTime == 0) {
// Hard close, so just proceed and abort the writes.
dbgExpectEmptyWriteQueue = true;
exToThrow = null;
} else {
// Wait for the linger-time to see if the writes are processed.
lock (_lockKey) {
if (m_arWriteQueue.Count == 0) {
// NOP
} else {
m_writeEmptied = new ManualResetEvent(false);
}
}
if (m_writeEmptied == null) {
Debug.Assert(m_arWriteQueue.Count == 0);
exToThrow = null;
dbgExpectEmptyWriteQueue = true;
} else {
const int MillisPerSeconds = 1000;
int msTime = LingerState.LingerTime * MillisPerSeconds;
bool emptied = m_writeEmptied.WaitOne(msTime, false);
if (emptied) {
Debug.Assert(m_arWriteQueue.Count == 0);
exToThrow = null;
dbgExpectEmptyWriteQueue = true;
} else {
Debug.Assert(m_arWriteQueue.Count != 0);
exToThrow = new Exception("Linger time-out FIXME");
dbgExpectEmptyWriteQueue = false;
}
}
}
}
} else {
exToThrow = null;
dbgExpectEmptyWriteQueue = false;
}
}
~CommonRfcommStream()
{
Dispose(false);
}
private static void MemoryBarrier()
{
#if ! PocketPC
Thread.MemoryBarrier();
#endif
}
protected virtual void VerifyPortIsInRange(BluetoothEndPoint bep)
{
VerifyRfcommScnIsInRange(bep);
}
protected static void VerifyRfcommScnIsInRange(BluetoothEndPoint bep)
{
if (bep.Port < BluetoothEndPoint.MinScn || bep.Port > BluetoothEndPoint.MaxScn)
throw new ArgumentOutOfRangeException("bep", "Channel Number must be in the range 1 to 30.");
}
protected static void VerifyL2CapPsmIsInRange(BluetoothEndPoint bep)
{
var psm = checked((ushort)bep.Port);
}
protected internal IAsyncResult BeginConnect(BluetoothEndPoint bep, //string pin,
AsyncCallback asyncCallback, Object state)
{
if (bep.Port == 0 || bep.Port == -1)
throw new ArgumentException("Channel Number must be set in the BluetoothEndPoint, i.e. SDP lookup done.", "bep");
VerifyPortIsInRange(bep);
int scn = bep.Port;
//
lock (_lockKey) {
if (m_state_ == State.Closed)
throw new ObjectDisposedException(ObjectDisposedException_ObjectName);
if (m_state_ != State.New) {
throw new SocketException((int)SocketError.IsConnected); // [Begin]Connect called twice
}
if (m_arConnect != null)
throw new InvalidOperationException("Another Connect operation is already in progress.");
Debug.Assert(m_arConnectCompleted == null);
AsyncResultNoResult ar = new AsyncResultNoResult(asyncCallback, state);
//
DoOtherSetup(bep, scn);
// Initiate the connect attempt, and then wait for the CONNECTED Event.
m_ocScn = scn;
m_addressToConnect = bep.Address;
DoOpenClient(m_ocScn, m_addressToConnect);
AddPortRecords();
Debug.Assert(ar != null);
m_arConnect = ar;
return ar;
}
}
/// <summary>
/// Called before DoOpenClient.
/// For instance is empty on BTPS, on Widcomm it calls SetScnForPeerServer and SetSecurityLevelClient.
/// </summary>
/// <param name="bep">Endpoint</param>
/// <param name="scn">Channel number</param>
protected abstract void DoOtherSetup(BluetoothEndPoint bep, int scn);
protected abstract void AddPortRecords();
/// <summary>
/// Starts the connect process. The async completion should call
/// either <see cref="HandleCONNECTED"/> or <see cref="HandleCONNECT_ERR"/>.
/// </summary>
/// <param name="scn">scn</param>
/// <param name="addressToConnect">addr</param>
protected abstract void DoOpenClient(int scn, BluetoothAddress addressToConnect);
internal void EndConnect(IAsyncResult ar)
{
// (Can't lock here as that would block the callback methods).
if (ar != m_arConnect && ar != m_arConnectCompleted)
throw new InvalidOperationException("Unknown IAsyncResult.");
try {
((AsyncResultNoResult)ar).EndInvoke();
} finally {
MemoryBarrier();
Debug.Assert(m_arConnect == null);
m_arConnectCompleted = null;
}
}
//----
internal IAsyncResult BeginAccept(BluetoothEndPoint bep, string serviceName, AsyncCallback asyncCallback, Object state)
{
if (bep == null)
throw new ArgumentNullException("bep");
if (bep.Port == 0 || bep.Port == -1)
throw new ArgumentException("Channel Number must be set in the BluetoothEndPoint, i.e. SDP lookup done.", "bep");
VerifyPortIsInRange(bep);
int scn = bep.Port;
//
lock (_lockKey) {
if (m_state_ == State.Closed)
throw new ObjectDisposedException(ObjectDisposedException_ObjectName);
if (m_state_ != State.New) {
throw new SocketException((int)SocketError.IsConnected); // [Begin]Connect/Accept called twice
}
//
if (m_arConnect != null)
throw new InvalidOperationException("Another Connect operation is already in progress.");
Debug.Assert(m_arConnectCompleted == null);
AsyncResultNoResult ar = new AsyncResultNoResult(asyncCallback, state);
//
// Initiate the connect attempt, and then wait for the CONNECTED Event.
DoOpenServer(scn);
AddPortRecords();
Debug.Assert(ar != null);
m_arConnect = ar;
return ar;
}
}
protected abstract void DoOpenServer(int scn);
internal void EndAccept(IAsyncResult ar)
{
// (Can't lock here as that would block the callback methods).
if (ar != m_arConnect && ar != m_arConnectCompleted)
throw new InvalidOperationException("Unknown IAsyncResult.");
try {
((AsyncResultNoResult)ar).EndInvoke();
} finally {
MemoryBarrier();
Debug.Assert(m_arConnect == null);
m_arConnectCompleted = null;
}
}
//----------------
internal BluetoothAddress RemoteAddress { get { return m_remoteAddress; } }
//----------------
public override bool CanRead { get { return Connected; } }
public override bool CanWrite { get { return Connected; } }
public override bool CanSeek { get { return false; } }
//----
/// <summary>
/// Call when connection is successfully made.
/// </summary>
/// <param name="eventId">Used for logging etc. Pass a string
/// containing the name of the stack's event/status that occurred.
/// </param>
[SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
protected void HandleCONNECTED(string eventId)
{
AsyncResultNoResult sacAr; // Call SetAsCompleted outside the lock.
lock (_lockKey) {
Debug.WriteLine(string.Format(System.Globalization.CultureInfo.InvariantCulture,
"CONNECTED {0}; m_state: {1}; m_arConnect {2}, IsCompleted: {3}.",
DebugId, m_state_,
(m_arConnect == null) ? "(null)" : "(set)",
(m_arConnect == null) ? "n/a" : m_arConnect.IsCompleted.ToString()));
if (m_arConnect != null && !m_arConnect.IsCompleted) {
// Success!
m_state_ = State.Connected;
m_connected = true;
sacAr = m_arConnect;
m_arConnectCompleted = m_arConnect;
m_arConnect = null;
// Get the remote address
try {
bool connected = DoIsConnected(out m_remoteAddress);
Debug.Assert(connected || TestUtilities.IsUnderTestHarness(), "!CRfCommPort.IsConnected");
} catch (Exception ex) {
Debug.Assert(TestUtilities.IsUnderTestHarness(), DebugId + ": " + "m_port.IsConnected exception: " + ex);
}
} else {
Debug.Assert(m_state_ == State.Connected
|| m_state_ == State.Closed // Consumer called Close soon after connect.
,
DebugId + ": " +
eventId + ": m_state wanted: " + "Connected" + ", but was: " + m_state_
+ ((m_arConnect == null) ? " ar==(null)" : " ar==(non-null)")
);
sacAr = null;
}
}//lock
if (sacAr != null)
sacAr.SetAsCompleted(null, AsyncResultCompletion.MakeAsync);
}
/// <summary>
/// Get the remote address.
/// </summary>
/// -
/// <param name="p_remote_bdaddr">On return contains the address to which we are connected.
/// </param>
/// -
/// <returns><see langword="true"/> if connected, but we ignore the result.
/// </returns>
protected virtual bool DoIsConnected(out BluetoothAddress p_remote_bdaddr)
{
if (m_addressToConnect != null) {
p_remote_bdaddr = m_addressToConnect;
} else {
Debug.Assert(TestUtilities.IsUnderTestHarness(), "Don't know remote address! Probably because we're used by BtLsnr...");
p_remote_bdaddr = BluetoothAddress.None;
}
return true;
}
/// <summary>
/// Call when connection is un-successfully made (fails),
/// and also when the connection closes.
/// </summary>
/// <param name="eventId">Used for logging etc. Pass a string
/// containing the name of the stack's event/status that occurred.
/// </param>
/// <param name="socketErrorCode">The socket error code for this failure
/// -- known.
/// Pass for instance a value from <see cref="T:System.Net.Sockets.SocketError"/>
/// as an <see cref="T:System.Int32"/>;
/// or <see langword="null"/> respectively.
/// </param>
protected void HandleCONNECT_ERR(string eventId, int? socketErrorCode)
{
AsyncResultNoResult sacAr; // Call SetAsCompleted outside the lock.
Exception sacEx;
ReadAsyncResult[] allRead = null;
WriteAsyncResult[] allWrite = null;
lock (_lockKey) {
Debug.WriteLine(string.Format(System.Globalization.CultureInfo.InvariantCulture,
"CONNECT_ERR {0}, m_state: {1}, m_arConnect {2}",
DebugId, m_state_,
(m_arConnect == null) ? "(null)" : "(set)"));
if (m_arConnect != null) {
Debug.Assert(m_state_ == State.New, eventId + ": m_state wanted: " + "New" + ", but was: " + m_state_);
Utils.MiscUtils.Trace_WriteLine("HandlePortEvent: connect failed.");
// It **could** be that the peer needs authentication/bonding,
// unfortunately there's no specific error returned in the CONNECT_ERR
// event, so we'll try a Bond and try OpenClient again if there's
// a Passcode provided. Three states are possible:
// 1. No Passcode, so we didn't retry.
// 2. Something failed, return that error to the consumer.
// 3. We are retrying, so expect CONNECT/CONNECT_ERR.
Exception bondEx;
Debug.Assert(m_arConnect != null);
Debug.Assert(!m_arConnect.IsCompleted, "!m_arConnect.IsCompleted");
bool retrying = TryBondingIf_inLock(m_addressToConnect, m_ocScn, out bondEx);
if (retrying) {
Debug.Assert(bondEx == null);
sacAr = null;
sacEx = null;
} else if (bondEx != null) {
// Report the new failure.
sacAr = m_arConnect;
sacEx = bondEx;
m_arConnectCompleted = m_arConnect;
m_arConnect = null;
} else {
// Report the original failure.
sacAr = m_arConnect;
sacEx = FooSocketExceptions.CreateConnectFailed("PortCONNECT_ERR=" + eventId, socketErrorCode);
m_arConnectCompleted = m_arConnect;
m_arConnect = null;
}
} else if (m_state_ == State.Closed) {
// On Win32, at least, we get CONNECT_ERR on calling Close
//Debug.Fail("Info: here be CONNECT_ERR after calling Close");
sacAr = null;
sacEx = null;
} else {
Debug.Assert(m_state_ == State.Connected
|| m_state_ == State.PeerDidClose, // We've seen CONNECT_ERR occur twice
DebugId + ": " +
eventId + ": m_state wanted: " + "Connected" + ", but was: " + m_state_);
Utils.MiscUtils.Trace_WriteLine("HandlePortEvent: closed when open.");
CloseInternal(out allRead, out allWrite);
sacAr = null;
sacEx = null;
}
}//lock
if (sacAr != null)
sacAr.SetAsCompleted(sacEx, AsyncResultCompletion.MakeAsync);
AbortIf_withPeerCloseAndEof(allRead, allWrite);
}
/// <summary>
/// Used: 1. when we get CONNECT_ERR from the stack, and POSSIBLY 2. when we close the
/// stream to do consumer timeout (SO_RCVTIMEO/etc).
/// </summary>
/// <param name="allRead">Out: to call <see cref="M:InTheHand.Net.Bluetooth.Widcomm.WidcommRfcommStream.AbortIf(System.Collections.Generic.IList{InTheHand.Net.AsyncResult{System.Int32,InTheHand.Net.Bluetooth.Widcomm.WidcommRfcommStream.BeginReadParameters}}, System.Collections.Generic.IList{InTheHand.Net.AsyncNoResult{InTheHand.Net.Bluetooth.Widcomm.WidcommRfcommStream.BeginReadParameters}})"/>
/// on.</param>
/// <param name="allWrite">Out: to call <see cref="M:InTheHand.Net.Bluetooth.Widcomm.WidcommRfcommStream.AbortIf(System.Collections.Generic.IList{InTheHand.Net.AsyncResult{System.Int32,InTheHand.Net.Bluetooth.Widcomm.WidcommRfcommStream.BeginReadParameters}}, System.Collections.Generic.IList{InTheHand.Net.AsyncNoResult{InTheHand.Net.Bluetooth.Widcomm.WidcommRfcommStream.BeginReadParameters}})"/>
/// on.</param>
private void CloseInternal(out ReadAsyncResult[] allRead, out WriteAsyncResult[] allWrite)
{
Debug.WriteLine(DateTime.Now.TimeOfDay.ToString()
+ ": RemovePort from CloseInternal (state: " + m_state_ + ")");
RemovePortRecords();
// For Listener an old port (previously accepted and closed by
// the peer) is re-used to accept a new connection even when other
// ports have been create'd/OpenServer'd, so close immmediately.
if (m_state_ != State.PeerDidClose) { // Don't know whether its ok to call twice...
// i.e. Debug.Assert(m_state_ == State.Connected);
// TODO !! Make this DoPortClose(disposing: true)
// Was mistakenly set to false at first use in r62686.
DoPortClose(false);
}
// No information whether this is a hard or clean close! :-(
m_state_ = State.PeerDidClose;
if (GetPendingReceiveDataLength() == 0) {
// Must signal EoS to release any pending Reads for which no data will arrive.
ClearAllReadRequests_inLock(out allRead);
} else {
allRead = null;
}
ClearAllWriteRequests_inLock(out allWrite);
}
/// <summary>
/// Close the connection from the network/stack side (not from the consumer side).
/// </summary>
/// -
/// <remarks>
/// <para>When we call Close the object is disposed and outstanding and
/// new operations fail with ObjectDisposedException. This method
/// instead closes the connection from the network/stack side and thus
/// operations fail with an IO error etc.
/// </para>
/// </remarks>
[SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes",
Justification = "Aborting, don't want errors.")]
internal void CloseInternalAndAbort_willLock()
{
ReadAsyncResult[] allRead; WriteAsyncResult[] allWrite;
AsyncResultNoResult connectToAbort = null;
lock (_lockKey) {
CloseInternal(out allRead, out allWrite);
if (m_arConnect != null) {
connectToAbort = m_arConnect;
m_arConnectCompleted = m_arConnect;
m_arConnect = null;
}
}
AbortIf_withPeerCloseAndEof(allRead, allWrite);
if (connectToAbort != null) {
//const int SocketError_Interrupted = 10004;
//const int SocketError_NetworkDown = 10050;
try {
connectToAbort.SetAsCompleted(new SocketException((int)SocketError.NetworkDown),
AsyncResultCompletion.MakeAsync);
} catch (Exception ex) {
Utils.MiscUtils.Trace_WriteLine("Port CloseInternal, SaC SocketError_NetworkDown ex: " + ex);
Debug.Fail("Port CloseInternal, SaC SocketError_NetworkDown ex: " + ex);
}
}//if
}
/// <summary>
/// DEPRECATED, should return false.
/// </summary>
/// <returns>Whether Bonding was attempted and thus the connect should be retried.
/// </returns>
protected abstract bool TryBondingIf_inLock(BluetoothAddress addressToConnect,
int ocScn, out Exception err);
//--------------------------------------------------------------
void ClearAllReadRequests_inLock(out ReadAsyncResult[] all)
{
all = m_arReceiveList.ToArray();
m_arReceiveList.Clear();
}
[SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes",
Justification = "Aborting, don't want errors.")]
void AbortReads_WithEof(IList<ReadAsyncResult> all)
{
foreach (ReadAsyncResult ar in all) {
try {
if (!ar.IsCompleted)
ar.SetAsCompleted(0, AsyncResultCompletion.MakeAsync);
} catch {
}
}
}
void ClearAllRequests_inLock(out ReadAsyncResult[] allRead, out WriteAsyncResult[] allWrite)
{
ClearAllReadRequests_inLock(out allRead);
ClearAllWriteRequests_inLock(out allWrite);
}
void AbortIf_withPeerCloseAndEof(IList<ReadAsyncResult> allRead, IList<WriteAsyncResult> allWrite)
{
if (allRead != null)
AbortReads_WithEof(allRead);
if (allWrite != null)
AbortWrites_AsPeerClose(allWrite);
}
//--------------------------------------------------------------
protected void HandlePortReceive(byte[] buffer)
{
lock (_lockKey) {
// Put it on the queue.
m_receivedData.Enqueue(buffer);
System.Diagnostics.Debug.Assert(m_amountInReadBuffers >= 0);
m_amountInReadBuffers += buffer.Length;
// TODO Check high-water mark and set SetFlowControl.
}//lock
// Free any waiting reads.
int readLen;
AsyncResult<int, BeginReadParameters> ar; // Call SetAsCompleted outside the lock.
while (true) {
lock (_lockKey) {
if (m_arReceiveList.Count == 0 || GetPendingReceiveDataLength() == 0)
break;
ar = m_arReceiveList.Dequeue();
BeginReadParameters args = ar.BeginParameters;
readLen = ReturnSomeReceivedData_MustBeInLock(args.buffer, args.offset, args.count);
}//lock
ar.SetAsCompleted(readLen, AsyncResultCompletion.MakeAsync);
}//while
}
private int GetPendingReceiveDataLength()
{
if (m_receivedData.Count == 0)
return 0;
else {
int firstLen, firstOffset;
FirstReceivedBlockInfo_(out firstLen, out firstOffset);
return firstLen;
}
}
private int ReturnSomeReceivedData_MustBeInLock(byte[] buffer, int offset, int count)
{
// Use the data; returning as much of the first block that will fit.
// Possible TO-DO: Or we could return data from many of the blocks to fill the buffer.
int firstLen, firstOffset;
FirstReceivedBlockInfo_(out firstLen, out firstOffset);
int returnedLen;
if (firstLen <= count) { // Use all of the first block and remove it.
byte[] data = m_receivedData.Dequeue();
Array.Copy(data, m_receivedDataFirstBlockOffset, buffer, offset, firstLen);
m_receivedDataFirstBlockOffset = 0;
Debug.Assert(firstLen != 0, "Mustn't return 0 except at closed.");
returnedLen = firstLen;
} else { // Use some of the first block.
byte[] data = m_receivedData.Peek();
int lenToRead = Math.Min(count, firstLen);
Array.Copy(data, firstOffset, buffer, offset, lenToRead);
m_receivedDataFirstBlockOffset += lenToRead;
Debug.Assert(m_receivedDataFirstBlockOffset <= data.Length, "Should not leave an empty block.");
Debug.Assert(lenToRead != 0 || count == 0, "Mustn't return 0 except at closed, or when requested.");
returnedLen = lenToRead;
}
m_amountInReadBuffers -= returnedLen;
System.Diagnostics.Debug.Assert(m_amountInReadBuffers >= 0, "m_amountInReadBuffers: " + m_amountInReadBuffers);
// TODO Check low-water mark and clear SetFlowControl
return returnedLen;
}
private void FirstReceivedBlockInfo_(out int length, out int offset)
{
Debug.Assert(m_receivedData.Count != 0);
byte[] data = m_receivedData.Peek();
offset = m_receivedDataFirstBlockOffset;
length = data.Length - offset;
Debug.Assert(length != 0, "Found empty block on receive list.");
}
public override int Read(byte[] buffer, int offset, int count)
{
// TODO Add parameter checking
EnsureOpenForRead();
IAsyncResult ar;
lock (_lockKey) {
if (m_receivedData.Count != 0) {
return ReturnSomeReceivedData_MustBeInLock(buffer, offset, count);
} else {
// No pending data, due to closed? Otherwise wait.
if (m_state_ == State.PeerDidClose)
return 0;
ar = this.BeginRead(buffer, offset, count, null, null);
// We must exit the lock to allow new data to arrive!
}
}//lock
Debug.Assert(ar != null, "Should be here only after calling BeginRead");
if (!IsInfiniteTimeout(_readTimeout)) {
ApplyTimeout(ar, _readTimeout, m_arReceiveList);
}
return this.EndRead(ar);
}
public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback callback, object state)
{
// TODO Add parameter checking
EnsureOpenForRead();
AsyncResult<int, BeginReadParameters> sacAr; // Call SetAsCompleted outside the lock.
int readLen;
lock (_lockKey) {
BeginReadParameters args = new BeginReadParameters(buffer, offset, count);
AsyncResult<int, BeginReadParameters> ar
= new AsyncResult<int, BeginReadParameters>(callback, state, args);
if (m_receivedData.Count == 0) {
switch (m_state_) {
case State.Connected:
// Wait for some more data then.
m_arReceiveList.Enqueue(ar);
return ar;
default: // Expect only "PeerDidClose", others blocked by EnsureOpen.
Debug.Assert(m_state_ == State.PeerDidClose, "Unexpected State: " + m_state_);
// So, we're at EoF -> Completed Synchronously
sacAr = ar;
readLen = 0;
break;
}
} else {
// Data available -> Completed Synchronously
readLen = ReturnSomeReceivedData_MustBeInLock(args.buffer, args.offset, args.count);
sacAr = ar;
}
}//lock
Debug.Assert(sacAr != null, "Only get here when want to call SetAsCompleted!");
sacAr.SetAsCompleted(readLen, true);
return sacAr;
}
public override int EndRead(IAsyncResult asyncResult)
{
// (Can't lock here as that would block the callback methods).
AsyncResult<int, BeginReadParameters> ar
= (AsyncResult<int, BeginReadParameters>)asyncResult;
int readLen = ar.EndInvoke();
Debug.Assert(!m_arReceiveList.Contains(ar), "Should have cleaned up outstanding IAsyncResult list");
return readLen;
}
internal sealed class BeginReadParameters
{
//Unused: public readonly int startTicks = Environment.TickCount;
public byte[] buffer;
public int offset;
public int count;
public BeginReadParameters(byte[] buffer, int offset, int count)
{
this.buffer = buffer;
this.offset = offset;
this.count = count;
}
}
public virtual bool DataAvailable { get { return AmountInReadBuffers > 0; } }
internal int AmountInReadBuffers
{
get
{
lock (_lockKey) {
System.Diagnostics.Debug.Assert(m_amountInReadBuffers >= 0);
return m_amountInReadBuffers;
}
}
}
//--------
int _readTimeout = Timeout.Infinite;
int _writeTimeout = Timeout.Infinite;
public override bool CanTimeout { get { return true; } }
public override int ReadTimeout
{
get { return _readTimeout; }
set { _readTimeout = value; }
}
public override int WriteTimeout
{
get { return _writeTimeout; }
set { _writeTimeout = value; }
}
internal static bool IsInfiniteTimeout(int timeout)
{
if (timeout == Timeout.Infinite)
return true;
if (timeout == 0)
return true;
return false;
}
private void ApplyTimeout<TAsyncResult>(IAsyncResult ar, int timeout,
Queue<TAsyncResult> queue)
where TAsyncResult : AsyncResultNoResult
{
Debug.Assert(timeout != Timeout.Infinite, "Called with invalid timeout (-1).");
Debug.Assert(timeout != 0, "Called with invalid timeout (0).");
TAsyncResult sacAr = null;
ReadAsyncResult[] allRead = null;
WriteAsyncResult[] allWrite = null;
int watchdog = 0;
while (true) { // See comment in loop-else-else
Debug.Assert(sacAr == null);
bool signalled = ar.AsyncWaitHandle.WaitOne(timeout, false);
if (signalled) {
break;
} else {
lock (_lockKey) {
if (queue.Count == 0) {
// The operation timed-out but completed before we
// got inside the lock...
// Either there were two (or more) operations in the
// queue and they timed out together, so the first
// one aborted all of them.
// Or possibly one timing-out operation completed
// successfully just *after* we timed it out, but
// just *before* we entered the lock.
Debug.WriteLine("Queue empty in ApplyTimeout (isc: " + ar.IsCompleted + ")");
//A bit racy--Debug.Assert(ar.IsCompleted, "NOT ar.IsCompleted but queue empty!");
sacAr = null;
break;
} else {
TAsyncResult peekFirstAr = queue.Peek();
if (peekFirstAr == ar) {
sacAr = queue.Dequeue();
Debug.Assert(sacAr == ar, "How did a different AR get to the front of the queue?!?");
CloseInternal(out allRead, out allWrite);
break;
} else {
// There are waiting Reads in the queue ahead of us
// and we can't remove ours from behind them, so we
// just have to wait some more...
sacAr = null;
}
}
}//lock
}
++watchdog;
//if (watchdog > 10) {
// Debug.Fail("Looping in DoTimeout!");
// break;
//}
}//while
if (sacAr != null) { // Set completion with error.
const int WSAETIMEDOUT = 10060;
Exception ex0 = new SocketException(WSAETIMEDOUT);
sacAr.SetAsCompleted(new IOException(ex0.Message, ex0), true);
AbortIf_withPeerCloseAndEof(allRead, allWrite);
} else {
Debug.Assert(allRead == null, "inconsistent allRead");
Debug.Assert(allWrite == null, "inconsistent allWrite");
}
}
//--------------------------------------------------------------
[SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
protected void FreePendingWrites()
{
// Release the async operations outside the lock.
Queue<WriteAsyncResult> sacArQueue = new Queue<WriteAsyncResult>();
WriteAsyncResult sacErroredLast = null;
Exception sacEx = null;
lock (_lockKey) {
int loops = 0;
while (m_arWriteQueue.Count != 0) {
++loops;
WriteAsyncResult arPeek = m_arWriteQueue.Peek();
BeginReadParameters work = arPeek.BeginParameters;
int lenWritten;
try {
lenWritten = PortWrite(work.buffer, work.offset, work.count);
} catch (Exception ex) {
if (!TestUtilities.IsUnderTestHarness())
Debug.Fail("Write failed at FPW -- Not seen (on Widcomm) in reality");
ImplicitPeerClose();
SetAsDisconnected();
WriteAsyncResult ar = m_arWriteQueue.Dequeue();
sacErroredLast = ar;
sacEx = ex;
break;
}
if (lenWritten == work.count) {
WriteAsyncResult ar = m_arWriteQueue.Dequeue();
sacArQueue.Enqueue(ar);
} else {
Debug.Assert(lenWritten < work.count, "lenWritten: " + lenWritten + ", work.count: " + work.count);
work.count -= lenWritten;
work.offset += lenWritten;
break; // Need to wait for the next event!
}
}//while
if (m_arWriteQueue.Count == 0 && m_writeEmptied != null) {
m_writeEmptied.Set();
}
//
if (loops == 0) {
// DEBUG test-coverage
} else if (loops == 1) {
// DEBUG test-coverage
} else {
// DEBUG test-coverage
}
}//lock
while (sacArQueue.Count != 0) {
WriteAsyncResult ar = sacArQueue.Dequeue();
ar.SetAsCompleted(null, AsyncResultCompletion.MakeAsync);
}
Debug.Assert((sacErroredLast == null) == (sacEx == null));
if (sacErroredLast != null) {
sacErroredLast.SetAsCompleted(sacEx, AsyncResultCompletion.MakeAsync);
}
}
void ClearAllWriteRequests_inLock(out WriteAsyncResult[] all)
{
all = m_arWriteQueue.ToArray();
m_arWriteQueue.Clear();
}
// TODO (?Use distinct errors for different abort reasons network/timeout/radio off).
void AbortWrites_AsPeerClose(IList<WriteAsyncResult> all)
{
foreach (WriteAsyncResult ar in all) {
ar.SetAsCompleted(new IOException(WrappingIOExceptionMessage,
FooSocketExceptions.ConnectionIsPeerClosed()), AsyncResultCompletion.MakeAsync);
}
}
public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback callback, object state)
{
EnsureOpenForWrite();
WriteAsyncResult sacAr; // SetAsComplete outside the lock.
lock (_lockKey) {
WriteAsyncResult ar;
if (m_arWriteQueue.Count == 0) { // Try to complete the send immediately.
bool success = false;
int lenWritten;
try {
lenWritten = PortWrite(buffer, offset, count);
success = true;
} finally {
if (!success) {
if (!TestUtilities.IsUnderTestHarness())
Debug.Fail("Write failed at BW -- Not seen in reality");
ImplicitPeerClose();
SetAsDisconnected();
}
}
if (lenWritten == count) { // -> CompletedSynchronously
ar = new WriteAsyncResult(callback, state, null);
sacAr = ar;
} else { // Queue the remainder...
BeginReadParameters work = new BeginReadParameters(
buffer, offset + lenWritten, count - lenWritten);
Debug.Assert(work.offset < buffer.Length, "work.offset: " + work.offset + ", buffer.Length: " + buffer.Length);
Debug.Assert(work.count > 0, "work.count: " + work.count);
ar = new WriteAsyncResult(callback, state, work);
m_arWriteQueue.Enqueue(ar);
return ar;
}
} else { //-----------------------
// Queue it all...
ar = new WriteAsyncResult(callback, state,
new BeginReadParameters(buffer, offset, count));
m_arWriteQueue.Enqueue(ar);
return ar;
}
}
// Fall throught from complete write.
sacAr.SetAsCompleted(null, true);
return sacAr;
}
public override void EndWrite(IAsyncResult asyncResult)
{
bool success = false;
try {
WriteAsyncResult ar2 = (WriteAsyncResult)asyncResult;
ar2.EndInvoke();
success = true;
} finally {
if (!success) {
Debug.Assert(!LiveConnected, "EndWrite on !success !LiveConnected already");
Debug.Assert(!LiveConnected || !Connected, "EndWrite on !success !Connected already");
}
}
}
public override void Write(byte[] buffer, int offset, int count)
{
IAsyncResult ar = BeginWrite(buffer, offset, count, null, null);
if (!IsInfiniteTimeout(_writeTimeout)) {
ApplyTimeout(ar, _writeTimeout, m_arWriteQueue);
}
EndWrite(ar);
}
private int PortWrite(byte[] buffer, int offset, int count)
{
int totalLenWritten = 0;
UInt16 lenAttemptedToWrite, lenWritten;
// Try in 16KB chunks
while (count > 0) {
PortWriteMax16kb(buffer, offset, count, out lenAttemptedToWrite, out lenWritten);
Debug.Assert(lenWritten <= lenAttemptedToWrite, "lenWritten<= lenToWrite ("
+ lenWritten + "," + lenAttemptedToWrite + ")");
Debug.Assert(lenWritten >= 0, "NOT +ve lenWritten: " + lenWritten);
count -= lenWritten;
offset += lenWritten;
Debug.Assert(count >= 0, "count: " + count);
Debug.Assert(offset <= buffer.Length, "offset: " + offset + ", buffer.Length: " + buffer.Length);
totalLenWritten += lenWritten;
//
if (lenAttemptedToWrite != lenWritten)
break; // port.Write accepted only part! The rest needs to be queued.
}
return totalLenWritten;
}
private void PortWriteMax16kb(byte[] buffer, int offset, int count, out UInt16 lenAttemptToWrite, out UInt16 lenWritten)
{
lenAttemptToWrite = (UInt16)Math.Min(count, UInt16.MaxValue);
byte[] data;
if (offset == 0 && lenAttemptToWrite == count && buffer.Length == lenAttemptToWrite) {
// perf optimisation
data = buffer;
} else {
data = new byte[lenAttemptToWrite];
Array.Copy(buffer, offset, data, 0, lenAttemptToWrite);
}
//Log("m_port.Write");
DoWrite(data, lenAttemptToWrite, out lenWritten);
//Utils.MiscUtils.Trace_WriteLine("m_port.Write: len in: {0}, len out: {1}", lenAttemptToWrite, lenWritten);
Debug.Assert(lenWritten <= lenAttemptToWrite, "lenWritten<= lenToWrite ("
+ lenWritten + "," + lenAttemptToWrite + ")");
}
protected abstract void DoWrite(byte[] p_data, UInt16 len_to_write, out UInt16 p_len_written);
public override void Flush()
{
// Can we do anything here? We're not a buffered stream so there is
// no need to flush. Any data in the write queue is there due to flow
// control, we can only send it when the stack signals that its ready.
}
//--------
public override long Length { get { throw NewNotSupportedException(); } }
public override long Position
{
get { throw NewNotSupportedException(); }
set { throw NewNotSupportedException(); }
}
public override long Seek(long offset, SeekOrigin origin)
{
throw NewNotSupportedException();
}
public override void SetLength(long value)
{
throw NewNotSupportedException();
}
//--------
private static Exception NewNotSupportedException()
{
// no message, for NETCF
throw new NotSupportedException();
}
}//class
static class FooSocketExceptions
{
internal static Exception ConnectionIsPeerClosed()
{
return CommonSocketExceptions.ConnectionIsPeerClosed();
}
internal static Exception CreateConnectFailed(string p, int? socketErrorCode)
{
return CommonSocketExceptions.CreateConnectFailed(p, socketErrorCode);
}
}
}