Esempio n. 1
0
        public async Task ConnectAndReconnectAsync()
        {
            const int connectTimeout = MaxTimeout;
            var       session        = await ClientFixture.ConnectAsync(ServerUrl, SecurityPolicies.Basic256Sha256, Endpoints).ConfigureAwait(false);

            Assert.NotNull(session);

            ManualResetEvent quitEvent = new ManualResetEvent(false);
            var reconnectHandler       = new SessionReconnectHandler();

            reconnectHandler.BeginReconnect(session, connectTimeout / 5,
                                            (object sender, EventArgs e) => {
                // ignore callbacks from discarded objects.
                if (!Object.ReferenceEquals(sender, reconnectHandler))
                {
                    return;
                }

                session = reconnectHandler.Session;
                reconnectHandler.Dispose();
                quitEvent.Set();
            });

            var timeout = quitEvent.WaitOne(connectTimeout);

            Assert.True(timeout);

            var result = session.Close();

            Assert.NotNull(result);
            session.Dispose();
        }
Esempio n. 2
0
 private void ReestablishConnection()
 {
     _logger.Trace();
     _reconnectionHandler = new SessionReconnectHandler();
     State = OpcState.Connecting;
     _reconnectionHandler.BeginReconnect(_session, _config.ReconnectAttemptInterval, OnSessionReconnectComplete);
 }
        private void StandardClient_KeepAlive(Session sender, KeepAliveEventArgs e)
        {
            if (e != null && sender != null)
            {
                // ignore callbacks from discarded objects.
                if (!Object.ReferenceEquals(sender, Session))
                {
                    return;
                }

                if (!ServiceResult.IsGood(e.Status))
                {
                    Program.Trace(String.Format(
                                      "Status: {0} Outstanding requests: {1} Defunct requests: {2}",
                                      e.Status,
                                      sender.OutstandingRequestCount,
                                      sender.DefunctRequestCount));

                    if (e.Status.StatusCode == StatusCodes.BadNoCommunication &&
                        m_reconnectHandler == null)
                    {
                        Program.Trace("--- RECONNECTING --- {0}", sender.Endpoint.EndpointUrl);
                        m_reconnectHandler = new SessionReconnectHandler();
                        m_reconnectHandler.BeginReconnect(sender, c_reconnectPeriod, Client_ReconnectComplete);
                    }
                }
            }
        }
