/// <summary>
 /// Closes a comunication stream for a specific BindingContext
 /// </summary>
 /// <param name="stream">The stream to close.</param>
 /// <param name="ctx">The context to close the stream for.</param>
 public void Close( Stream stream, BindingContext ctx )
 {
     if(this.OnClose( stream, ctx ) == ChainResult.Continue && m_Next != null)
     {
         m_Next.Close( stream, ctx );
     }
 }
        /// <summary>
        /// Opens a comunication stream for a specific BindingContext
        /// </summary>
        /// <param name="stream">The stream to open.</param>
        /// <param name="ctx">The context to open the stream for.</param>
        public void Open( ref Stream stream, BindingContext ctx )
        {
            if (this.m_Next != null)
            {
                this.m_Next.Open(ref stream, ctx);
            }

            this.OnOpen(ref stream, ctx);
        }
        /// <summary>
        /// Processes an output message for a specific context
        /// </summary>
        /// <param name="message">The message to process.</param>
        /// <param name="ctx">The context to process the message for.</param>
        /// <returns><c>true</c> if the processing was succesfull, <c>false</c>otherwise.</returns>
        public bool ProcessOutputMessage(ref WsMessage message, BindingContext ctx)
        {
            ChainResult res = this.OnProcessOutputMessage( ref message, ctx );
            if( ChainResult.Continue == res && m_Next != null)
            {
                return m_Next.ProcessOutputMessage( ref message, ctx );
            }

            return res == ChainResult.Handled;
        }
        /// <summary>
        /// Opens the stream for the HTTP tansport binding 
        /// </summary>
        /// <param name="stream">The stream for this binding.</param>
        /// <param name="ctx">The context associated with the stream for this binding.</param>
        /// <returns>The handling status for this operation.</returns>
        protected override ChainResult OnOpen(ref Stream stream, BindingContext ctx)
        {
            if(ctx is ServerBindingContext)
            {
                m_httpListener = new HttpListener("http", m_endpointUri.Port);
                m_httpListener.Start();
            }

            return ChainResult.Handled;
        }
