/// <summary> /// Exchange messages with Outlook. /// </summary> private void ChatWithOutlook() { Debug.Assert(m_msgQueue.Count == 0); Debug.Assert(m_chattingFlag); try { AnpTransport transport = new AnpTransport(m_outlookSock); while (true) { if (!transport.isReceiving) transport.beginRecv(); if (!transport.isSending && m_msgQueue.Count != 0) transport.sendMsg(m_msgQueue.Dequeue()); SelectSockets select = new SelectSockets(); select.AddRead(m_outlookSock); if (transport.isSending) select.AddWrite(m_outlookSock); Block(select); transport.doXfer(); if (transport.doneReceiving) { OutlookUiThreadMsg msg = new OutlookUiThreadMsg(m_broker, OutlookBrokerMsgType.ReceivedMsg); msg.Msg = transport.getRecv(); PostToUI(msg); } } } catch (Exception ex) { m_chattingFlag = false; CloseOutlookSock(); m_msgQueue.Clear(); if (ex is WorkerCancellationException) throw; OutlookUiThreadMsg msg = new OutlookUiThreadMsg(m_broker, OutlookBrokerMsgType.Disconnected); msg.Ex = ex; PostToUI(msg); } }
/// <summary> /// Wait for one of the sockets specified to become ready, for a message /// to arrive or for the thread to be cancelled. Be careful not to call /// Block() while handling a message, since this method does not handle /// recursivity. Do not call this method after it has thrown an exception. /// </summary> protected void Block(SelectSockets set) { Debug.Assert(!BlockedFlag); BlockedFlag = true; set.AddRead(SocketPair[1]); set.Select(); FlushWakeUp(); CheckCancellation(); CheckMessages(); BlockedFlag = false; }
/// <summary> /// Block until the tunnel is connected or a timeout occurs. If the tunnel /// is connected, a new AnpTransport is created and the method returns true. /// Otherwise, the method returns false. /// </summary> /// <param name="timeout">Microseconds to wait in the Select.</param> public bool CheckConnect(int timeout) { // FIXME : loop to call tunnel.CheckTls() // at regular intervals. SelectSockets select = new SelectSockets(); select.Timeout = timeout; select.AddRead(tunnel.Sock); tunnel.CheckTls(); select.Select(); if (select.ReadSockets.Contains(tunnel.Sock)) { CreateTransport(); return true; } return false; }
/// <summary> /// Run one pass of the main loop. /// </summary> private void RunPass() { // Wait for a command. while (m_commandQueue.Count == 0) Block(new SelectSockets()); // Get the next command. KmodThreadCommand command = m_commandQueue.Dequeue(); // Send the command. m_transport.sendMsg(command.Msg); while (true) { m_transport.doXfer(); if (!m_transport.isSending) break; SelectSockets select = new SelectSockets(); select.AddWrite(m_kmodSock); Block(select); } // We're done. if (!command.HaveResultFlag) return; // Wait for the results or the next command. bool firstFlag = true; while (m_commandQueue.Count == 0) { if (!m_transport.isReceiving) m_transport.beginRecv(); m_transport.doXfer(); // Push the next result. if (m_transport.doneReceiving) { lock (Mon) { m_elementQueue.Enqueue(m_transport.getRecv()); Monitor.Pulse(Mon); } // Send the notification that results are ready for this command. if (firstFlag) { firstFlag = false; PostToUI(new KmodThreadNotif(m_broker, command)); } } // Wait for the next result or the next command. else { SelectSockets select = new SelectSockets(); select.AddRead(m_kmodSock); Block(select); } } }
/// <summary> /// Analyse the result of the select() call for the specified KAS. /// </summary> private void UpdateStateAfterSelect(KcmKas k, SelectSockets selectSockets) { try { k.CheckNoMessageReceivedInvariant(); // We have nothing to do if we don't have an established TCP // connection. if (k.ConnStatus != KcmKasConnectionStatus.Connected && k.ConnStatus != KcmKasConnectionStatus.RoleReply) return; // Perform transfers only if the socket is ready. Debug.Assert(k.Tunnel.Sock != null); if (!selectSockets.InReadOrWrite(k.Tunnel.Sock)) return; // Do up to 20 transfers (the limit exists for quenching purposes). for (int i = 0; i < 20; i++) { // Send a message if possible. k.SendNextQueuedMsgIfNeeded(); // Remember if we are sending a message. bool sendingFlag = k.Tunnel.IsSendingMessage(); // Do transfers. k.Tunnel.DoXfer(); // Stop if no message has been received and no message has been sent. if (!k.Tunnel.HasReceivedMessage() && (!sendingFlag || k.Tunnel.IsSendingMessage())) break; // Process the message received. if (k.Tunnel.HasReceivedMessage()) ProcessIncomingKasMessage(k, k.Tunnel.GetMsg()); } k.CheckNoMessageReceivedInvariant(); } catch (Exception ex) { HandleDisconnectedKas(k, ex); } }
/// <summary> /// Add the socket of the KAS in the select sets as needed and manage /// ktlstunnel.exe processes. /// </summary> private void PrepareStateForSelect(KcmKas k, SelectSockets selectSockets, bool quenchFlag, ref bool connWatchFlag) { // Note: the KAS should never have received a message when this function // is called. The function UpdateStateAfterSelect() is responsible for // doing all the transfers and handling any message received after these // transfers. try { k.CheckNoMessageReceivedInvariant(); if (k.ConnStatus == KcmKasConnectionStatus.Scheduled) { // Start ktlstunnel.exe. k.ConnStatus = KcmKasConnectionStatus.Connecting; k.Tunnel.BeginConnect(); } if (k.ConnStatus == KcmKasConnectionStatus.Connecting) { // The TCP connection is now open. if (k.Tunnel.CheckConnect(0)) { // Send the select role command. k.SendSelectRoleMsg(); // Wait for the reply to arrive. k.ConnStatus = KcmKasConnectionStatus.RoleReply; } // Wait for the TCP connection to be established. We busy wait // to monitor the status of ktlstunnel.exe regularly, to detect // the case where the connection fails. else connWatchFlag = true; } if (k.ConnStatus == KcmKasConnectionStatus.RoleReply) { // Wait for the reply to arrive. if (!quenchFlag) k.Tunnel.UpdateSelect(selectSockets); } if (k.ConnStatus == KcmKasConnectionStatus.Connected) { // Send the next message, if possible. k.SendNextQueuedMsgIfNeeded(); if (!quenchFlag) k.Tunnel.UpdateSelect(selectSockets); } k.CheckNoMessageReceivedInvariant(); } catch (Exception ex) { HandleDisconnectedKas(k, ex); } }
/// <summary> /// Loop processing KASes. /// </summary> private void MainLoop() { while (true) { Debug.Assert(!MustReplyToWm()); // Refresh the quench deadline if it depends on the amount of // time elapsed. if (m_quenchDeadline != DateTime.MinValue && m_quenchDeadline != DateTime.MaxValue) m_wmNotifFlag = true; // If we were notified, process the WM messages. This refreshes // the quench deadline. if (m_wmNotifFlag) { m_wmNotifFlag = false; ProcessIncomingWmMessages(); } Debug.Assert(!MustReplyToWm()); // Determine whether we are quenched and the value of the // select timeout. By default we wait forever in select(). bool quenchFlag = false; int timeout = -2; // Be quenched until we are notified. if (m_quenchDeadline == DateTime.MaxValue) quenchFlag = true; // Be quenched up to the deadline we were given. else if (m_quenchDeadline != DateTime.MinValue) { DateTime now = DateTime.Now; if (m_quenchDeadline > now) { quenchFlag = true; timeout = (int)(m_quenchDeadline - now).TotalMilliseconds; } } // Prepare for call to select. bool connWatchFlag = false; SelectSockets selectSockets = new SelectSockets(); foreach (KcmKas k in m_kasTree.Values) PrepareStateForSelect(k, selectSockets, quenchFlag, ref connWatchFlag); // Our state has changed. Notify the WM and recompute our state. if (MustReplyToWm()) { ReplyToWm(); continue; } // Reduce the timeout to account for ktlstunnel.exe. if (connWatchFlag) DecrementTimeout(ref timeout, 300); // Block in the call to select(). Note that we receive notifications // here. selectSockets.Timeout = timeout * 1000; Block(selectSockets); // If we are not quenched, perform transfers. if (!quenchFlag) { foreach (KcmKas k in m_kasTree.Values) UpdateStateAfterSelect(k, selectSockets); ReplyToWm(); } } }
/// <summary> /// Block until the connection to Outlook is established. /// </summary> private void ConnectToOutlook() { Debug.Assert(!m_chattingFlag); Logging.Log("ConnectToOutlook() called."); while (!m_chattingFlag) { Logging.Log("In while loop."); // Close the Outlook socket if we have opened it inconclusively. CloseOutlookSock(); try { // Accept a connection. SelectSockets select = new SelectSockets(); select.AddRead(m_listenSock); Block(select); if (!select.InRead(m_listenSock)) continue; m_outlookSock = m_listenSock.Accept(); m_outlookSock.Blocking = false; Logging.Log("About to read MSO auth data."); // Read the authentication data. Timeout after 2 seconds. DateTime startTime = DateTime.Now; byte[] authSockData = new byte[m_secret.Length]; int nbRead = 0; while (nbRead != m_secret.Length) { Logging.Log("In Read while loop"); select = new SelectSockets(); select.AddRead(m_outlookSock); SetSelectTimeout(startTime, 2000, select, "no authentication data received"); Logging.Log("About to block"); Block(select); Logging.Log("Out of block"); int r = Base.SockRead(m_outlookSock, authSockData, nbRead, m_secret.Length - nbRead); if (r > 0) nbRead += r; Logging.Log("End Read while loop"); } if (!Base.ByteArrayEqual(m_secret, authSockData)) throw new Exception("invalid authentication data received"); Logging.Log("About to tell the broker we are connected."); // Inform the broker that we're connected. OutlookUiThreadMsg msg = new OutlookUiThreadMsg(m_broker, OutlookBrokerMsgType.Connected); msg.SessionID = ++m_sessionID; PostToUI(msg); // We're now chatting. m_chattingFlag = true; } catch (Exception ex) { if (ex is WorkerCancellationException) throw; Logging.Log(2, ex.Message); } } }
/// <summary> /// Send a message on the tunnel. /// </summary> protected void SendAnpMsg(AnpMsg m) { AnpTransport transfer = InternalAnpTunnel.GetTransport(); Debug.Assert(transfer.isReceiving || transfer.doneReceiving); Debug.Assert(!transfer.isSending); transfer.sendMsg(m); while (transfer.isSending) { transfer.doXfer(); if (transfer.isSending) { SelectSockets set = new SelectSockets(); InternalAnpTunnel.UpdateSelect(set); Block(set); } } }
protected override void Run() { // Connect the tunnel. InternalAnpTunnel = Helper.CreateTunnel(); Tunnel tunnel = InternalAnpTunnel.GetTunnel(); InternalAnpTunnel.BeginConnect(Host, Port); while (true) { SelectSockets set = new SelectSockets(); set.Timeout = 100; tunnel.CheckTls(); set.AddRead(tunnel.Sock); Block(set); if (set.ReadSockets.Contains(tunnel.Sock)) { InternalAnpTunnel.CreateTransport(); break; } } // Handle the tunnel. OnTunnelConnected(); }
/// <summary> /// Retrieve a message from the tunnel. /// </summary> protected AnpMsg GetAnpMsg() { AnpTransport transfer = InternalAnpTunnel.GetTransport(); Debug.Assert(transfer.isReceiving || transfer.doneReceiving); Debug.Assert(!transfer.isSending); while (!transfer.doneReceiving) { transfer.doXfer(); if (!transfer.doneReceiving) { SelectSockets set = new SelectSockets(); InternalAnpTunnel.UpdateSelect(set); Block(set); } } AnpMsg msg = transfer.getRecv(); transfer.beginRecv(); return msg; }
/// <summary> /// Update the select set specified with the socket of the tunnel. /// </summary> public void UpdateSelect(SelectSockets select) { Debug.Assert(transport.isReceiving || transport.doneReceiving); if (transport.isSending) select.AddWrite(tunnel.Sock); if (transport.isReceiving && !transport.doneReceiving) select.AddRead(tunnel.Sock); }
/// <summary> /// Send an AnpMsg. This method blocks if a message is currently being /// transferred. /// </summary> /// <param name="msg"></param> public void SendMsg(AnpMsg msg) { Debug.Assert(transport.isReceiving || transport.doneReceiving); while (transport.isSending) { SelectSockets select = new SelectSockets(); transport.doXfer(); UpdateSelect(select); if (transport.isSending) select.Select(); } transport.sendMsg(msg); transport.doXfer(); }
/// <summary> /// Get an AnpMsg. This is a blocking method, unless a message has already /// been received. /// </summary> public AnpMsg GetMsg() { Debug.Assert(transport.isReceiving || transport.doneReceiving); while (!transport.doneReceiving) { SelectSockets select = new SelectSockets(); transport.doXfer(); UpdateSelect(select); if (!transport.doneReceiving) select.Select(); } AnpMsg msg = transport.getRecv(); transport.beginRecv(); return msg; }
/// <summary> /// Helper method for StartKmod(). /// </summary> private void SetSelectTimeout(DateTime startTime, int msec, SelectSockets select, String msg) { int remaining = msec - (int)(DateTime.Now - startTime).TotalMilliseconds; if (remaining < 0 ) throw new Exception(msg); select.Timeout = remaining * 1000; }
/// <summary> /// Start kmod and connect to it. /// </summary> private void StartKmod() { FileStream file = null; Socket listenSock = null; RegistryKey kwmRegKey = null; try { // Get the path to the kmod executable in the registry. kwmRegKey = Base.GetKwmLMRegKey(); String Kmod = "\"" + (String)kwmRegKey.GetValue("InstallDir", @"C:\Program Files\Teambox\Teambox Manager") + "\\kmod\\kmod.exe\""; // The directory where KMOD will save logs and its database for use with the kwm. String KmodDir = Misc.GetKmodDirPath(); Directory.CreateDirectory(KmodDir); // Start listening for kmod to connect when it'll be started. IPEndPoint endPoint = new IPEndPoint(IPAddress.Loopback, 0); listenSock = new Socket(endPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp); listenSock.Bind(endPoint); listenSock.Listen(1); int port = ((IPEndPoint)listenSock.LocalEndPoint).Port; // Start KMOD in debugging mode if our settings say so. String debug = Misc.ApplicationSettings.ktlstunnelLoggingLevel > 0 ? " -l 3" : ""; String args = " -C kmod_connect -p " + port + debug + " -m 20000 -k \"" + KmodDir + "\""; String cmdLine = Kmod + args; Logging.Log("About to start kmod.exe: " + cmdLine); m_kmodProc = new RawProcess(cmdLine); m_kmodProc.InheritHandles = false; m_kmodProc.CreationFlags = (uint)Syscalls.CREATION_FLAGS.CREATE_NO_WINDOW; m_kmodProc.Start(); // Wait for KMOD to connect for about 10 seconds. DateTime startTime = DateTime.Now; while (true) { SelectSockets select = new SelectSockets(); select.AddRead(listenSock); SetSelectTimeout(startTime, 10000, select, "no KMOD connection received"); Block(select); if (select.InRead(listenSock)) { m_kmodSock = listenSock.Accept(); m_kmodSock.Blocking = false; break; } } // Read the authentication data. byte[] authSockData = new byte[32]; byte[] authFileData = new byte[32]; startTime = DateTime.Now; int nbRead = 0; while (nbRead != 32) { SelectSockets select = new SelectSockets(); select.AddRead(m_kmodSock); SetSelectTimeout(startTime, 2000, select, "no authentication data received"); Block(select); int r = Base.SockRead(m_kmodSock, authSockData, nbRead, 32 - nbRead); if (r > 0) nbRead += r; } file = File.Open(KmodDir + "\\connect_secret", FileMode.Open); file.Read(authFileData, 0, 32); if (!Base.ByteArrayEqual(authFileData, authSockData)) throw new Exception("invalid authentication data received"); // Set the transport. m_transport = new K3pTransport(m_kmodSock); } finally { if (file != null) file.Close(); if (listenSock != null) listenSock.Close(); if (kwmRegKey != null) kwmRegKey.Close(); } }