Esempio n. 4
0
        /// <summary>
        /// Disconnects from the current session.
        /// </summary>
        private void Server_ReconnectComplete(object sender, EventArgs e)
        {
            if (this.InvokeRequired)
            {
                this.BeginInvoke(new EventHandler(Server_ReconnectComplete), sender, e);
                return;
            }

            try
            {
                // ignore callbacks from discarded objects.
                if (!Object.ReferenceEquals(sender, m_reconnectHandler))
                {
                    return;
                }

                m_session = m_reconnectHandler.Session;

                m_reconnectHandler.Dispose();
                m_reconnectHandler = null;

                ConnectedLB.Text         = "Connected";
                LastKeepAliveTimeLB.Text = "---";
            }
            catch (Exception exception)
            {
                MessageBox.Show(exception.Message, this.Text, MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }
Esempio n. 5
0
        /// <summary>
        /// Handles a reconnect event complete from the reconnect handler.
        /// </summary>
        private void Server_ReconnectComplete(object sender, EventArgs e)
        {
            if (this.InvokeRequired)
            {
                this.BeginInvoke(new EventHandler(Server_ReconnectComplete), sender, e);
                return;
            }

            try
            {
                // ignore callbacks from discarded objects.
                if (!Object.ReferenceEquals(sender, m_reconnectHandler))
                {
                    return;
                }

                m_session = m_reconnectHandler.Session;
                m_reconnectHandler.Dispose();
                m_reconnectHandler = null;

                // raise any additional notifications.
                if (m_ReconnectComplete != null)
                {
                    m_ReconnectComplete(this, e);
                }
            }
            catch (Exception exception)
            {
                ClientUtils.HandleException(this.Text, exception);
            }
        }
Esempio n. 6
0
        private void StandardClient_Server_ReconnectComplete(object sender, EventArgs e)
        {
            if (InvokeRequired)
            {
                BeginInvoke(new EventHandler(StandardClient_Server_ReconnectComplete), sender, e);
                return;
            }

            try
            {
                // ignore callbacks from discarded objects.
                if (!Object.ReferenceEquals(sender, m_reconnectHandler))
                {
                    return;
                }

                m_session = m_reconnectHandler.Session;
                m_reconnectHandler.Dispose();
                m_reconnectHandler = null;

                BrowseCTRL.SetView(m_session, BrowseViewType.Objects, null);

                SessionsCTRL.Reload(m_session);

                StandardClient_KeepAlive(m_session, null);
            }
            catch (Exception exception)
            {
                GuiUtils.HandleException(this.Text, MethodBase.GetCurrentMethod(), exception);
            }
        }
Esempio n. 7
0
 /// <summary>
 /// 处理保持连接心跳事件
 /// </summary>
 private void OnKeepAliveNotification(Session session, KeepAliveEventArgs e)
 {
     try
     {
         if (e.Status != null && ServiceResult.IsNotGood(e.Status))
         {
             IsConnected = false;
             if (ReconnectPeriod <= 0)
             {
                 OnErrorHappened?.Invoke(this, new OpcUaErrorEventArgs((OpcUaStatusCodes)e.Status.Code, $"{ToString()},保持连接时错误", null));
                 return;
             }
             if (sessionReconnectHandler == null)
             {
                 OnLogHappened?.Invoke(this, new OpcUaLogEventArgs($"{ToString()},重新连接中... "));
                 sessionReconnectHandler = new SessionReconnectHandler();
                 sessionReconnectHandler.BeginReconnect(session, ReconnectPeriod * 1000, Server_ReconnectComplete);
             }
         }
     }
     catch (Exception ex)
     {
         OnErrorHappened?.Invoke(this, new OpcUaErrorEventArgs(OpcUaStatusCodes.Uncertain, $"{ToString()},保持连接时发生未处理的异常", ex));
     }
 }
        public ExitCode Run()
        {
            Session session;

            try
            {
                session = ConsoleSampleClient().Result;
            }
            catch (Exception exception)
            {
                Utils.Trace("Exception:" + exception.Message);
                Console.WriteLine("Exception: {0}", exception.Message);
                return(ExitCode);
            }

            quitEvent_ = new ManualResetEvent(false);
            try
            {
                Console.CancelKeyPress += (sender, eArgs) =>
                {
                    quitEvent_.Set();
                    eArgs.Cancel = true;
                };
            }
            catch (Exception exception)
            {
                Utils.Trace("Exception:" + exception.Message);
                Console.WriteLine("Exception: {0}", exception.Message);
            }

            // Test the session reconnect handler
            var eventResult = quitEvent_.WaitOne(5000);

            if (!eventResult)
            {
                Console.WriteLine(" --- Start simulated reconnect... --- ");
                reconnectHandler_ = new SessionReconnectHandler();
                if (reverseConnectManager_ != null)
                {
                    reconnectHandler_.BeginReconnect(session, reverseConnectManager_, 1000, OnServerReconnectComplete);
                }
                else
                {
                    reconnectHandler_.BeginReconnect(session, 1000, OnServerReconnectComplete);
                }
            }

            // wait for timeout or Ctrl-C
            quitEvent_.WaitOne(clientRunTime_);

            // return error conditions
            if (session.KeepAliveStopped)
            {
                ExitCode = ExitCode.ErrorNoKeepAlive;
                return(ExitCode);
            }

            ExitCode = ExitCode.Ok;
            return(ExitCode);
        }
Esempio n. 9
0
 public void Dispose()
 {
     if (reconnectHandler != null)
     {
         reconnectHandler.Dispose();
         reconnectHandler = null;
     }
 }
Esempio n. 10
0
 private void Client_ReconnectComplete(object sender, EventArgs e)
 {
     if (!Object.ReferenceEquals(sender, m_reconnectHandler))
     {
         return;
     }
     m_session = m_reconnectHandler.Session;
     m_reconnectHandler.Dispose();
     m_reconnectHandler = null;
 }
Esempio n. 11
0
        /// <summary>
        /// Handles a keep alive event from a session.
        /// </summary>
        private void Session_KeepAlive(Session session, KeepAliveEventArgs e)
        {
            if (this.InvokeRequired)
            {
                this.BeginInvoke(new KeepAliveEventHandler(Session_KeepAlive), session, e);
                return;
            }

            try
            {
                // check for events from discarded sessions.
                if (!Object.ReferenceEquals(session, m_session))
                {
                    return;
                }

                // start reconnect sequence on communication error.
                if (ServiceResult.IsBad(e.Status))
                {
                    if (m_reconnectPeriod <= 0)
                    {
                        UpdateStatus(true, e.CurrentTime, "Communication Error ({0})", e.Status);
                        return;
                    }

                    UpdateStatus(true, e.CurrentTime, "Reconnecting in {0}s", m_reconnectPeriod);

                    if (m_reconnectHandler == null)
                    {
                        if (m_ReconnectStarting != null)
                        {
                            m_ReconnectStarting(this, e);
                        }

                        m_reconnectHandler = new SessionReconnectHandler();
                        m_reconnectHandler.BeginReconnect(m_session, m_reconnectPeriod * 1000, Server_ReconnectComplete);
                    }

                    return;
                }

                // update status.
                UpdateStatus(false, e.CurrentTime, "Connected [{0}]", session.Endpoint.EndpointUrl);

                // raise any additional notifications.
                if (m_KeepAliveComplete != null)
                {
                    m_KeepAliveComplete(this, e);
                }
            }
            catch (Exception exception)
            {
                ClientUtils.HandleException(this.Text, exception);
            }
        }
Esempio n. 12
0
 private void Client_KeepAlive(Session sender, KeepAliveEventArgs e)
 {
     if (e.Status != null && ServiceResult.IsNotGood(e.Status))
     {
         if (m_reconnectHandler == null)
         {
             m_reconnectHandler = new SessionReconnectHandler();
             m_reconnectHandler.BeginReconnect(sender, 10000, Client_ReconnectComplete);
         }
     }
 }
        private void ReconnectComplete(object sender, EventArgs e)
        {
            if (!(sender == reconnectHandler))
            {
                return;
            }

            session = reconnectHandler.Session;
            reconnectHandler.Dispose();
            reconnectHandler = null;
        }
Esempio n. 14
0
 /// <summary>
 /// Hier wird die Session überwacht und gegebenenfalls ein Reconnect angestoßen
 /// </summary>
 /// <param name="sender"></param>
 /// <param name="e"></param>
 private void Client_KeepAlive(Session sender, KeepAliveEventArgs e)
 {
     if (e.Status != null && ServiceResult.IsNotGood(e.Status))
     {
         if (reconnectHandler == null)
         {
             Console.WriteLine("--- RECONNECTING ---");
             reconnectHandler = new SessionReconnectHandler();
             reconnectHandler.BeginReconnect(sender, reconnectPeriod * 1000, Client_ReconnectComplete);
         }
     }
 }
Esempio n. 15
0
        private void Client_ReconnectComplete(object sender, EventArgs e)
        {
            // ignore callbacks from discarded objects.
            if (!Object.ReferenceEquals(sender, reconnectHandler))
            {
                return;
            }

            m_session = reconnectHandler.Session;
            reconnectHandler.Dispose();
            reconnectHandler = null;
        }
Esempio n. 16
0
        private void Client_ReconnectComplete(object sender, EventArgs e)
        {
            if (!Object.ReferenceEquals(sender, reconnectHandler))
            {
                return;
            }

            session = reconnectHandler.Session;
            reconnectHandler.Dispose();
            reconnectHandler = null;
            Console.WriteLine("--- RECONNECTED ---");
        }
Esempio n. 17
0
        public ExitCode Run()
        {
            Session session;

            try
            {
                session = ConsoleSampleClient().Result;
            }
            catch (Exception ex)
            {
                Utils.Trace("ServiceResultException:" + ex.Message);
                Console.WriteLine("Exception: {0}", ex.Message);
                return(ExitCode);
            }

            ManualResetEvent quitEvent = new ManualResetEvent(false);

            try
            {
                Console.CancelKeyPress += (sender, eArgs) =>
                {
                    quitEvent.Set();
                    eArgs.Cancel = true;
                };
            }
            catch
            {
            }

            // Test the session reconnecthandler
            bool eventResult = quitEvent.WaitOne(5000);

            if (!eventResult)
            {
                Console.WriteLine(" --- Start simulated reconnect... --- ");
                _reconnectHandler = new SessionReconnectHandler();
                _reconnectHandler.BeginReconnect(session, 1000, Client_ReconnectComplete);
            }

            // wait for timeout or Ctrl-C
            quitEvent.WaitOne(_clientRunTime);

            // return error conditions
            if (session.KeepAliveStopped)
            {
                ExitCode = ExitCode.ErrorNoKeepAlive;
                return(ExitCode);
            }

            ExitCode = ExitCode.Ok;
            return(ExitCode);
        }
Esempio n. 18
0
        /// <summary>
        /// Handles a keep alive event from a session.
        /// 处理会话中维持状态的事件
        /// </summary>
        private void Session_KeepAlive(Session session, KeepAliveEventArgs e)
        {
            if (InvokeRequired)
            {
                BeginInvoke(new KeepAliveEventHandler(Session_KeepAlive), session, e);
                return;
            }

            try
            {
                // 检查会话是否已经被丢弃
                if (!ReferenceEquals(session, m_session))
                {
                    return;
                }

                // start reconnect sequence on communication error.
                // 当通信出错的时候进行重连
                if (ServiceResult.IsBad(e.Status))
                {
                    if (m_reconnectPeriod <= 0)
                    {
                        UpdateStatus(true, e.CurrentTime, "Communication Error ({0})", e.Status);
                        return;
                    }

                    UpdateStatus(true, e.CurrentTime, "Reconnecting in {0}s", m_reconnectPeriod);

                    if (m_reconnectHandler == null)
                    {
                        m_ReconnectStarting?.Invoke(this, e);

                        m_reconnectHandler = new SessionReconnectHandler();
                        m_reconnectHandler.BeginReconnect(m_session, m_reconnectPeriod * 1000, Server_ReconnectComplete);
                    }

                    return;
                }

                // update status.
                // 更新状态
                UpdateStatus(false, e.CurrentTime, "Connected [{0}]", session.Endpoint.EndpointUrl);

                // raise any additional notifications.
                // 触发保持成功状态的事件,相当于心跳机制确认
                m_KeepAliveComplete?.Invoke(this, e);
            }
            catch (Exception exception)
            {
                ClientUtils.HandleException(this.Text, exception);
            }
        }
Esempio n. 19
0
        private void Client_KeepAlive(Session sender, KeepAliveEventArgs e)
        {
            if (e.Status != null && ServiceResult.IsNotGood(e.Status))
            {
                //log.InfoFormat("{0} {1}/{2}", e.Status, sender.OutstandingRequestCount, sender.DefunctRequestCount);

                if (reconnectHandler == null)
                {
                    reconnectHandler = new SessionReconnectHandler();
                    reconnectHandler.BeginReconnect(sender, ReconnectPeriod * 1000, Client_ReconnectComplete);
                }
            }
        }
        private void Client_ReconnectComplete(object sender, EventArgs e)
        {
            // ignore callbacks from discarded objects.
            if (!ReferenceEquals(sender, _reconnectHandler))
            {
                return;
            }

            Session = _reconnectHandler.Session;
            _reconnectHandler.Dispose();
            _reconnectHandler = null;

            Logger.Info("--- RECONNECTED ---");
        }
Esempio n. 21
0
        private void Client_KeepAlive(Session sender, KeepAliveEventArgs e)
        {
            if (e.Status != null && ServiceResult.IsNotGood(e.Status))
            {
                info.LabelText = e.Status.ToString() + sender.OutstandingRequestCount.ToString() + "/" + sender.DefunctRequestCount.ToString();

                if (reconnectHandler == null)
                {
                    info.LabelText   = "--- RECONNECTING ---";
                    reconnectHandler = new SessionReconnectHandler();
                    reconnectHandler.BeginReconnect(sender, ReconnectPeriod * 1000, Client_ReconnectComplete);
                }
            }
        }
Esempio n. 22
0
        private void Client_ReconnectComplete(object sender, EventArgs e)
        {
            // ignore callbacks from discarded objects.
            if (!Object.ReferenceEquals(sender, reconnectHandler))
            {
                return;
            }

            session = reconnectHandler.Session;
            reconnectHandler.Dispose();
            reconnectHandler = null;

            info.LabelText = "--- RECONNECTING ---";
        }
Esempio n. 23
0
        private void Client_KeepAlive(Session sender, KeepAliveEventArgs e)
        {
            if (e.Status != null && ServiceResult.IsNotGood(e.Status))
            {
                Console.WriteLine("{0} {1}/{2}", e.Status, sender.OutstandingRequestCount, sender.DefunctRequestCount);

                if (m_reconnectHandler == null)
                {
                    Console.WriteLine("--- RECONNECTING ---");
                    m_reconnectHandler = new SessionReconnectHandler();
                    m_reconnectHandler.BeginReconnect(sender, ReconnectPeriod * 1000, Client_ReconnectComplete);
                }
            }
        }
        /// <summary>
        /// The created Session KeepAlive Callback
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void OnKeepAliveEvent(Session session, KeepAliveEventArgs e)
        {
            if (e.Status != null && ServiceResult.IsNotGood(e.Status))
            {
                log.Warn($"KeepAlive {e.Status} {session.OutstandingRequestCount} {session.DefunctRequestCount}");

                if (reconnectHandler == null)
                {
                    log.Info("KeepAlive attempting Reconnect");
                    reconnectHandler = new SessionReconnectHandler();
                    reconnectHandler.BeginReconnect(session, reconnectPeriodSeconds, OnKeepAliveComplete);
                }
            }
        }
Esempio n. 25
0
            private void Client_KeepAlive(Session sender, KeepAliveEventArgs e)
            {
                if (e.Status != null && ServiceResult.IsNotGood(e.Status))
                {
                    Log(conn_name + " - " + e.Status + "," + sender.OutstandingRequestCount + ", " + sender.DefunctRequestCount);

                    if (reconnectHandler == null)
                    {
                        Log(conn_name + " - " + "--- RECONNECTING ---");
                        reconnectHandler = new SessionReconnectHandler();
                        reconnectHandler.BeginReconnect(sender, ReconnectPeriod * 1000, Client_ReconnectComplete);
                    }
                }
            }
        private void Client_ReconnectComplete(object sender, EventArgs e)
        {
            // ignore callbacks from discarded objects.
            if (!Object.ReferenceEquals(sender, m_reconnectHandler))
            {
                return;
            }

            m_session = m_reconnectHandler.Session;
            m_reconnectHandler.Dispose();
            m_reconnectHandler = null;

            Console.WriteLine("--- RECONNECTED ---");
        }
        private void Client_ReconnectComplete(object sender, EventArgs e)
        {
            // ignore callbacks from discarded objects.
            if (!Object.ReferenceEquals(sender, m_reconnectHandler))
            {
                return;
            }

            Session = m_reconnectHandler.Session;
            m_reconnectHandler.Dispose();
            m_reconnectHandler = null;

            Program.Trace(String.Format("--- RECONNECTED --- {0}", Session.Endpoint.EndpointUrl));
        }
Esempio n. 28
0
        private void OnSessionKeepAliveEvent(object sender, SessionKeepAliveEventArgs e)
        {
            if (sender is Session session && e.Status != null && ServiceResult.IsNotGood(e.Status))
            {
                Console.WriteLine("{0} {1}/{2}", e.Status, session.OutstandingRequestCount, session.DefunctRequestCount);

                if (reconnectHandler_ == null)
                {
                    Console.WriteLine("--- RECONNECTING ---");
                    reconnectHandler_ = new SessionReconnectHandler();
                    reconnectHandler_.BeginReconnect(session, ReconnectPeriod * 1000, OnServerReconnectComplete);
                }
            }
        }
Esempio n. 29
0
        /// <summary>
        /// Handles a keep alive event from a session.
        /// </summary>
        private void Session_KeepAlive(Session session, KeepAliveEventArgs e)
        {
            try
            {
                // check for events from discarded sessions.
                if (!Object.ReferenceEquals(session, m_session))
                {
                    return;
                }

                // start reconnect sequence on communication error.
                if (ServiceResult.IsBad(e.Status))
                {
                    if (m_reconnectPeriod <= 0)
                    {
                        UpdateStatus(true, e.CurrentTime, "Communication Error ({0})", e.Status);
                        return;
                    }

                    UpdateStatus(true, e.CurrentTime, "Reconnecting in {0}s", m_reconnectPeriod);

                    if (m_reconnectHandler == null)
                    {
                        if (reconnectEnable == true)
                        {
                            m_ReconnectStarting?.Invoke(this, e);

                            m_reconnectHandler = new SessionReconnectHandler();
                            m_reconnectHandler.BeginReconnect(m_session, m_reconnectPeriod * 1000, Server_ReconnectComplete);
                        }
                    }

                    return;
                }

                // update status.
                UpdateStatus(false, e.CurrentTime, "Connected [{0}]", session.Endpoint.EndpointUrl);

                // raise any additional notifications.
                m_KeepAliveComplete?.Invoke(this, e);
            }
            catch (Exception exception)
            {
#if !NETSTANDARD2_0
                ClientUtils.HandleException(OpcUaName, exception);
#else
                throw;
#endif
            }
        }
Esempio n. 30
0
        /// <summary>
        /// Cleans up when the main form closes.
        /// </summary>
        private void MainForm_FormClosing(object sender, FormClosingEventArgs e)
        {
            if (m_reconnectHandler != null)
            {
                m_reconnectHandler.Dispose();
                m_reconnectHandler = null;
            }

            if (m_session != null)
            {
                m_session.Close(1000);
                m_session = null;
            }
        }
        /// <summary>
        /// Disconnects from the server.
        /// </summary>
        public void Disconnect()
        {
            UpdateStatus(false, DateTime.UtcNow, "Disconnected");

            // stop any reconnect operation.
            if (m_reconnectHandler != null)
            {
                m_reconnectHandler.Dispose();
                m_reconnectHandler = null;
            }

            // disconnect any existing session.
            if (m_session != null)
            {
                m_session.Close(10000);
                m_session = null;
            }

            // raise an event.
            DoConnectComplete(null);
        }
        /// <summary>
        /// Handles a keep alive event from a session.
        /// </summary>
        private void Session_KeepAlive(Session session, KeepAliveEventArgs e)
        {
            if (this.InvokeRequired)
            {
                this.BeginInvoke(new KeepAliveEventHandler(Session_KeepAlive), session, e);
                return;
            }

            try
            {
                // check for events from discarded sessions.
                if (!Object.ReferenceEquals(session, m_session))
                {
                    return;
                }

                // start reconnect sequence on communication error.
                if (ServiceResult.IsBad(e.Status))
                {
                    if (m_reconnectPeriod <= 0)
                    {
                        UpdateStatus(true, e.CurrentTime, "Communication Error ({0})", e.Status);
                        return;
                    }

                    UpdateStatus(true, e.CurrentTime, "Reconnecting in {0}s", m_reconnectPeriod);

                    if (m_reconnectHandler == null)
                    {
                        if (m_ReconnectStarting != null)
                        {
                            m_ReconnectStarting(this, e);
                        }

                        m_reconnectHandler = new SessionReconnectHandler();
                        m_reconnectHandler.BeginReconnect(m_session, m_reconnectPeriod * 1000, Server_ReconnectComplete);
                    }

                    return;
                }

                // update status.
                UpdateStatus(false, e.CurrentTime, "Connected [{0}]", session.Endpoint.EndpointUrl);
                                
                // raise any additional notifications.
                if (m_KeepAliveComplete != null)
                {
                    m_KeepAliveComplete(this, e);
                }
            }
            catch (Exception exception)
            {
                ClientUtils.HandleException(this.Text, exception);
            }
        }
        /// <summary>
        /// Handles a reconnect event complete from the reconnect handler.
        /// </summary>
        private void Server_ReconnectComplete(object sender, EventArgs e)
        {
            if (this.InvokeRequired)
            {
                this.BeginInvoke(new EventHandler(Server_ReconnectComplete), sender, e);
                return;
            }

            try
            {
                // ignore callbacks from discarded objects.
                if (!Object.ReferenceEquals(sender, m_reconnectHandler))
                {
                    return;
                }

                m_session = m_reconnectHandler.Session;
                m_reconnectHandler.Dispose();
                m_reconnectHandler = null;

                // raise any additional notifications.
                if (m_ReconnectComplete != null)
                {
                    m_ReconnectComplete(this, e);
                }
            }
            catch (Exception exception)
            {
                ClientUtils.HandleException(this.Text, exception);
            }
        }