/// <summary>
        /// This is the heart of the CII.  This takes data blobs from the backend manager
        /// and interprets it according to the spec.
        /// </summary>
        /// <remarks>
        /// This is called by the backend connected to us.  It needs to know about 
        /// the CiiClient.  This always runs on a worker thread internal to this
        /// library.  A user should never call this.  
        /// </remarks>
        /// <param name="buffer">The byte array where our data lives.</param>
        /// <param name="dataLength">Length valid data in the array.  (dataLength != buffer.Length)</param>
        public void RouteReceivedMessage(byte[] buffer, int dataLength)
        {
            uint sequenceNumber;
            uint statusCode;
            uint subcommand;
            CiiMessageTracker messageTracker;

            uint substatus;

            CiiMessageType type = (CiiMessageType)BitConverter.ToUInt32(buffer, 0);

            switch (type)
            {
                case CiiMessageType.MtAccept:
                    logger.Log("ACCEPT", buffer, dataLength);
                    grantedAccess = (CiiAccessLevel)BitConverter.ToInt32(buffer, 4);
                    loginAcceptEvent.Set();
                    break;

                case CiiMessageType.MtAck:
                    logger.Log("ACK", buffer, dataLength);
                    sequenceNumber = BitConverter.ToUInt32(buffer, 4);

                    messageTracker = messagesInFlight.Retrieve(sequenceNumber);
                    if (messageTracker == null)
                    {
                        SendAsyncError("Protocol Failure - Unexpected ACK");
                        break;
                    }

                    if (messageTracker.AckReceived)
                    {
                        //
                        //  Error!  Double ACK!
                        //
                        messagesInFlight.Delete(sequenceNumber);
                        SendAsyncError("Protocol Failure - Double ACK");
                        break;
                    }
                    else
                    {
                        messageTracker.AckReceived = true;
                    }

                    if ((messageTracker.Completion != null) && (messageTracker.Completion.AckHandler != null))
                    {
                        messageTracker.Completion.AckHandler(
                           messageTracker.Completion.UserData,
                           sequenceNumber);
                    }
                    else
                    {
                        Debug.WriteLine("Discarding ACK for Sequence # " + sequenceNumber);
                    }

                    break;

                case CiiMessageType.MtNak:
                    logger.Log("NAK", buffer, dataLength);
                    sequenceNumber = BitConverter.ToUInt32(buffer, 4);
                    statusCode = BitConverter.ToUInt32(buffer, 8);

                    messageTracker = messagesInFlight.Retrieve(sequenceNumber);
                    if (messageTracker == null)
                    {
                        SendAsyncError("Protocol Failure - Unexpected NAK");
                        break;
                    }

                    messagesInFlight.Delete(sequenceNumber);

                    if (messageTracker.AckReceived)
                    {
                        //
                        //  Error!  ACK / NAK!
                        //
                        SendAsyncError("Protocol Failure - ACK - NAK");
                        break;
                    }

                    if ((messageTracker.Completion != null) && (messageTracker.Completion.NakHandler != null))
                    {
                        messageTracker.Completion.NakHandler(
                           messageTracker.Completion.UserData,
                           sequenceNumber,
                           statusCode);
                    }
                    else
                    {
                        Debug.WriteLine("Discarding NAK for Sequence # " + sequenceNumber);
                    }

                    messageTracker = null;
                    break;

                case CiiMessageType.MtResponse:
                    logger.Log("RSP", buffer, dataLength);
                    sequenceNumber = BitConverter.ToUInt32(buffer, 4);
                    subcommand = BitConverter.ToUInt32(buffer, 8);
                    statusCode = BitConverter.ToUInt32(buffer, 12);

                    messageTracker = messagesInFlight.Retrieve(sequenceNumber);
                    if (messageTracker == null)
                    {
                        SendAsyncError("Protocol Failure - Unexpected RSP");
                        break;
                    }

                    messagesInFlight.Delete(sequenceNumber);

                    if (!messageTracker.AckReceived)
                    {
                        //
                        //  Error!  No ACK!
                        //
                        SendAsyncError("Protocol Failure - Missing ACK");
                        break;
                    }

                    if ((messageTracker.Completion != null) && (messageTracker.Completion.ResponseHandler != null))
                    {
                       messageTracker.Completion.ResponseHandler(   messageTracker.Completion.UserData,
                                                                    sequenceNumber,
                                                                    subcommand,
                                                                    statusCode,
                                                                    buffer,
                                                                    16,
                                                                    dataLength - 16);
                    }
                    else
                    {
                        Debug.WriteLine("Discarding RSP for Sequence # " + sequenceNumber);
                    }

                    break;

                case CiiMessageType.MtStatus:

                    logger.Log("STAT", buffer, dataLength);

                    substatus = BitConverter.ToUInt32(buffer, 4);

                    if (connectionState != ConnectionState.Connected)
                    {
                        Debug.WriteLine("Throwing away early status message");
                        break;
                    }

                    lock (statusCallbacksLock)
                    {
                        ReceiveStatusHandler callback;
                        StatusCallbacks.TryGetValue(substatus, out callback);

                        if ((callback != null) && (dataLength >= 8))
                        {
                            callback(substatus, buffer, 8, dataLength - 8);
                        }
                        else if ((UnhandledStatusCallback != null) && (dataLength >= 8))
                        {
                            UnhandledStatusCallback(substatus, buffer, 8, dataLength - 8);
                        }
                    }
                    break;

                //
                //  We should never see another type of message here.
                //  This is an asymetric protocol between client and server.
                //
                default:
                    logger.Log("UNKNOWN", buffer, dataLength);
                    SendAsyncError("Unknown MessageType! " + type.ToString());
                    break;
            }
        }
        private bool Login(CiiAccessLevel requestedAccess)
        {
            byte[] LoginBuffer;
            byte[] MyAddress = BackEndManager.GetLocalAddress();
            byte[] Access = BitConverter.GetBytes((uint)requestedAccess);
            int CopyLength;

            #if WindowsCE
            byte[] Username = System.Text.Encoding.UTF8.GetBytes("Display");
            byte[] MachineName = System.Text.Encoding.UTF8.GetBytes("Cortex");
            #else
            byte[] Username = System.Text.Encoding.UTF8.GetBytes(Environment.UserName);
            byte[] MachineName = System.Text.Encoding.UTF8.GetBytes(Environment.MachineName);
            #endif

            LoginBuffer = new byte[BytesLogin.Length + Access.Length + MyAddress.Length + 64 + 64];

            Array.Copy(BytesLogin, 0, LoginBuffer, 0, BytesLogin.Length);
            Array.Copy(Access, 0, LoginBuffer, 4, Access.Length);
            Array.Copy(MyAddress, 0, LoginBuffer, 8, MyAddress.Length);

            CopyLength = Username.Length > 64 ? 64 : Username.Length;
            Array.Copy(Username, 0, LoginBuffer, 12, CopyLength);

            CopyLength = MachineName.Length > 64 ? 64 : MachineName.Length;
            Array.Copy(MachineName, 0, LoginBuffer, 76, CopyLength);

            loginAcceptEvent.Reset();

            logger.Log("LOGIN", LoginBuffer, LoginBuffer.Length);

            bool Success = BackEndManager.SendMessage(LoginBuffer);

            if (!Success)
            {
                SendAsyncError("Failed Login!");
            }
            else
            {
                Success = loginAcceptEvent.WaitOne(LoginTimeout, false);
                if (!Success)
                {
                    SendAsyncError("Login Accept timed out! " + LoginTimeout + " ms");
                }
            }
            return Success;
        }
        /// <summary>
        /// Client invoked code.
        /// </summary>
        /// <param name="requestedAccess"></param>
        /// <returns></returns>
        public bool Connect(CiiAccessLevel requestedAccess)
        {
            if (connectionState != ConnectionState.NotConnected)
            {
                return false;
            }

            bool success = BackEndManager.Connect();

            if (success)
            {
                connectionState = ConnectionState.WaitingForLogin;
                success = Login(requestedAccess);

                if (success)
                {
                    connectionState = ConnectionState.Connected;
                    ConnectEventHandler callbacks = ConnectEvent;
                    if (callbacks != null)
                    {
                        callbacks(this, new EventArgs());
                    }
                }
                else
                {
                    BackEndManager.Disconnect();
                    connectionState = ConnectionState.NotConnected;
                }
            }

            return success;
        }