Beispiel #5
0
 /// <summary>
 /// A RequestContext object is created by the ReplyChannel when a request is received.
 /// </summary>
 /// <param name="message">The request incomming message</param>
 /// <param name="channel">The communication channel associated with the message</param>
 /// <param name="ctx">The binding context associated with the channel</param>
 internal RequestContext(WsMessage message, ReplyChannel channel, BindingContext ctx)
 {
     this.m_message = message;
     this.m_channel = channel;
     this.m_context = ctx;
 }
        /// <summary>
        /// Processes a message
        /// </summary>
        /// <param name="stream">The message being processed.</param>
        /// <param name="ctx">The context associated with the message.</param>
        /// <returns>The handling status for this operation.</returns>
        protected override ChainResult OnProcessInputMessage( ref WsMessage msg, BindingContext ctx )
        {
            byte[] soapMessage = null;
            byte[] buffer = new byte[MaxUdpPacketSize];

            while (true)
            {
                EndPoint remoteEndpoint = new IPEndPoint(IPAddress.Any, 0);

                int size = m_udpReceiveClient.ReceiveFrom(buffer, MaxUdpPacketSize, SocketFlags.None, ref remoteEndpoint);

                // If the stack is set to ignore request from this address do so
                if (m_config.IgnoreRequestsFromThisIp && ((IPEndPoint)remoteEndpoint).Address.Equals(s_localIP))
                {
                    continue;
                }

                if (size > 0)
                {
                    soapMessage = new byte[size];
                    Array.Copy(buffer, soapMessage, size);
                }
                else
                {
                    System.Ext.Console.Write("UDP Receive returned 0 bytes");
                }

                ctx.ContextObject = remoteEndpoint;

                System.Ext.Console.Write("UDP Request From: " + remoteEndpoint.ToString());
                System.Ext.Console.Write(soapMessage);

                break;
            }


            msg.Body = soapMessage;

            return ChainResult.Continue;
        }
        /// <summary>
        /// Opens the stream for the UDP tansport binding 
        /// </summary>
        /// <param name="stream">The stream for this binding.</param>
        /// <param name="ctx">The context associated with the stream for this binding.</param>
        /// <returns>The handling status for this operation.</returns>
        protected override ChainResult OnOpen( ref Stream stream, BindingContext ctx )
        {
            m_udpReceiveClient = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
            IPEndPoint localEP = new IPEndPoint(s_localIP, m_config.DiscoveryPort);
            m_udpReceiveClient.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
            m_udpReceiveClient.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReceiveBuffer, 0x5000);

            // Join Multicast Group
            byte[] discoveryAddr = m_config.DiscoveryAddress.GetAddressBytes();
            byte[] ipAddr        = s_localIP.GetAddressBytes();
            byte[] multicastOpt  = new byte[] {  discoveryAddr[0], discoveryAddr[1], discoveryAddr[2], discoveryAddr[3],   // WsDiscovery Multicast Address: 239.255.255.250
                                                 ipAddr       [0], ipAddr       [1], ipAddr       [2], ipAddr       [3] }; // Local IPAddress
            m_udpReceiveClient.Bind(localEP);
            m_udpReceiveClient.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.MulticastInterface, ipAddr );
            m_udpReceiveClient.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.AddMembership, multicastOpt);

            return ChainResult.Continue;
        }
        /// <summary>
        /// Processes a message 
        /// </summary>
        /// <param name="msg">The message being processed.</param>
        /// <param name="ctx">The context associated with the message.</param>
        /// <returns>The handling status for this operation.</returns>
        protected override ChainResult OnProcessOutputMessage( ref WsMessage msg, BindingContext ctx )
        {
            byte[] data = null;

            // if the body is a byte[] then it is already serialized (UDP stuff)
            if (msg == null || (msg.Body != null && msg.Body is byte[])) return ChainResult.Continue;

            // Build response message
            using (XmlMemoryWriter xmlWriter = XmlMemoryWriter.Create())
            {
                WsSoapMessageWriter smw = new WsSoapMessageWriter(ctx.Version);

                // Write start message up to body element content
                smw.WriteSoapMessageStart(xmlWriter, msg, ctx.Version.IncludeSoapHeaders);

                if (msg.Body != null && msg.Serializer != null)
                {
                    DataContractSerializer ser = (DataContractSerializer)msg.Serializer;
                    // Serialize the body element
                    ser.WriteObject(xmlWriter, msg.Body);

                    if(ser.BodyParts != null && ser.BodyParts.Count > 0)
                    {
                        msg.BodyParts = ser.BodyParts;
                    }
                }

                // Write end message
                smw.WriteSoapMessageEnd(xmlWriter);

                data = xmlWriter.ToArray();
            }

            WsMtomBodyParts bodyParts = msg.BodyParts;

            if (bodyParts != null)
            {
                DataContractSerializer reqDcs = (DataContractSerializer)msg.Serializer;

                bodyParts.Start = "<soap@soap>";
                bodyParts.AddStart(reqDcs.CreateNewBodyPart(data, bodyParts.Start));
                if (reqDcs.BodyParts.Count > 0)
                {
                    bodyParts.Add(reqDcs.BodyParts[0]);
                }

                WsMtomParams mtomParams = new WsMtomParams();
                if (bodyParts.Boundary == null)
                    bodyParts.Boundary = Guid.NewGuid().ToString() + '-' + Guid.NewGuid().ToString().Substring(0, 33);
                mtomParams.start = bodyParts.Start;
                mtomParams.boundary = bodyParts.Boundary;
                msg.MtomPropeties = mtomParams;
                WsMtom mtom = new WsMtom();
                data = mtom.CreateMessage(bodyParts);
            }

            msg.Body = data;

            return ChainResult.Continue;
        }
        internal void SendMessage( WsMessage message, BindingContext ctx )
        {
            // Reset the binding properties as they were likely changed by the receive
            ctx.BindingProperties = (ArrayList)m_context.BindingProperties.Clone();

            m_binding.Elements.ProcessOutputMessage( ref message, ctx );
        }
 /// <summary>
 /// A RequestContext object is created by the ReplyChannel when a request is received.
 /// </summary>
 /// <param name="message">The request incomming message</param>
 /// <param name="channel">The communication channel associated with the message</param>
 /// <param name="ctx">The binding context associated with the channel</param>
 internal RequestContext( WsMessage message, ReplyChannel channel, BindingContext ctx )
 {
     this.m_message = message;
     this.m_channel = channel;
     this.m_context = ctx;
 }
 private void SendMessage( WsMessage message, BindingContext ctx )
 {
     m_binding.Elements.ProcessOutputMessage( ref message, ctx );
 }
        /// <summary>
        /// Processes a message
        /// </summary>
        /// <param name="msg">The message being processed.</param>
        /// <param name="ctx">The context associated with the message.</param>
        /// <returns>The handling status for this operation.</returns>
        protected override ChainResult OnProcessOutputMessage(ref WsMessage msg, BindingContext ctx)
        {
            ArrayList props = ctx.BindingProperties;

            byte[] message     = null;
            string contentType = "text/plain";

            if (msg != null)
            {
                message = msg.Body as byte[];

                if (msg.BodyParts != null)
                {
                    contentType = "Multipart/Related;boundary=" +
                                  msg.MtomPropeties.boundary +
                                  ";type=\"application/xop+xml\";start=\"" +
                                  msg.MtomPropeties.start +
                                  "\";start-info=\"application/soap+xml\"";

                    ctx.BindingProperties.Add(new BindingProperty("header", HttpKnownHeaderNames.Server, "Microsoft-MF HTTP 1.0"));
                    ctx.BindingProperties.Add(new BindingProperty("header", HttpKnownHeaderNames.MimeVersion, "1.0"));
                    ctx.BindingProperties.Add(new BindingProperty("header", HttpKnownHeaderNames.Date, DateTime.Now.ToString()));
                }
                else
                {
                    contentType = "application/soap+xml; charset=utf-8";
                }
            }

            if (ctx is ClientBindingContext)
            {
                if (message == null)
                {
                    return(ChainResult.Abort);
                }

                HttpWebRequest request = HttpWebRequest.Create(new Uri(m_endpointUri.AbsoluteUri)) as HttpWebRequest;
                request.Timeout          = (int)(ctx.OpenTimeout.Ticks / TimeSpan.TicksPerMillisecond);
                request.ReadWriteTimeout = (int)(ctx.ReceiveTimeout.Ticks / TimeSpan.TicksPerMillisecond);

                ctx.ContextObject = request;

                // Post method
                request.Method = "POST";

                WebHeaderCollection headers = request.Headers;

                request.ContentType = contentType;
                request.UserAgent   = "MFWsAPI";

                request.Headers.Add(HttpKnownHeaderNames.CacheControl, "no-cache");
                request.Headers.Add(HttpKnownHeaderNames.Pragma, "no-cache");

                if (props != null)
                {
                    int len = props.Count;
                    for (int i = 0; i < len; i++)
                    {
                        BindingProperty prop      = (BindingProperty)props[i];
                        string          container = prop.Container;

                        if (container == "header")
                        {
                            headers.Add(prop.Name, (string)prop.Value);
                        }
                        else if (container == null || container == "")
                        {
                            string name = prop.Name;

                            if (name == HttpKnownHeaderNames.ContentType)
                            {
                                request.ContentType = (string)prop.Value;
                            }
                            else if (name == HttpKnownHeaderNames.UserAgent)
                            {
                                request.UserAgent = (string)prop.Value;
                            }
                        }
                    }
                }

                if (message != null)
                {
                    System.Ext.Console.Write("Http message sent: ");
                    System.Ext.Console.Write(message);

                    request.ContentLength = message.Length;

                    using (Stream stream = request.GetRequestStream())
                    {
                        // Write soap message
                        stream.Write(message, 0, message.Length);

                        // Flush the stream and force a write
                        stream.Flush();
                    }
                }
            }
            else
            {
                HttpListenerContext listenerContext = ctx.ContextObject as HttpListenerContext;

                if (listenerContext == null)
                {
                    return(ChainResult.Abort);
                }

                HttpListenerResponse listenerResponse = listenerContext.Response;

                if (listenerResponse == null || listenerResponse.OutputStream == null)
                {
                    return(ChainResult.Abort);
                }

                try
                {
                    using (StreamWriter streamWriter = new StreamWriter(listenerResponse.OutputStream))
                    {
                        // Write Header, if message is null write accepted
                        if (message == null)
                        {
                            listenerResponse.StatusCode = 202;
                        }
                        else
                        {
                            listenerResponse.StatusCode = 200;
                        }

                        // Check to see it the hosted service is sending mtom
                        WebHeaderCollection headers = listenerResponse.Headers;

                        listenerResponse.ContentType = contentType;

                        bool isChunked = false;

                        if (props != null)
                        {
                            int len = props.Count;
                            for (int i = 0; i < len; i++)
                            {
                                BindingProperty prop      = (BindingProperty)props[i];
                                string          container = prop.Container;
                                string          name      = prop.Name;
                                string          value     = (string)prop.Value;

                                if (container == "header")
                                {
                                    if (!isChunked && name == HttpKnownHeaderNames.TransferEncoding && value.ToLower() == "chunked")
                                    {
                                        isChunked = true;
                                    }

                                    headers.Add(name, (string)prop.Value);
                                }
                                else if (container == null || container == "")
                                {
                                    if (name == HttpKnownHeaderNames.ContentType)
                                    {
                                        listenerResponse.ContentType = (string)prop.Value;
                                        System.Ext.Console.Write(HttpKnownHeaderNames.ContentType + ": " + listenerResponse.ContentType);
                                    }
                                }
                            }
                        }

                        // If chunked encoding is enabled write chunked message else write Content-Length
                        if (isChunked)
                        {
                            // Chunk message
                            int bufferIndex      = 0;
                            int chunkSize        = 0;
                            int defaultChunkSize = 0xff;
#if DEBUG
                            byte[] displayBuffer = new byte[defaultChunkSize];
#endif
                            while (bufferIndex < message.Length)
                            {
                                // Calculate chunk size and write to stream
                                chunkSize = message.Length - bufferIndex < defaultChunkSize ? message.Length - bufferIndex : defaultChunkSize;
                                streamWriter.WriteLine(chunkSize.ToString("{0:X}"));
                                System.Ext.Console.Write(chunkSize.ToString("{0:X}"));

                                // Write chunk
                                streamWriter.WriteBytes(message, bufferIndex, chunkSize);
                                streamWriter.WriteLine();
#if DEBUG
                                Array.Copy(message, bufferIndex, displayBuffer, 0, chunkSize);
                                System.Ext.Console.Write(displayBuffer, bufferIndex, chunkSize);
#endif

                                // Adjust buffer index
                                bufferIndex = bufferIndex + chunkSize;
                            }

                            // Write 0 length and blank line
                            streamWriter.WriteLine("0");
                            streamWriter.WriteLine();
                            System.Ext.Console.Write("0");
                            System.Ext.Console.Write("");
                        }
                        else
                        {
                            if (message == null)
                            {
                                listenerResponse.ContentLength64 = 0;
                            }
                            else
                            {
                                listenerResponse.ContentLength64 = message.Length;
                            }

                            System.Ext.Console.Write("Content Length: " + listenerResponse.ContentLength64);

                            // If an empty message is returned (i.e. oneway request response) don't send
                            if (message != null && message.Length > 0)
                            {
                                System.Ext.Console.Write(message);

                                // Write soap message
                                streamWriter.WriteBytes(message, 0, message.Length);
                            }
                        }

                        // Flush the stream and return
                        streamWriter.Flush();
                    }
                }
                catch
                {
                    return(ChainResult.Abort);
                }
                finally
                {
                    listenerContext.Close();
                }
            }
            return(ChainResult.Handled);
        }
        /// <summary>
        /// Closes the stream for the HTTP transport binding
        /// </summary>
        /// <param name="stream">The stream for this binding.</param>
        /// <param name="ctx">The context associated with the stream for this binding.</param>
        /// <returns>The handling status for this operation.</returns>
        protected override ChainResult OnClose( Stream stream, BindingContext ctx )
        {
            if (ctx is ServerBindingContext)
            {
                if (ctx.ContextObject != null)
                {
                    ((HttpListenerContext)ctx.ContextObject).Close();
                    ctx.ContextObject = null;
                }

                if (m_httpListener != null)
                {
                    m_httpListener.Close();
                    m_httpListener = null;
                }
            }
            else
            {
                if(ctx.ContextObject != null)
                {
                    ((HttpWebRequest)ctx.ContextObject).Dispose();
                    ctx.ContextObject = null;
                }
            }

            return ChainResult.Handled;
        }
        /// <summary>
        /// Processes a message
        /// </summary>
        /// <param name="stream">The message being processed.</param>
        /// <param name="ctx">The context associated with the message.</param>
        /// <returns>The handling status for this operation.</returns>
        protected override ChainResult OnProcessInputMessage(ref WsMessage msg, BindingContext ctx)
        {
            byte[] soapResponse         = null;
            WebHeaderCollection headers = null;

            if (ctx is ClientBindingContext)
            {
                HttpWebRequest request = ctx.ContextObject as HttpWebRequest;

                if (request == null)
                {
                    msg = null;
                    return(ChainResult.Abort);
                }

                HttpWebResponse resp = request.GetResponse() as HttpWebResponse;

                if (resp == null)
                {
                    throw new WebException("", WebExceptionStatus.ReceiveFailure); // No response was received on the HTTP channel
                }

                try
                {
                    headers = (System.Net.WebHeaderCollection)resp.Headers;

                    if (resp.ProtocolVersion != HttpVersion.Version11)
                    {
                        throw new IOException(); // Invalid http version in response line.
                    }

                    if (resp.StatusCode != HttpStatusCode.OK && resp.StatusCode != HttpStatusCode.Accepted)
                    {
                        throw new IOException(); // Bad status code in response
                    }

                    if (resp.ContentLength > 0)
                    {
                        // Return the soap response.
                        soapResponse = new byte[(int)resp.ContentLength];
                        Stream respStream = resp.GetResponseStream();

                        respStream.ReadTimeout = (int)(ctx.ReceiveTimeout.Ticks / TimeSpan.TicksPerMillisecond);

                        // Now need to read all data. We read in the loop until resp.ContentLength or zero bytes read.
                        // Zero bytes read means there was error on server and it did not send all data.
                        int respLength = (int)resp.ContentLength;
                        for (int totalBytesRead = 0; totalBytesRead < respLength;)
                        {
                            int bytesRead = respStream.Read(soapResponse, totalBytesRead, (int)resp.ContentLength - totalBytesRead);
                            // If nothing is read - means server closed connection or timeout. In this case no retry.
                            if (bytesRead == 0)
                            {
                                break;
                            }

                            // Adds number of bytes read on this iteration.
                            totalBytesRead += bytesRead;
                        }

                        headers = resp.Headers;
                    }
                    //
                    // ContentLenght == 0 is OK
                    //
                    else if (resp.ContentLength < 0)
                    {
                        throw new ProtocolViolationException(); // Invalid http header, content lenght < 0
                    }
                }
                finally
                {
                    resp.Dispose();
                    request.Dispose();
                    ctx.ContextObject = null;
                }
            }
            else // server waits for messages
            {
                if (m_httpListener == null)
                {
                    msg = null;
                    return(ChainResult.Abort);
                }

                HttpListenerContext listenerContext = m_httpListener.GetContext();

                if (listenerContext == null)
                {
                    msg = null;
                    return(ChainResult.Abort);
                }

                ctx.ContextObject = listenerContext;

                // The context returned by m_httpListener.GetContext(); can be null in case the service was stopped.
                HttpListenerRequest listenerRequest = listenerContext.Request;

                HttpListenerResponse listenerResponse = listenerContext.Response;

                listenerRequest.InputStream.ReadTimeout    = (int)(ctx.ReceiveTimeout.Ticks / TimeSpan.TicksPerMillisecond);
                listenerResponse.OutputStream.WriteTimeout = (int)(ctx.SendTimeout.Ticks / TimeSpan.TicksPerMillisecond);

                headers = (System.Net.WebHeaderCollection)listenerRequest.Headers;

                System.Ext.Console.Write("Request From: " + listenerRequest.RemoteEndPoint.ToString());

                // Checks and process headers important for DPWS
                if (!ProcessKnownHeaders(listenerContext))
                {
                    msg = null;
                    return(ChainResult.Abort);
                }

                soapResponse = null;

                int messageLength = (int)listenerRequest.ContentLength64;
                if (messageLength > 0)
                {
                    // If there is content length for the message, we read it complete.
                    soapResponse = new byte[messageLength];

                    for (int offset = 0; offset < messageLength;)
                    {
                        int noRead = listenerRequest.InputStream.Read(soapResponse, offset, messageLength - offset);
                        if (noRead == 0)
                        {
                            throw new IOException("Http server got only " + offset + " bytes. Expected to read " + messageLength + " bytes.");
                        }

                        offset += noRead;
                    }
                }
                else
                {
                    // In this case the message is chunk encoded, but m_httpRequest.InputStream actually does processing.
                    // So we we read until zero bytes are read.
                    bool readComplete = false;
                    int  bufferSize   = ReadPayload;
                    soapResponse = new byte[bufferSize];
                    int offset = 0;
                    while (!readComplete)
                    {
                        while (offset < ReadPayload)
                        {
                            int noRead = listenerRequest.InputStream.Read(soapResponse, offset, messageLength - offset);
                            // If we read zero bytes - means this is end of message. This is how InputStream.Read for chunked encoded data.
                            if (noRead == 0)
                            {
                                readComplete = true;
                                break;
                            }

                            offset += noRead;
                        }

                        // If read was not complete - increase the buffer.
                        if (!readComplete)
                        {
                            bufferSize += ReadPayload;
                            byte[] newMessageBuf = new byte[bufferSize];
                            Array.Copy(soapResponse, newMessageBuf, offset);
                            soapResponse = newMessageBuf;
                        }
                    }
                }
            }

            if (headers != null)
            {
                string [] keys = headers.AllKeys;
                int       len  = keys.Length;

                ArrayList props = ctx.BindingProperties;

                for (int i = 0; i < len; i++)
                {
                    string key = keys[i];

                    props.Add(new BindingProperty("header", key, headers[key]));
                }
            }

            System.Ext.Console.Write(soapResponse);


            msg.Body = soapResponse;

            return(ChainResult.Continue);
        }
        /// <summary>
        /// Opens the stream for the UDP tansport binding 
        /// </summary>
        /// <param name="stream">The stream for this binding.</param>
        /// <param name="ctx">The context associated with the stream for this binding.</param>
        /// <returns>The handling status for this operation.</returns>
        protected override ChainResult OnOpen( ref Stream stream, BindingContext ctx )
        {
            m_udpReceiveClient = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
            IPEndPoint localEP = new IPEndPoint(IPAddress.Any, m_config.DiscoveryPort);
            m_udpReceiveClient.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
            m_udpReceiveClient.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReceiveBuffer, 0x5000);
            m_udpReceiveClient.Bind(localEP);
            // Join Multicast Group
            byte[] discoveryAddr = m_config.DiscoveryAddress.GetAddressBytes();
            byte[] multicastOpt = new byte[] { discoveryAddr[0], discoveryAddr[1], discoveryAddr[2], discoveryAddr[3],   // WsDiscovery Multicast Address: 239.255.255.250
                                                 0,   0,   0,   0 }; // IPAddress.Any: 0.0.0.0
            m_udpReceiveClient.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.AddMembership, multicastOpt);

            // Create a UdpClient used to send request responses. Set SendTimeout.
            m_udpSendClient = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
            m_udpSendClient.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);

            return ChainResult.Continue;
        }
        /// <summary>
        /// Processes a message 
        /// </summary>
        /// <param name="msg">The message being processed.</param>
        /// <param name="ctx">The context associated with the message.</param>
        /// <returns>The handling status for this operation.</returns>
        protected override ChainResult OnProcessOutputMessage( ref WsMessage msg, BindingContext ctx )
        {
            if (m_remoteEndpoint == null) throw new Exception();

            byte []message = msg.Body as byte[];

            if (message == null) return ChainResult.Abort;

            System.Ext.Console.Write("UDP Message Sent To: " + m_remoteEndpoint.ToString());
            System.Ext.Console.Write(message);
            
            try
            {
                Random rand = new Random();
                for (int i = 0; i < 3; ++i)
                {
                    int backoff = rand.Next(200) + 50; // 50-250
                    System.Threading.Thread.Sleep(backoff);
                    m_udpSendClient.SendTo(message, message.Length, SocketFlags.None, m_remoteEndpoint);
                }
            }
            catch
            {
                return ChainResult.Abort;
            }

            return ChainResult.Handled;
        }
        /// <summary>
        /// Closes the stream for the UDP transport binding
        /// </summary>
        /// <param name="stream">The stream for this binding.</param>
        /// <param name="ctx">The context associated with the stream for this binding.</param>
        /// <returns>The handling status for this operation.</returns>
        protected override ChainResult OnClose( Stream stream, BindingContext ctx )
        {
            m_udpReceiveClient.Close();
            m_udpSendClient.Close();

            return ChainResult.Handled;
        }
Beispiel #18
0
 internal ReplyChannel(Binding binding, BindingContext context)
 {
     this.m_binding = binding;
     this.m_context = context;
 }
        /// <summary>
        /// Processes an input message for a specific context
        /// </summary>
        /// <param name="message">The message to process.</param>
        /// <param name="ctx">The context to process the message for.</param>
        /// <returns><c>true</c> if the processing was succesfull, <c>false</c>otherwise.</returns>
        public bool ProcessInputMessage(ref WsMessage message, BindingContext ctx)
        {
            if (m_Next != null)
            {
                if(!m_Next.ProcessInputMessage( ref message, ctx ))
                {
                    return false;
                }
            }

            return this.OnProcessInputMessage(ref message, ctx) != ChainResult.Abort;
        }
        /// <summary>
        /// Closes the stream for the HTTP transport binding
        /// </summary>
        /// <param name="stream">The stream for this binding.</param>
        /// <param name="ctx">The context associated with the stream for this binding.</param>
        /// <returns>The handling status for this operation.</returns>
        protected override ChainResult OnClose( Stream stream, BindingContext ctx )
        {
            if (ctx is ServerBindingContext)
            {
                if (ctx.ContextObject != null && ctx.ContextObject is HttpListenerContext)
                {
                    try
                    {
                        ((HttpListenerContext)ctx.ContextObject).Close(ctx.CloseTimeout.Seconds);
                    }
                    catch
                    {
                    }
                    ctx.ContextObject = null;
                }

                if (m_httpListener != null)
                {
                    try
                    {
                        m_httpListener.Close();
                    }
                    catch
                    {
                    }
                    m_httpListener = null;
                }
            }
            else
            {
                if(ctx.ContextObject != null && ctx.ContextObject is IDisposable)
                {
                    try
                    {
                        ((IDisposable)ctx.ContextObject).Dispose();
                    }
                    catch
                    {
                    }
                    ctx.ContextObject = null;
                }
            }

            return ChainResult.Handled;
        }
        private WsMessage ReceiveMessage( object deserializer, BindingContext ctx )
        {
            WsMessage msg = new WsMessage( new WsWsaHeader(), null, WsPrefix.None );

            msg.Deserializer = deserializer;

            // reset the binding properties as they likely were modified during the Send
            ctx.BindingProperties = (ArrayList)m_context.BindingProperties.Clone();

            m_binding.Elements.ProcessInputMessage( ref msg, ctx );

            return msg;
        }
        /// <summary>
        /// Processes a message
        /// </summary>
        /// <param name="stream">The message being processed.</param>
        /// <param name="ctx">The context associated with the message.</param>
        /// <returns>The handling status for this operation.</returns>
        protected override ChainResult OnProcessInputMessage(ref WsMessage msg, BindingContext ctx)
        {
            byte[] soapResponse = null;
            WebHeaderCollection headers = null;

            if(ctx is ClientBindingContext)
            {                
                HttpWebRequest request = ctx.ContextObject as HttpWebRequest;
                
                if(request == null)
                {
                    msg = null;
                    return ChainResult.Abort;
                }

                HttpWebResponse resp = request.GetResponse() as HttpWebResponse;
               
                if (resp == null)
                {
                    throw new WebException("", WebExceptionStatus.ReceiveFailure); // No response was received on the HTTP channel
                }
                
                try                    
                {
                    headers = (System.Net.WebHeaderCollection)resp.Headers;

                    if (resp.ProtocolVersion != HttpVersion.Version11)
                    {
                        throw new IOException(); // Invalid http version in response line.
                    }
            
                    if (resp.StatusCode != HttpStatusCode.OK && resp.StatusCode != HttpStatusCode.Accepted)
                    {
                        if (resp.ContentType.IndexOf("application/soap+xml") == -1)
                        {
                            throw new IOException(); // Bad status code in response
                        }
                    }
            
                    if (resp.ContentLength > 0)
                    {
                        
                        // Return the soap response.
                        soapResponse        = new byte[(int)resp.ContentLength];
                        Stream respStream   = resp.GetResponseStream();

                        respStream.ReadTimeout = (int)(ctx.ReceiveTimeout.Ticks / TimeSpan.TicksPerMillisecond);
            
                        // Now need to read all data. We read in the loop until resp.ContentLength or zero bytes read.
                        // Zero bytes read means there was error on server and it did not send all data.
                        int respLength = (int)resp.ContentLength;
                        for (int totalBytesRead = 0; totalBytesRead < respLength; )
                        {
                            int bytesRead = respStream.Read(soapResponse, totalBytesRead, (int)resp.ContentLength - totalBytesRead);
                            // If nothing is read - means server closed connection or timeout. In this case no retry.
                            if (bytesRead == 0)
                            {
                                break;
                            }
            
                            // Adds number of bytes read on this iteration.
                            totalBytesRead += bytesRead;
                        }

                        headers = resp.Headers;
                    }
                    //
                    // ContentLenght == 0 is OK
                    //
                    else if(resp.ContentLength < 0)
                    {
                        throw new ProtocolViolationException(); // Invalid http header, content lenght < 0
                    }
                }
                finally
                {
                    resp.Dispose();
                    request.Dispose();
                    ctx.ContextObject = null;
                }
            }
            else // server waits for messages
            {
                HttpListenerContext listenerContext;

                try
                {
                    if(m_persistConn && ctx.ContextObject != null)
                    {
                        listenerContext = (HttpListenerContext)ctx.ContextObject;

                        listenerContext.Reset();
                    }
                    else
                    {
                        if (m_httpListener == null)
                        {
                            msg = null;
                            return ChainResult.Abort;
                        }

                        listenerContext = m_httpListener.GetContext();
                    }

                    if (listenerContext == null)
                    {
                        msg = null;
                        return ChainResult.Abort;
                    }

                    ctx.ContextObject = listenerContext;

                    // The context returned by m_httpListener.GetContext(); can be null in case the service was stopped.
                    HttpListenerRequest listenerRequest = listenerContext.Request;

                    HttpListenerResponse listenerResponse = listenerContext.Response;

                    listenerRequest.InputStream.ReadTimeout = (int)(ctx.ReceiveTimeout.Ticks / TimeSpan.TicksPerMillisecond);
                    listenerResponse.OutputStream.WriteTimeout = (int)(ctx.SendTimeout.Ticks / TimeSpan.TicksPerMillisecond);

                    headers = (System.Net.WebHeaderCollection)listenerRequest.Headers;

                    System.Ext.Console.Write("Request From: " + listenerRequest.RemoteEndPoint.ToString());

                    // Checks and process headers important for DPWS
                    if (!ProcessKnownHeaders(listenerContext))
                    {
                        msg = null;
                        return ChainResult.Abort;
                    }

                    soapResponse = null;

                    int messageLength = (int)listenerRequest.ContentLength64;
                    if (messageLength > 0)
                    {
                        // If there is content length for the message, we read it complete.
                        soapResponse = new byte[messageLength];

                        for (int offset = 0; offset < messageLength; )
                        {
                            int noRead = listenerRequest.InputStream.Read(soapResponse, offset, messageLength - offset);
                            if (noRead == 0)
                            {
                                throw new IOException("Http server got only " + offset + " bytes. Expected to read " + messageLength + " bytes.");
                            }

                            offset += noRead;
                        }
                    }
                    else
                    {
                        // In this case the message is chunk encoded, but m_httpRequest.InputStream actually does processing.
                        // So we we read until zero bytes are read.
                        bool readComplete = false;
                        int bufferSize = ReadPayload;
                        soapResponse = new byte[bufferSize];
                        int offset = 0;
                        while (!readComplete)
                        {
                            while (offset < ReadPayload)
                            {
                                int noRead = listenerRequest.InputStream.Read(soapResponse, offset, messageLength - offset);
                                // If we read zero bytes - means this is end of message. This is how InputStream.Read for chunked encoded data.
                                if (noRead == 0)
                                {
                                    readComplete = true;
                                    break;
                                }

                                offset += noRead;
                            }

                            // If read was not complete - increase the buffer.
                            if (!readComplete)
                            {
                                bufferSize += ReadPayload;
                                byte[] newMessageBuf = new byte[bufferSize];
                                Array.Copy(soapResponse, newMessageBuf, offset);
                                soapResponse = newMessageBuf;
                            }
                        }
                    }                
                }
                catch
                {
                    ctx.ContextObject = null;
                    throw;
                }
            }

            if(headers != null)
            {
                string [] keys = headers.AllKeys;
                int       len  = keys.Length;

                ArrayList props = ctx.BindingProperties;
                
                for(int i=0; i<len; i++)
                {
                    string key = keys[i];
                    
                    if(!WebHeaderCollection.IsRestricted(key))
                    {
                        props.Add( new BindingProperty( "header", key, headers[key] ) );
                    }
                }
            }

            System.Ext.Console.Write(soapResponse);
         

            msg.Body = soapResponse;

            return ChainResult.Continue;
        }
 internal ReplyChannel( Binding binding, BindingContext context )
 {
     this.m_binding = binding;
     this.m_context = context;
 }
        /// <summary>
        /// Processes a message 
        /// </summary>
        /// <param name="msg">The message being processed.</param>
        /// <param name="ctx">The context associated with the message.</param>
        /// <returns>The handling status for this operation.</returns>
        protected override ChainResult OnProcessOutputMessage( ref WsMessage msg, BindingContext ctx )
        {
            ArrayList props = ctx.BindingProperties;

            byte[] message = null;
            string contentType = "text/plain";

            if (msg != null)
            {
                message = msg.Body as byte[];

                if (msg.BodyParts != null)
                {
                    contentType = "Multipart/Related;boundary=" +
                        msg.MtomPropeties.boundary +
                        ";type=\"application/xop+xml\";start=\"" +
                        msg.MtomPropeties.start +
                        "\";start-info=\"application/soap+xml\"";

                    ctx.BindingProperties.Add(new BindingProperty("header", HttpKnownHeaderNames.Server, "Microsoft-MF HTTP 1.0"));
                    ctx.BindingProperties.Add(new BindingProperty("header", HttpKnownHeaderNames.MimeVersion, "1.0"));
                    ctx.BindingProperties.Add(new BindingProperty("header", HttpKnownHeaderNames.Date, DateTime.Now.ToString()));
                }
                else
                {
                    contentType = "application/soap+xml; charset=utf-8";
                }
            }

            if(ctx is ClientBindingContext)
            {
                if (message == null) return ChainResult.Abort;

                HttpWebRequest request;

                try
                {
                    if(!m_persistConn || ctx.ContextObject == null)
                    {
                        request = HttpWebRequest.Create(new Uri(m_transportUri.AbsoluteUri)) as HttpWebRequest;
                        request.Timeout          = (int)(ctx.OpenTimeout.Ticks / TimeSpan.TicksPerMillisecond);
                        request.ReadWriteTimeout = (int)(ctx.ReceiveTimeout.Ticks / TimeSpan.TicksPerMillisecond);

                        ctx.ContextObject = request;
                    }
                    else
                    {
                        request = (HttpWebRequest)ctx.ContextObject;

                        request.Reset();
                    }
                    

                    // Post method
                    request.Method = "POST";

                    WebHeaderCollection headers = request.Headers;

                    request.ContentType = contentType;
                    request.UserAgent   = "MFWsAPI";

                    request.Headers.Add(HttpKnownHeaderNames.CacheControl, "no-cache");
                    request.Headers.Add(HttpKnownHeaderNames.Pragma      , "no-cache");

                    if (props != null)
                    {
                        int len = props.Count;
                        for (int i = 0; i < len; i++)
                        {
                            BindingProperty prop = (BindingProperty)props[i];
                            string container = prop.Container;

                            if (container == "header")
                            {
                                headers.Add(prop.Name, (string)prop.Value);
                            }
                            else if (container == null || container == "")
                            {
                                string name = prop.Name;

                                if (name == HttpKnownHeaderNames.ContentType)
                                {
                                    request.ContentType = (string)prop.Value;
                                }
                                else if (name == HttpKnownHeaderNames.UserAgent)
                                {
                                    request.UserAgent = (string)prop.Value;
                                }
                            }
                        }
                    }

                    if (message != null)
                    {
                        System.Ext.Console.Write("Http message sent: ");
                        System.Ext.Console.Write(message);

                        request.ContentLength = message.Length;

                        using (Stream stream = request.GetRequestStream())
                        {
                            // Write soap message
                            stream.Write(message, 0, message.Length);

                            // Flush the stream and force a write
                            stream.Flush();
                        }
                    }
                }
                catch
                {
                    ctx.ContextObject = null;

                    throw;
                }
            }
            else
            {
                HttpListenerContext listenerContext = ctx.ContextObject as HttpListenerContext;

                if(listenerContext == null) return ChainResult.Abort;
                
                HttpListenerResponse listenerResponse = listenerContext.Response;

                if (listenerResponse == null || listenerResponse.OutputStream == null)
                {
                    ctx.ContextObject = null;
                    return ChainResult.Abort;
                }

                try
                {
                    StreamWriter streamWriter = new StreamWriter(listenerResponse.OutputStream);

                    // Write Header, if message is null write accepted
                    if (message == null || (msg != null && msg.Header != null && msg.Header.IsFaultMessage))
                        listenerResponse.StatusCode = 202;
                    else
                        listenerResponse.StatusCode = 200;

                    // Check to see it the hosted service is sending mtom
                    WebHeaderCollection headers = listenerResponse.Headers;

                    listenerResponse.ContentType = contentType;

                    bool isChunked = false;

                    if (props != null)
                    {
                        int len = props.Count;
                        for (int i = 0; i < len; i++)
                        {
                            BindingProperty prop = (BindingProperty)props[i];
                            string container = prop.Container;
                            string name = prop.Name;
                            string value = (string)prop.Value;

                            if (container == "header")
                            {
                                if (!isChunked && name == HttpKnownHeaderNames.TransferEncoding && value.ToLower() == "chunked")
                                {
                                    isChunked = true;
                                }

                                headers.Add(name, (string)prop.Value);
                            }
                            else if (container == null || container == "")
                            {
                                if (name == HttpKnownHeaderNames.ContentType)
                                {
                                    listenerResponse.ContentType = (string)prop.Value;
                                    System.Ext.Console.Write(HttpKnownHeaderNames.ContentType + ": " + listenerResponse.ContentType);
                                }
                            }
                        }
                    }

                    // If chunked encoding is enabled write chunked message else write Content-Length
                    if (isChunked)
                    {
                        // Chunk message
                        int bufferIndex = 0;
                        int chunkSize = 0;
                        int defaultChunkSize = 0xff;
#if DEBUG
                    byte[] displayBuffer = new byte[defaultChunkSize];
#endif
                        while (bufferIndex < message.Length)
                        {

                            // Calculate chunk size and write to stream
                            chunkSize = message.Length - bufferIndex < defaultChunkSize ? message.Length - bufferIndex : defaultChunkSize;
                            streamWriter.WriteLine(chunkSize.ToString("{0:X}"));
                            System.Ext.Console.Write(chunkSize.ToString("{0:X}"));

                            // Write chunk
                            streamWriter.WriteBytes(message, bufferIndex, chunkSize);
                            streamWriter.WriteLine();
#if DEBUG
                            Array.Copy(message, bufferIndex, displayBuffer, 0, chunkSize);
                            System.Ext.Console.Write(displayBuffer, bufferIndex, chunkSize);
#endif

                            // Adjust buffer index
                            bufferIndex = bufferIndex + chunkSize;
                        }

                        // Write 0 length and blank line
                        streamWriter.WriteLine("0");
                        streamWriter.WriteLine();
                        System.Ext.Console.Write("0");
                        System.Ext.Console.Write("");

                    }
                    else
                    {
                        if (message == null)
                        {
                            listenerResponse.ContentLength64 = 0;
                        }
                        else
                        {
                            listenerResponse.ContentLength64 = message.Length;
                        }

                        System.Ext.Console.Write("Content Length: " + listenerResponse.ContentLength64);

                        // If an empty message is returned (i.e. oneway request response) don't send
                        if (message != null && message.Length > 0)
                        {
                            System.Ext.Console.Write(message);

                            // Write soap message
                            streamWriter.WriteBytes(message, 0, message.Length);
                        }
                    }

                    // Flush the stream and return
                    streamWriter.Flush();

                }
                catch
                {
                    return ChainResult.Abort;
                }
                finally
                {
                    if (m_persistConn)
                    {
                        listenerResponse.Detach();
                    }
                    else
                    {
                        listenerContext.Close( ctx.CloseTimeout.Seconds );
                        ctx.ContextObject = null;
                    }
                }
            }
            return ChainResult.Handled;
        }
        internal WsMessage ReceiveMessage( BindingContext ctx )
        {
            WsMessage msg = new WsMessage(new WsWsaHeader(), null, WsPrefix.None);

            while(true)
            {
                try
                {
                    if (!m_binding.Elements.ProcessInputMessage(ref msg, ctx))
                    {
                        msg = null;
                    }
                    // only need to loop if we received a bad request
                    break;
                }
                catch (Faults.WsFaultException ex)
                {
                    WsMessage faultResp = Faults.WsFault.GenerateFaultResponse(ex, ctx.Version);

                    if (faultResp != null)
                    {
                        SendMessage(faultResp, ctx);
                    }
                }
            }

            return msg;
        }
 /// <summary>
 /// Signaled by the <ref>Open</ref> method to allow subclasses to create a sub-class of Binding Element with specific behaviour
 /// </summary>
 /// <param name="stream">The stream that is being opened.</param>
 /// <param name="ctx">The context for which the stream is being opened.</param>
 /// <returns>The handling status for this operation.</returns>
 protected virtual ChainResult OnOpen(ref Stream stream, BindingContext ctx) { return ChainResult.Continue; }
        /// <summary>
        /// Processes a message
        /// </summary>
        /// <param name="stream">The message being processed.</param>
        /// <param name="ctx">The context associated with the message.</param>
        /// <returns>The handling status for this operation.</returns>
        protected override ChainResult OnProcessInputMessage(ref WsMessage msg, BindingContext ctx)
        {
            if(msg == null) return ChainResult.Abort;
            
            ArrayList props = ctx.BindingProperties;
            if (props != null)
            {
                int len = props.Count;

                for (int j = 0; j < len; j++)
                {
                    BindingProperty prop = (BindingProperty)props[j];

                    if (prop.Name == HttpKnownHeaderNames.ContentType)
                    {
                        string strContentType = ((string)prop.Value).ToLower();

                        if (strContentType.IndexOf("multipart/related;") == 0)
                        {
                            // Create the mtom header class
                            msg.MtomPropeties = new WsMtomParams();

                            // Parse Mtom Content-Type parameters
                            string[] fields = strContentType.Substring(18).Split(';');
                            int fieldsLen = fields.Length;
                            for (int i = 0; i < fieldsLen; ++i)
                            {
                                string type = fields[i];
                                int idx = type.IndexOf('=');

                                if(idx != -1)
                                {
                                    string param = type.Substring(0, idx).Trim();
                                    string value = type.Substring(idx + 1).Trim('\"');
                                    switch (param.ToUpper())
                                    {
                                        case "BOUNDARY":
                                            if (param.Length > 72)
                                                throw new ArgumentException("Mime boundary element length exceeded.", "boundary");
                                            msg.MtomPropeties.boundary = value;
                                            break;
                                        case "TYPE":
                                            msg.MtomPropeties.type = value;
                                            break;
                                        case "START":
                                            msg.MtomPropeties.start = value;
                                            break;
                                        case "START-INFO":
                                            msg.MtomPropeties.startInfo = value;
                                            break;
                                        default:
                                            break;
                                    }
                                }
                            }

                            // Check required Mtom fields
                            if (msg.MtomPropeties.boundary == null || msg.MtomPropeties.type == null || msg.MtomPropeties.start == null)
                            {
                                throw new WsFaultException(msg.Header, WsFaultType.WseInvalidMessage);
                            }

                            WsMtom mtom = new WsMtom((byte[])msg.Body);

                            msg.Body = mtom.ParseMessage(msg.MtomPropeties.boundary);
                            msg.BodyParts = mtom.BodyParts;
                        }
                        else if (strContentType.IndexOf("application/soap+xml") != 0)
                        {
                            throw new WsFaultException(msg.Header, WsFaultType.WseInvalidMessage);
                        }
                    }
                }
            }

            if (msg.Body == null) return ChainResult.Continue;

            MemoryStream requestStream = new MemoryStream((byte[])msg.Body);

            XmlReader reader = XmlReader.Create(requestStream);
            WsWsaHeader hdr = new WsWsaHeader();

            reader.ReadStartElement("Envelope", WsWellKnownUri.SoapNamespaceUri);

            if(ctx.Version.IncludeSoapHeaders)
            {
                hdr.ParseHeader(reader, ctx.Version);
            }
            
            msg.Header = hdr;

            reader.ReadStartElement("Body", WsWellKnownUri.SoapNamespaceUri);


            if(msg.Deserializer != null)
            {
                msg.Body = ((DataContractSerializer)msg.Deserializer).ReadObject(reader);
                reader.Dispose();
            }
            else
            {
                msg.Reader = reader;
            }

            return ChainResult.Continue;
        }
 /// <summary>
 /// Signaled by the <ref>Close</ref> method to allow subclasses to create a sub-class of Binding Element with specific behaviour
 /// </summary>
 /// <param name="stream">The stream that is being closed.</param>
 /// <param name="ctx">The context for which the stream is being closed.</param>
 /// <returns>The handling status for this operation.</returns>
 protected virtual ChainResult OnClose(Stream stream, BindingContext ctx) { return ChainResult.Continue; }
        /// <summary>
        /// Closes the stream for the UDP transport binding
        /// </summary>
        /// <param name="stream">The stream for this binding.</param>
        /// <param name="ctx">The context associated with the stream for this binding.</param>
        /// <returns>The handling status for this operation.</returns>
        protected override ChainResult OnClose( Stream stream, BindingContext ctx )
        {
            m_udpReceiveClient.Close();

            s_udpTimer.Change(-1,-1);
            s_udpTimer.Dispose();

            return ChainResult.Handled;
        }
 /// <summary>
 /// Signaled by the <ref>ProcessInputMessage</ref> method to allow subclasses to create a sub-class of Binding Element with specific behaviour
 /// </summary>
 /// <param name="message">The message being processed.</param>
 /// <param name="ctx">The context associated with the message being processed.</param>
 /// <returns>The handling status for this operation.</returns>
 protected abstract ChainResult OnProcessInputMessage(ref WsMessage message, BindingContext ctx);
        /// <summary>
        /// Processes a message 
        /// </summary>
        /// <param name="msg">The message being processed.</param>
        /// <param name="ctx">The context associated with the message.</param>
        /// <returns>The handling status for this operation.</returns>
        protected override ChainResult OnProcessOutputMessage( ref WsMessage msg, BindingContext ctx )
        {
            if (ctx.ContextObject == null) throw new Exception();

            byte []message = msg.Body as byte[];

            if (message == null) return ChainResult.Abort;

            IPEndPoint epRemote = (IPEndPoint)ctx.ContextObject;

            if(!m_config.IgnoreRequestsFromThisIp && epRemote.Address == IPAddress.GetDefaultLocalAddress())
            {
                epRemote = new IPEndPoint(IPAddress.Loopback, epRemote.Port);
            }

            System.Ext.Console.Write("UDP Message Sent To: " + epRemote.ToString());
            System.Ext.Console.Write(message);
            
            try
            {
                /// Add a UDP repeat record to the current list of UDP responses to be processed
                /// by a common timer.
                lock (s_repeats)
                {
                    s_repeats.Add(new UdpResend(message, epRemote, 3));

                    if (s_repeats.Count == 1) s_udpTimer.Change(50, 100);
                }
            }
            catch
            {
                return ChainResult.Abort;
            }

            return ChainResult.Handled;
        }
Beispiel #32
0
 private void SendMessage(WsMessage message, BindingContext ctx)
 {
     m_binding.Elements.ProcessOutputMessage(ref message, ctx);
 }