/// <summary>
        /// Eventing UnSubscribe stub.
        /// </summary>
        /// <param name="header">Header object.</param>
        /// <param name="reader">An XmlReader positioned at the begining of the Unsubscribe request body element.</param>
        /// <param name="serviceEndpoints">A Collection of serviceEndpoints used to determine what services contain the specified event.</param>
        /// <returns>Byte array containing an UnSubscribe response.</returns>
        /// <remarks>This method is used by the stack framework. Do not use this method.</remarks>
        public WsMessage Unsubscribe(WsWsaHeader header, XmlReader reader, WsServiceEndpoints serviceEndpoints)
        {
            // Parse Unsubscribe Request
            ///////////////////////////////
            // there's no info in Unsubscribe that we actually need, just get the identifier from header
            String eventSinkID = header.Any.GetNodeValue("Identifier", WsWellKnownUri.WseNamespaceUri);

            bool eventSourceFound = false;
            if (eventSinkID != null)
            {

                // Parse urn:uuid from the To address
                string endpointAddress = FixToAddress(header.To);

                // Iterate the list of hosted services at the specified endpoint and unsubscribe from each event source
                // that matches the eventSinkID

                DpwsHostedService serv = (DpwsHostedService)Device.HostedServices[endpointAddress];

                if(serv != null)
                {
                    DpwsWseEventSources eventSources = serv.EventSources;
                        
                    // Delete Subscription
                    // Look for matching event in hosted services event sources
                    DpwsWseEventSource eventSource;
                    DpwsWseEventSinks eventSinks;
                    DpwsWseEventSink eventSink;
                    int eventSourcesCount = eventSources.Count;
                    int eventSinksCount;
                    for (int i = 0; i < eventSourcesCount; i++)
                    {
                        eventSource = eventSources[i];
                        eventSinks = eventSource.EventSinks;
                        eventSinksCount = eventSinks.Count;

                        eventSink = eventSinks[eventSinkID];
                        
                        if (eventSink != null)
                        {
                            eventSourceFound = true;
                            eventSource.EventSinks.Remove(eventSink);
                        }
                    }
                }

                if (eventSourceFound)
                {
                    // Generate Response
                    using(XmlMemoryWriter xmlWriter = XmlMemoryWriter.Create())
                    {
                        WsWsaHeader responseHeader = new WsWsaHeader(
                            WsWellKnownUri.WseNamespaceUri + "/UnsubscribeResponse",// Action
                            header.MessageID,                                       // RelatesTo
                            header.ReplyTo.Address.AbsoluteUri,                     // To
                            null, null, null);

                        WsMessage msg = new WsMessage( responseHeader, null, WsPrefix.Wse, null, 
                            new WsAppSequence(Device.AppSequence, Device.SequenceID, Device.MessageID));

                        WsSoapMessageWriter smw = new WsSoapMessageWriter(m_version);
                        smw.WriteSoapMessageStart(xmlWriter, msg);
                        smw.WriteSoapMessageEnd(xmlWriter);

                        // Return stream buffer
                        msg.Body = xmlWriter.ToArray();

                        return msg;
                    }
                }
            }

            // Something went wrong
            throw new WsFaultException(header, WsFaultType.WseEventSourceUnableToProcess);
        }
        private WsMessage GetStatusResponse(WsWsaHeader header, long newDuration)
        {
            using(XmlMemoryWriter xmlWriter = XmlMemoryWriter.Create())
            {
                WsWsaHeader responseHeader = new WsWsaHeader(
                    WsWellKnownUri.WseNamespaceUri + "/RenewResponse",      // Action
                    header.MessageID,                                       // RelatesTo
                    header.ReplyTo.Address.AbsoluteUri,                     // To
                    null, null, null);                                      // ReplyTo, From, Any

                WsMessage msg = new WsMessage(responseHeader, null, WsPrefix.Wse, null, 
                    new WsAppSequence(Device.AppSequence, Device.SequenceID, Device.MessageID));

                WsSoapMessageWriter smw = new WsSoapMessageWriter(m_version);
                smw.WriteSoapMessageStart(xmlWriter, msg);

                // write body
                xmlWriter.WriteStartElement(WsNamespacePrefix.Wse, "Expires", null);
                xmlWriter.WriteString(new WsDuration(newDuration).DurationString);
                xmlWriter.WriteEndElement(); // End Expires

                smw.WriteSoapMessageEnd(xmlWriter);

                msg.Body = xmlWriter.ToArray();

                return msg;
            }
        }
        /// <summary>
        /// This method build a subscription end message.
        /// </summary>
        /// <param name="eventSink">An event sink containing client endpoint information.</param>
        /// <param name="shutdownMessage">A string containing reason why the subscription is ending.</param>
        /// <param name="subMangerID">An id sent by the client that they use to reference a subscription.</param>
        /// <returns></returns>
        private WsMessage SubscriptionEndResponse(DpwsWseEventSink eventSink, string shutdownMessage, string subMangerID)
        {
            using(XmlMemoryWriter xmlWriter = XmlMemoryWriter.Create())
            {
                WsWsaHeader responseHeader = new WsWsaHeader(
                    WsWellKnownUri.WseNamespaceUri + "/SubscriptionEnd",    // Action
                    null,                                                   // RelatesTo
                    eventSink.EndTo.Address.AbsoluteUri,                    // To
                    null, null, eventSink.EndTo.RefProperties);             // ReplyTo, From, Any

                WsMessage msg = new WsMessage(responseHeader, null, WsPrefix.Wse, null, null);

                WsSoapMessageWriter smw = new WsSoapMessageWriter(m_version);
                smw.WriteSoapMessageStart(xmlWriter, msg);

                // write body
                xmlWriter.WriteStartElement(WsNamespacePrefix.Wse, "SubscriptionEnd", null);
                xmlWriter.WriteStartElement(WsNamespacePrefix.Wse, "SubscriptionManager", null);
                xmlWriter.WriteStartElement(WsNamespacePrefix.Wsa, "Address", null);
                xmlWriter.WriteString("http://" + Device.IPV4Address + ":" + Device.Port + "/" + subMangerID);
                xmlWriter.WriteEndElement(); // End Address
                xmlWriter.WriteStartElement(WsNamespacePrefix.Wsa, "ReferenceParameters", null);
                xmlWriter.WriteStartElement(WsNamespacePrefix.Wse, "Identifier", null);
                xmlWriter.WriteString(eventSink.ID);
                xmlWriter.WriteEndElement(); // End Identifier
                xmlWriter.WriteEndElement(); // End ReferenceParameters
                xmlWriter.WriteEndElement(); // End SubscriptionManager
                xmlWriter.WriteStartElement(WsNamespacePrefix.Wse, "Code", null);
                xmlWriter.WriteString(WsNamespacePrefix.Wse + ":" + shutdownMessage);
                xmlWriter.WriteEndElement(); // End Code
                xmlWriter.WriteEndElement(); // End SubscriptionEnd

                smw.WriteSoapMessageEnd(xmlWriter);

                msg.Body = xmlWriter.ToArray();

                // Return stream buffer
                return msg;
            }
        }
        /// <summary>
        /// Global eventing Subscribe stub.
        /// </summary>
        /// <param name="header">Header object.</param>
        /// <param name="reader">An XmlReader positioned at the begining of the Subscribe request body element.</param>
        /// <param name="serviceEndpoints">A Collection of serviceEndpoints used to determine what services contain the event source specified in the filter.</param>
        /// <returns>Byte array containing a Subscribe response.</returns>
        internal WsMessage Subscribe(WsWsaHeader header, XmlReader reader, WsServiceEndpoints serviceEndpoints)
        {
            WsMessage msg = null;

            // Parse Subscribe Request
            /////////////////////////////
            DpwsWseEventSink eventSink = new DpwsWseEventSink();
            try
            {
                reader.ReadStartElement("Subscribe", WsWellKnownUri.WseNamespaceUri);

                if (reader.IsStartElement("EndTo", WsWellKnownUri.WseNamespaceUri))
                {
                    eventSink.EndTo = new WsWsaEndpointRef(reader, m_version.AddressingNamespace);
                }

                reader.ReadStartElement("Delivery", WsWellKnownUri.WseNamespaceUri);
                if (reader.IsStartElement("NotifyTo", WsWellKnownUri.WseNamespaceUri))
                {
                    eventSink.NotifyTo = new WsWsaEndpointRef(reader, m_version.AddressingNamespace);
                }
                else
                {
                    throw new WsFaultException(header, WsFaultType.WseDeliverModeRequestedUnavailable);
                }

                reader.ReadEndElement();

                if (reader.IsStartElement("Expires", WsWellKnownUri.WseNamespaceUri))
                {
                    long expires = new WsDuration(reader.ReadElementString()).DurationInSeconds;

                    if (expires > 0)
                    {
                        eventSink.Expires = expires;
                    }
                    else
                    {
                        throw new WsFaultException(header, WsFaultType.WseInvalidExpirationTime);
                    }
                }
                else
                {
                    // Never Expires
                    eventSink.Expires = -1;
                }

                if (reader.IsStartElement("Filter", WsWellKnownUri.WseNamespaceUri))
                {
                    if (reader.MoveToAttribute("Dialect") == false || reader.Value != m_version.WsdpNamespaceUri + "/Action")
                    {
                        throw new WsFaultException(header, WsFaultType.WseFilteringRequestedUnavailable);
                    }

                    reader.MoveToElement();

                    String filters = reader.ReadElementString();

                    if (filters != String.Empty)
                    {
                        eventSink.Filters = filters.Split(' ');
                    }
                }

                XmlReaderHelper.SkipAllSiblings(reader);

                reader.ReadEndElement(); // Subscribe
            }
            catch (XmlException e)
            {
                throw new WsFaultException(header, WsFaultType.WseInvalidMessage, e.ToString());
            }

            // Parse urn:uuid from the To address
            string endpointAddress = FixToAddress(header.To);

            // Build a temporary collection of device services that match the specified endpoint address.
            WsServiceEndpoints matchingServices = new WsServiceEndpoints();
            for (int i = 0; i < serviceEndpoints.Count; ++i)
            {
                if (serviceEndpoints[i].EndpointAddress == endpointAddress)
                    matchingServices.Add(serviceEndpoints[i]);
            }

            // For each service with a matching endpoint and event sources add an event sink to the
            // event source collection
            for (int i = 0; i < matchingServices.Count; ++i)
            {
                DpwsWseEventSources eventSources = ((DpwsHostedService)matchingServices[i]).EventSources;

                // Set the EventSinkID
                eventSink.ID = "urn:uuid:" + Guid.NewGuid().ToString();

                // If subscribing to all event sources
                if (eventSink.Filters == null)
                {
                    int count = eventSources.Count;
                    for (int ii = 0; i < count; i++)
                    {
                        DpwsWseEventSource eventSource = eventSources[ii];
                        eventSink.StartTime = DateTime.Now.Ticks;
                        eventSource.EventSinks.Add(eventSink);
                    }
                }
                else
                {
                    // If subscribing to a specific event based on an event filter.
                    DpwsWseEventSource eventSource;
                    string[] filterList = eventSink.Filters;
                    int length = filterList.Length;
                    for (int ii = 0; i < length; i++)
                    {
                        if ((eventSource = eventSources[filterList[ii]]) != null)
                        {
                            eventSink.StartTime = DateTime.Now.Ticks;
                            eventSource.EventSinks.Add(eventSink);
                        }
                        else
                        {
                            throw new Exception("Event source " + filterList[ii] + " was not found.");
                        }
                    }
                }
            }

            // Generate Response
            //////////////////////////
            using(XmlMemoryWriter xmlWriter = XmlMemoryWriter.Create())
            {
                WsWsaHeader responseHeader = new WsWsaHeader(
                    WsWellKnownUri.WseNamespaceUri + "/SubscribeResponse",  // Action
                    header.MessageID,                                       // RelatesTo
                    header.ReplyTo.Address.AbsoluteUri,                     // To
                    null, null, null);                                      // ReplyTo, From, Any

                msg = new WsMessage(responseHeader, null, WsPrefix.Wse, null, 
                    new WsAppSequence(Device.AppSequence, Device.SequenceID, Device.MessageID));

                WsSoapMessageWriter smw = new WsSoapMessageWriter(m_version);
                smw.WriteSoapMessageStart(xmlWriter, msg);

                // write body
                xmlWriter.WriteStartElement(WsNamespacePrefix.Wse, "SubscribeResponse", null);
                xmlWriter.WriteStartElement(WsNamespacePrefix.Wse, "SubscriptionManager", null);
                xmlWriter.WriteStartElement(WsNamespacePrefix.Wsa, "Address", null);
                // Create a uri. Use the path (by default will be a uuid) for the sub manager endpoint
                Uri subMgrUri = new Uri(((DpwsHostedService)matchingServices[0]).EndpointAddress);
                xmlWriter.WriteString("http://" + Device.IPV4Address + ":" + Device.Port + "/" + subMgrUri.AbsolutePath);
                xmlWriter.WriteEndElement(); // End Address
                xmlWriter.WriteStartElement(WsNamespacePrefix.Wsa, "ReferenceParameters", null);
                xmlWriter.WriteStartElement(WsNamespacePrefix.Wse, "Identifier", null);
                xmlWriter.WriteString(eventSink.ID);
                xmlWriter.WriteEndElement(); // End Identifier
                xmlWriter.WriteEndElement(); // End ReferenceParameters
                xmlWriter.WriteEndElement(); // End SubscriptionManager
                xmlWriter.WriteStartElement(WsNamespacePrefix.Wse, "Expires", null);
                xmlWriter.WriteString(new WsDuration(eventSink.Expires).DurationString);
                xmlWriter.WriteEndElement(); // End Expires
                xmlWriter.WriteEndElement(); // End SubscribeResponse

                smw.WriteSoapMessageEnd(xmlWriter);

                // Return stream buffer
                msg.Body = xmlWriter.ToArray();
            }

            return msg;
        }
        /// <summary>
        /// Builds a probe request message based on the filters parameter.
        /// </summary>
        /// <param name="serviceAddress">
        /// A string containing the target service address.
        /// For example: urn:uuid:3cb0d1ba-cc3a-46ce-b416-212ac2419b20
        /// </param>
        /// <param name="filters">
        /// A DpwsServiceTypes object containing a collection of types a service must support to signal a match.
        /// Null = any type.
        /// </param>
        /// <param name="messageID">
        /// A string used to return the messageID assigned to this message.
        /// </param>
        /// <returns>A byte array containing the probe message or null if an error occures.</returns>
        private WsMessage BuildProbeRequest(string serviceAddress, DpwsServiceTypes filters, out String messageID)
        {
            // Performance debugging
            DebugTiming timeDebuger = new DebugTiming();
            long startTime = timeDebuger.ResetStartTime("");
            WsMessage msg = null;

            // Build Probe request
            using(XmlMemoryWriter xmlWriter = XmlMemoryWriter.Create())
            {
                WsWsaHeader header = new WsWsaHeader(
                    m_version.DiscoveryNamespace + "/Probe",      // Action
                    null,                                         // RelatesTo
                    serviceAddress,                               // To
                    m_version.AnonymousUri, null, null);          // ReplyTo, From, Any

                header.MustUnderstand = true;

                // If filters are supplied, write filter namespaces if prefixed. Build filter list for use later
                WsXmlNamespaces namespaces = new WsXmlNamespaces();

                // Prefix hack for now:
                int i = 0;
                string prefix;
                string filterList = "";
                bool spaceFlag = false;
                if (filters != null)
                {
                    int count = filters.Count;
                    for (int j = 0; j < count; j++)
                    {
                        DpwsServiceType serviceType = filters[j];
                        prefix = namespaces.LookupPrefix(serviceType.NamespaceUri);
                        if (prefix == null)
                        {
                            prefix = "dp" + (i++);
                            namespaces.Add(new WsXmlNamespace(prefix, serviceType.NamespaceUri));
                        }

                        filterList = filterList + ((spaceFlag == true) ? " " : "") + prefix + ":" + serviceType.TypeName;
                        spaceFlag = true;
                    }
                }

                msg = new WsMessage(header, null, WsPrefix.Wsd, namespaces, null);

                WsSoapMessageWriter smw = new WsSoapMessageWriter(m_version);
                messageID = smw.WriteSoapMessageStart(xmlWriter, msg);

                // Performance debuging
                timeDebuger.PrintElapsedTime("*****Write Header Took");

                // write body
                xmlWriter.WriteStartElement(WsNamespacePrefix.Wsd, "Probe", null);

                // If filter is supplied add filter types tag else write blank string to probe body, force an empty tag
                if (filterList.Length != 0)
                {
                    xmlWriter.WriteStartElement(WsNamespacePrefix.Wsd, "Types", null);
                    xmlWriter.WriteString(filterList);
                    xmlWriter.WriteEndElement(); // End Filter
                }
                else
                    xmlWriter.WriteString("");

                xmlWriter.WriteEndElement(); // End Probe

                // Performance debuging
                timeDebuger.PrintElapsedTime("*****Write Body Took");

                smw.WriteSoapMessageEnd(xmlWriter);

                // Performance debuging
                timeDebuger.PrintTotalTime(startTime, "***Probe Message Build Took");

                // return the probe message
                msg.Body = xmlWriter.ToArray();
            }

            return msg;
        }
        /// <summary>
        /// Builds a probe request message based on the filters parameter.
        /// </summary>
        /// <param name="targetServiceAddress">
        /// A string containing the target service address of a known service to resolve.
        /// For example: urn:uuid:3cb0d1ba-cc3a-46ce-b416-212ac2419b20
        /// </param>
        /// <param name="serviceAddress">
        /// A string containing the address of a service endpoint used to process the resolve request.
        /// For example: urn:uuid:22d0d1ba-cc3a-46ce-b416-212ac2419b20
        /// </param>
        /// <param name="messageID">
        /// A string reference used to store retreive the messageID from resolve message generation. The id is
        /// used to verify probe match responses for ad-hoc operation.
        /// </param>
        /// <returns>A byte array containing the resolve message or null if an error occures.</returns>
        private WsMessage BuildResolveRequest(string targetServiceAddress, string serviceAddress, ref String messageID)
        {
            WsMessage msg = null;

            // Build Resolve Request
            using(XmlMemoryWriter xmlWriter = XmlMemoryWriter.Create())
            {
                WsWsaHeader header = new WsWsaHeader(
                    m_version.DiscoveryNamespace + "/Resolve",    // Action
                    null,                                           // RelatesTo
                    serviceAddress,                                 // To
                    null, null, null);                              // ReplyTo, From, Any

                msg = new WsMessage(header, null, WsPrefix.Wsd, null, null);

                WsSoapMessageWriter smw = new WsSoapMessageWriter(m_version);
                messageID = smw.WriteSoapMessageStart(xmlWriter, msg);


                // write body
                xmlWriter.WriteStartElement(WsNamespacePrefix.Wsd, "Resolve", null);
                xmlWriter.WriteStartElement(WsNamespacePrefix.Wsa, "EndpointReference", null);
                xmlWriter.WriteStartElement(WsNamespacePrefix.Wsa, "Address", null);
                xmlWriter.WriteString(targetServiceAddress);

                xmlWriter.WriteEndElement(); // End Address
                xmlWriter.WriteEndElement(); // End EndpointReference
                xmlWriter.WriteEndElement(); // End Resolve

                smw.WriteSoapMessageEnd(xmlWriter);

                // return the resolve message
                msg.Body = xmlWriter.ToArray();
            }

            return msg;
        }
        internal WsMessage BuildHelloMessage(string endpointAddress, WsWsaHeader header, XmlReader reader)
        {
            using(XmlMemoryWriter xmlWriter = XmlMemoryWriter.Create())
            {
                WsXmlNamespaces additionalPrefixes = null;
                // If a Host exist write the Host namespace
                if (Device.Host != null)
                {
                    additionalPrefixes = new WsXmlNamespaces();
                    additionalPrefixes.Add(Device.Host.ServiceNamespace);
                }

                WsWsaHeader helloHeader = new WsWsaHeader(
                    m_version.DiscoveryNamespace + "/Hello",    // Action
                    null,                                       // RelatesTo
                    endpointAddress,                            // To
                    null, null, null);                          // ReplyTo, From, Any

                WsMessage msg = new WsMessage(helloHeader, null, WsPrefix.Wsd | WsPrefix.Wsdp, additionalPrefixes, 
                    new WsAppSequence(Device.AppSequence, Device.SequenceID, Device.MessageID));

                WsSoapMessageWriter smw = new WsSoapMessageWriter(m_version);
                smw.WriteSoapMessageStart(xmlWriter, msg);

                // write body
                xmlWriter.WriteStartElement(WsNamespacePrefix.Wsd, "Hello", null);

                xmlWriter.WriteStartElement(WsNamespacePrefix.Wsa, "EndpointReference", null);
                xmlWriter.WriteStartElement(WsNamespacePrefix.Wsa, "Address", null);
                xmlWriter.WriteString(Device.EndpointAddress);
                xmlWriter.WriteEndElement();
                xmlWriter.WriteEndElement();

                // Write hosted service types
                xmlWriter.WriteStartElement(WsNamespacePrefix.Wsd, "Types", null);
                WriteDeviceServiceTypes(xmlWriter, false);
                xmlWriter.WriteEndElement(); // End Types

                xmlWriter.WriteStartElement(WsNamespacePrefix.Wsd, "XAddrs", null);
                xmlWriter.WriteString(Device.TransportAddress);
                xmlWriter.WriteEndElement();

                xmlWriter.WriteStartElement(WsNamespacePrefix.Wsd, "MetadataVersion", null);
                xmlWriter.WriteString(Device.MetadataVersion.ToString());
                xmlWriter.WriteEndElement();

                xmlWriter.WriteEndElement();

                // Flush and close writer. Return stream buffer
                smw.WriteSoapMessageEnd(xmlWriter);

                msg.Body = xmlWriter.ToArray();

                return msg;
            }
        }
        // Build ws-discovery bye message
        internal WsMessage BuildByeMessage(string endpointAddress, WsWsaHeader header, XmlReader reader)
        {
            using(XmlMemoryWriter xmlWriter = XmlMemoryWriter.Create())
            {
                WsWsaHeader byeHeader = new WsWsaHeader(
                    m_version.DiscoveryNamespace + "/Bye",  // Action
                    null,                                   // RelatesTo
                    endpointAddress,                        // To
                    null, null, null);                      // ReplyTo, From, Any

                WsMessage msg = new WsMessage(byeHeader, null, WsPrefix.Wsd, null, 
                    new WsAppSequence(Device.AppSequence, Device.SequenceID, Device.MessageID));

                WsSoapMessageWriter smw = new WsSoapMessageWriter(m_version);
                smw.WriteSoapMessageStart(xmlWriter, msg);

                // write body
                xmlWriter.WriteStartElement(WsNamespacePrefix.Wsd, "Bye", null);

                xmlWriter.WriteStartElement(WsNamespacePrefix.Wsa, "EndpointReference", null);
                xmlWriter.WriteStartElement(WsNamespacePrefix.Wsa, "Address", null);
                xmlWriter.WriteString(Device.EndpointAddress);
                xmlWriter.WriteEndElement();
                xmlWriter.WriteEndElement();

                xmlWriter.WriteEndElement();

                smw.WriteSoapMessageEnd(xmlWriter);

                msg.Body = xmlWriter.ToArray();

                // Return stream buffer
                return msg;
            }
        }
        /// <summary>
        /// Use to unsubscribe from a devices event source.
        /// </summary>
        /// <param name="endpointAddress">
        /// A Uri containing the endpoint address of the service or subscription manager that is currently
        /// maintaining this event subscription on behalf of the device. This address is an http uri
        /// (i.e. http://ip_address:port/serviceID).
        /// </param>
        /// <param name="subscription">An event subscription returned from a previous subscribe call.
        /// The subscription contains among other things a subscription ID used by the subscription manager
        /// to identify a specific event source subscription and the endpoint address of the subscription manager.
        /// </param>
        /// <returns>True if the Unsubscribe request was successful.</returns>
        public bool Unsubscribe(Uri endpointAddress, DpwsEventSubscription subscription)
        {
            // Performance debugging
            DebugTiming timeDebuger = new DebugTiming();
            long startTime = timeDebuger.ResetStartTime("");

            // Build Unsubscribe Request
            using(XmlMemoryWriter xmlWriter = XmlMemoryWriter.Create())
            {
                WsXmlNodeList nodeList = new WsXmlNodeList();
                nodeList.Add(new WsXmlNode(null, "identifier", WsWellKnownUri.WseNamespaceUri, subscription.SubscriptionID));

                WsWsaHeader header = new WsWsaHeader(
                    m_version.EventingNamespace + "/Unsubscribe",            // Action
                    null,                                                    // RelatesTo
                    endpointAddress.AbsoluteUri,                             // To
                    m_version.AddressingNamespace,                           // ReplyTo
                    subscription.SubscriptionManager.Address.AbsoluteUri,    // From
                    nodeList);                                               // Identifier

                WsMessage msg = new WsMessage(header, null, WsPrefix.Wse, null, null);

                WsSoapMessageWriter smw = new WsSoapMessageWriter(m_version);
                String messageID = smw.WriteSoapMessageStart(xmlWriter, msg);

                // Performance debuging
                timeDebuger.PrintElapsedTime("*****Write Header Took");

                // write body
                xmlWriter.WriteStartElement(WsNamespacePrefix.Wse, "Unsubscribe", null);
                xmlWriter.WriteString("");
                xmlWriter.WriteEndElement(); // End Unsubscribe

                // Performance debuging
                timeDebuger.PrintElapsedTime("*****Write Body Took");

                smw.WriteSoapMessageEnd(xmlWriter);

                // Performance debuging
                timeDebuger.PrintTotalTime(startTime, "***Unsubscribe Message Build Took");

                // Create an Http client and send Unsubscribe request
                WsHttpClient httpClient = new WsHttpClient(m_version);

                msg.Body = xmlWriter.ToArray();

                WsMessage unsubscribeResponse = httpClient.SendRequest(msg, endpointAddress);

                // If a unsubscribe response is received simple validate that the messageID and action are correct and
                // If a parsing fault is received print exception and go on.
                if (unsubscribeResponse == null)
                {
                    System.Ext.Console.Write("");
                    System.Ext.Console.Write("Unsubscribe response is null.");
                    return false;
                }
                else
                {
                    System.Ext.Console.Write("");
                    System.Ext.Console.Write("Response From: " + endpointAddress.Host);
                    System.Ext.Console.Write(unsubscribeResponse.Body as byte[]);
                    try
                    {
                        return ProcessUnsubscribeResponse((byte[])unsubscribeResponse.Body, messageID);
                    }
                    catch (Exception e)
                    {
                        System.Ext.Console.Write("");
                        System.Ext.Console.Write("Unsubscribe response parsing failed.");
                        System.Ext.Console.Write(e.Message);
                        return false;
                    }
                }
            }
        }
        /// <summary>
        /// Use to get the status of an event subscription.
        /// </summary>
        /// <param name="endpointAddress">
        /// A Uri containing the endpoint address of the service or subscription manager that is currently
        /// maintaining this event subscription on behalf of the device. This address is an http uri
        /// (i.e. http://ip_address:port/serviceID).
        /// </param>
        /// <param name="subscriptionID">
        /// A subscription ID returned from a previous subscribe response. The device uses this ID
        /// to identify a specific event source subscription.
        /// </param>
        /// <returns>
        /// A WsDuration object containing the remaining subscription time for this event subscription, null = infinite.
        /// </returns>
        public WsDuration GetStatus(Uri endpointAddress, string subscriptionID)
        {
            // Performance debugging
            DebugTiming timeDebuger = new DebugTiming();
            long startTime = timeDebuger.ResetStartTime("");

            // Build Renew request
            using(XmlMemoryWriter xmlWriter = XmlMemoryWriter.Create())
            {
                WsXmlNodeList nodeList = new WsXmlNodeList();
                nodeList.Add(new WsXmlNode(WsNamespacePrefix.Wse, "Identifier", null, subscriptionID));

                WsWsaHeader header = new WsWsaHeader(
                    m_version.EventingNamespace + "/GetStatus",  // Action
                    null,                                           // RelatesTo
                    endpointAddress.AbsoluteUri,                    // To
                    null, null, nodeList);                          // ReplyTo, From, Any

                WsMessage msg = new WsMessage(header, null, WsPrefix.Wse, null, null);

                WsSoapMessageWriter smw = new WsSoapMessageWriter(m_version);
                String messageID = smw.WriteSoapMessageStart(xmlWriter, msg);

                // Performance debuging
                timeDebuger.PrintElapsedTime("*****Write Header Took");

                // write body
                xmlWriter.WriteStartElement(WsNamespacePrefix.Soap, "Body", null);
                xmlWriter.WriteStartElement(WsNamespacePrefix.Wse, "GetStatus", null);
                xmlWriter.WriteString("");
                xmlWriter.WriteEndElement(); // End GetStatus

                // Performance debuging
                timeDebuger.PrintElapsedTime("*****Write Body Took");

                smw.WriteSoapMessageEnd(xmlWriter);

                // Performance debuging
                timeDebuger.PrintTotalTime(startTime, "***Renew Message Build Took");

                // Create an Http client and send GetStatus request
                WsHttpClient httpClient = new WsHttpClient(m_version);

                msg.Body = xmlWriter.ToString();

                WsMessage getStatusResponse = httpClient.SendRequest(msg, endpointAddress);

                // If a GetStatus response is received validate the messageID and action and get the remaining
                // event subscription time. If a fault is received print exception and go on.
                if (getStatusResponse == null)
                {
                    System.Ext.Console.Write("");
                    System.Ext.Console.Write("Renew response is null.");
                    return null;
                }
                else
                {
                    System.Ext.Console.Write("");
                    System.Ext.Console.Write("Response From: " + endpointAddress.Host);
                    System.Ext.Console.Write(getStatusResponse.Body as byte[]);

                    // Note: Since the response is the same for GetStatus ans it is for Renew reuse the
                    // Renew response parser.
                    try
                    {
                        return ProcessRenewResponse((byte[])getStatusResponse.Body, messageID);
                    }
                    catch (Exception e)
                    {
                        System.Ext.Console.Write("");
                        System.Ext.Console.Write("Unsubscribe response parsing failed.");
                        System.Ext.Console.Write(e.Message);
                    }
                }
            }

            return null;
        }
        /// <summary>
        /// Use to subscribe to a devices, hosted service event sources.
        /// </summary>
        /// <param name="subscriptionRequest">
        /// A DpwsSubscriptionRequest object containing the address of the service hosting the desired event,
        /// the address where the event is sent, an optional address where subscription end messages are sent,
        /// A subscription expiration (in duration format) and an optional user defined identifier.
        /// </param>
        /// <returns>
        /// A DpwsEventSubscription containing the the subscription managers address, the time when the subscription
        /// expires (duration) and optional reference parameters and properties. Per spec the
        /// sub mananger may assign a different duration value than that specified in the request.
        /// </returns>
        /// <exception cref="ArgumentNullException">If required subscription parameters are not set.</exception>
        public DpwsEventSubscription Subscribe(DpwsSubscribeRequest subscriptionRequest)
        {
            if ((subscriptionRequest.SubscriptionType              == null) ||
                (subscriptionRequest.SubscriptionType.TypeName     == null) ||
                (subscriptionRequest.SubscriptionType.NamespaceUri == null) ||
                (subscriptionRequest.EndpointAddress               == null) ||
                (subscriptionRequest.NotifyTo                      == null) ||
                (subscriptionRequest.NotifyTo.Address              == null))
            {
                throw new ArgumentNullException();
            }

            // Convert the address string to a Uri
            Uri serviceUri = null;
            try
            {
                serviceUri = subscriptionRequest.EndpointAddress;
                if (serviceUri.Scheme != "http")
                {
                    System.Ext.Console.Write("");
                    System.Ext.Console.Write("Invalid endpoint address. Must be a Uri. Http Uri schemes only.");
                    System.Ext.Console.Write("");
                    return null;
                }
            }
            catch (Exception e)
            {
                System.Ext.Console.Write("");
                System.Ext.Console.Write(e.Message);
                System.Ext.Console.Write("");
                return null;
            }

            // Performance debugging
            DebugTiming timeDebuger = new DebugTiming();
            long startTime = timeDebuger.ResetStartTime("");
            WsMessage subscribeResponse = null;

            // Build Subscribe Request
            using(XmlMemoryWriter xmlWriter = XmlMemoryWriter.Create())
            {
                WsWsaHeader header = new WsWsaHeader(
                    m_version.EventingNamespace + "/Subscribe",     // Action
                    null,                                           // RelatesTo
                    serviceUri.AbsoluteUri,                         // To
                    m_version.AnonymousUri,                         // ReplyTo
                    null, null);                                    // From, Any

                WsXmlNamespaces additionalPrefixes = new WsXmlNamespaces();
                additionalPrefixes.Add(new WsXmlNamespace("myPrefix", subscriptionRequest.SubscriptionType.NamespaceUri));

                WsMessage msg = new WsMessage(header, null, WsPrefix.Wsd | WsPrefix.Wse, additionalPrefixes, null);

                WsSoapMessageWriter smw = new WsSoapMessageWriter(m_version);
                String messageID = smw.WriteSoapMessageStart(xmlWriter, msg);

                // Performance debuging
                timeDebuger.PrintElapsedTime("*****Write Header Took");

                // write body
                xmlWriter.WriteStartElement(WsNamespacePrefix.Wse, "Subscribe", null);
                
                // If EndTo is set write it
                if (subscriptionRequest.EndTo != null)
                    WriteEndpointRef(xmlWriter, subscriptionRequest.EndTo, "EndTo");

                // Add the delivery element and NotifyTo endpoint
                xmlWriter.WriteStartElement(WsNamespacePrefix.Wse, "Delivery", null);
                xmlWriter.WriteAttributeString("Mode", m_version.EventingNamespace + "/DeliveryModes/Push");

                // Writer the notify to endpoint
                WriteEndpointRef(xmlWriter, subscriptionRequest.NotifyTo, "NotifyTo");

                xmlWriter.WriteEndElement(); // End Delivery

                // Write Expiration time
                if (subscriptionRequest.Expires != null)
                {
                    xmlWriter.WriteStartElement(WsNamespacePrefix.Wse, "Expires", null);
                    xmlWriter.WriteString(subscriptionRequest.Expires.DurationString);
                    xmlWriter.WriteEndElement(); // End Expires
                }

                // Write Filter element specifying the event to subscribe to.
                xmlWriter.WriteStartElement(WsNamespacePrefix.Wse, "Filter", null);
                xmlWriter.WriteAttributeString("Dialect", m_version.WsdpNamespaceUri + "/Action");
                xmlWriter.WriteString(subscriptionRequest.SubscriptionType.NamespaceUri + "/" + subscriptionRequest.SubscriptionType.TypeName);
                xmlWriter.WriteEndElement(); // End Filter

                xmlWriter.WriteEndElement(); // End Subscribe

                smw.WriteSoapMessageEnd(xmlWriter);

                // Performance debuging
                timeDebuger.PrintElapsedTime("*****Write Body Took");

                // Performance debuging
                timeDebuger.PrintTotalTime(startTime, "***Subscribe Message Build Took");

                // Create an Http client and send Subscribe request
                WsHttpClient httpClient = new WsHttpClient(m_version);

                msg.Body = xmlWriter.ToArray();

                subscribeResponse = httpClient.SendRequest(msg, subscriptionRequest.EndpointAddress);

                // If a subscribe response is received process it and return expiration time the subscription manager
                // actually assigned.
                // If a parsing fault is received print exception and go on.
                DpwsEventSubscription response = null;
                if (subscribeResponse == null)
                {
                    System.Ext.Console.Write("");
                    System.Ext.Console.Write("Subscribe response is null.");
                    return null;
                }
                else
                {
                    byte[] responseBytes = subscribeResponse.Body as byte[];

                    // It is ok for the service to return a 202 and a 0 length response
                    // if thi is the case just return null
                    if(responseBytes == null || responseBytes.Length == 0) return null;

                    System.Ext.Console.Write("");
                    System.Ext.Console.Write("Response From: " + subscriptionRequest.EndpointAddress.Host);
                    System.Ext.Console.Write(responseBytes);
                    try
                    {
                        response = ProcessSubscribeResponse(responseBytes, messageID);
                    }
                    catch (Exception e)
                    {
                        System.Ext.Console.Write("");
                        System.Ext.Console.Write("Subscription response parsing failed.");
                        System.Ext.Console.Write(e.Message);
                    }
                }
                
                return response;
            }
        }
        public virtual WsMessage ProbeMatch(WsMessage probe, DpwsHostedService matchedService)
        {
            XmlReader   reader = probe.Reader;
            WsWsaHeader header = probe.Header;

            // Performance debugging
            DebugTiming timeDebuger = new DebugTiming();
            long startTime = timeDebuger.ResetStartTime("");

            // Build ProbeMatch
            using(XmlMemoryWriter xmlWriter = XmlMemoryWriter.Create())
            {
                // If a Host exist write the Host namespace
                WsXmlNamespaces additionalPrefixes = null;
                if (Device.Host != null)
                {
                    additionalPrefixes = new WsXmlNamespaces();
                    additionalPrefixes.Add(Device.Host.ServiceNamespace);
                }

                WsWsaHeader matchHeader = new WsWsaHeader(
                    this.Version.DiscoveryNamespace + "/ProbeMatches", // Action
                    header.MessageID,                                  // RelatesTo
                    this.Version.AnonymousUri,                         // To
                    null, null, null);                                 // ReplyTo, From, Any

                WsMessage msg = new WsMessage(matchHeader, null, WsPrefix.Wsd | WsPrefix.Wsdp, additionalPrefixes,
                    new WsAppSequence(Device.AppSequence, Device.SequenceID, Device.MessageID));

                WsSoapMessageWriter smw = new WsSoapMessageWriter(this.Version);
                smw.WriteSoapMessageStart(xmlWriter, msg);

                // Performance debuging
                timeDebuger.PrintElapsedTime("*****Write Header Took");

                // write body
                xmlWriter.WriteStartElement(WsNamespacePrefix.Wsd, "ProbeMatches", null);
                xmlWriter.WriteStartElement(WsNamespacePrefix.Wsd, "ProbeMatch", null);
                xmlWriter.WriteStartElement(WsNamespacePrefix.Wsa, "EndpointReference", null);
                xmlWriter.WriteStartElement(WsNamespacePrefix.Wsa, "Address", null);
                xmlWriter.WriteString(matchedService == null ? Device.EndpointAddress : matchedService.EndpointAddress);
                xmlWriter.WriteEndElement();
                xmlWriter.WriteEndElement();

                // Write hosted service types
                xmlWriter.WriteStartElement(WsNamespacePrefix.Wsd, "Types", null);
                WriteDeviceServiceTypes(xmlWriter);
                xmlWriter.WriteEndElement(); // End Types

                xmlWriter.WriteStartElement(WsNamespacePrefix.Wsd, "XAddrs", null);

                string transport = Device.TransportAddress;
                if(matchedService != null)
                {
                    int idx = transport.LastIndexOf('/');
                    if(idx != -1)
                    {
                        transport = transport.Substring(0, idx + 1);
                        transport += matchedService.EndpointAddress.Substring(matchedService.EndpointAddress.IndexOf("urn:uuid:") + 9);
                    }
                }

                int idx2 = transport.ToLower().IndexOf("localhost");

                if(idx2 != -1)
                {
                    transport = transport.Substring(0, idx2) + WsNetworkServices.GetLocalIPV4Address() + transport.Substring(idx2 + 9 /*localhost*/);
                }
                
                xmlWriter.WriteString(transport);
                xmlWriter.WriteEndElement();

                xmlWriter.WriteStartElement(WsNamespacePrefix.Wsd, "MetadataVersion", null);
                xmlWriter.WriteString(Device.MetadataVersion.ToString());
                xmlWriter.WriteEndElement();

                xmlWriter.WriteEndElement();
                xmlWriter.WriteEndElement();

                smw.WriteSoapMessageEnd(xmlWriter);

                msg.Body = xmlWriter.ToArray();

                // Performance debuging
                timeDebuger.PrintTotalTime(startTime, "***ProbeMatch Took");

                // Delay probe match as per Ws-Discovery specification (2.4 Protocol Assignments)
                Thread.Sleep(new Random().Next(Device.ProbeMatchDelay));

                // Return stream buffer
                return msg;
            }
        }
        public virtual WsMessage ResolveMatch(WsMessage message)
        {
            XmlReader   reader = message.Reader;
            WsWsaHeader header = message.Header;
            bool        match  = false;
            string      epAddr = "";

            reader.ReadStartElement("Resolve", this.Version.DiscoveryNamespace);

            
            if(reader.IsStartElement("EndpointReference", this.Version.AddressingNamespace) == false)
            {
                return null;
            }

            WsWsaEndpointRef epRef = new WsWsaEndpointRef(reader, this.Version.AddressingNamespace);

            epAddr = epRef.Address.AbsoluteUri;

            if(Device.EndpointAddress != epAddr)
            {
                // If the destination endpoint is ours send a resolve match else return null
                int servicesCount = Device.HostedServices.Count;
                DpwsHostedService hostedService;
                for (int i = 0; i < servicesCount; i++)
                {
                    hostedService = (DpwsHostedService)Device.HostedServices[i];
                    // Skip internal services
                    if (hostedService.ServiceTypeName == "Internal")
                        continue;
                
                    if (hostedService.EndpointAddress == epAddr)
                    {
                        match = true;
                        break;
                    }
                }

                if (!match)
                    return null;
            }

            // Build ResolveMatch
            using(XmlMemoryWriter xmlWriter = XmlMemoryWriter.Create())
            {
                // If a Host exist write the Host namespace
                WsXmlNamespaces additionalPrefixes = null;
                if (Device.Host != null)
                {
                    additionalPrefixes = new WsXmlNamespaces();
                    additionalPrefixes.Add(Device.Host.ServiceNamespace);
                }

                WsWsaHeader matchHeader = new WsWsaHeader(
                    this.Version.DiscoveryNamespace + "/ResolveMatches",  // Action
                    header.MessageID,                                     // RelatesTo
                    this.Version.AnonymousUri,                            // To
                    null, null, null);                                    // ReplyTo, From, Any

                WsMessage msg = new WsMessage(matchHeader, null, WsPrefix.Wsd | WsPrefix.Wsdp, additionalPrefixes, 
                    new WsAppSequence(Device.AppSequence, Device.SequenceID, Device.MessageID));

                WsSoapMessageWriter smw = new WsSoapMessageWriter(this.Version);
                smw.WriteSoapMessageStart(xmlWriter, msg);

                // write body
                xmlWriter.WriteStartElement(WsNamespacePrefix.Wsd, "ResolveMatches", null);
                xmlWriter.WriteStartElement(WsNamespacePrefix.Wsd, "ResolveMatch", null);
                xmlWriter.WriteStartElement(WsNamespacePrefix.Wsa, "EndpointReference", null);
                xmlWriter.WriteStartElement(WsNamespacePrefix.Wsa, "Address", null);
                xmlWriter.WriteString(epRef.Address.AbsoluteUri);
                xmlWriter.WriteEndElement(); // End Address
                xmlWriter.WriteEndElement(); // End EndpointReference

                // Write hosted service types
                xmlWriter.WriteStartElement(WsNamespacePrefix.Wsd, "Types", null);
                WriteDeviceServiceTypes(xmlWriter);
                xmlWriter.WriteEndElement(); // End Types

                string transport = Device.TransportAddress;
                if(match)
                {
                    int idx = transport.LastIndexOf('/');
                    if(idx != -1)
                    {
                        transport = transport.Substring(0, idx + 1);
                        transport += epAddr.Substring(epAddr.IndexOf("urn:uuid:") + 9);
                    }
                }

                int idx2 = transport.ToLower().IndexOf("localhost");

                if(idx2 != -1)
                {
                    transport = transport.Substring(0, idx2) + WsNetworkServices.GetLocalIPV4Address() + transport.Substring(idx2 + 9 /*localhost*/);
                }

                xmlWriter.WriteStartElement(WsNamespacePrefix.Wsd, "XAddrs", null);
                xmlWriter.WriteString(transport);
                xmlWriter.WriteEndElement(); // End XAddrs

                xmlWriter.WriteStartElement(WsNamespacePrefix.Wsd, "MetadataVersion", null);
                xmlWriter.WriteString(Device.MetadataVersion.ToString());
                xmlWriter.WriteEndElement(); // End MetadataVersion

                xmlWriter.WriteEndElement(); // End ResolveMatch
                xmlWriter.WriteEndElement(); // End ResolveMatches

                smw.WriteSoapMessageEnd(xmlWriter);

                msg.Body = xmlWriter.ToArray();

                // Return stream buffer
                return msg;
            }
        }
Beispiel #14
0
        /// <summary>
        /// Use to request metadata from a devices hosted service endpoint.
        /// </summary>
        /// <param name="serviceAddress">
        /// A string containing the transport address of a service endpoint. For Dpws the address represents
        /// a devices transport address.
        /// For example: http://192.168.0.1:8084/3cb0d1ba-cc3a-46ce-b416-212ac2419b20
        /// </param>
        /// <returns>
        /// A collection of DpwsMetadata objects containing endpoint details about services hosted by a device.
        /// </returns>
        public DpwsMetadata Get(string serviceAddress)
        {
            // Convert the address string to a Uri
            Uri serviceUri = null;
            try
            {
                serviceUri = new Uri(serviceAddress);
                if (serviceUri.Scheme != "http")
                {
                    System.Ext.Console.Write("");
                    System.Ext.Console.Write("Invalid serviceAddress. Must be a Uri. Http Uri schemes only.");
                    System.Ext.Console.Write("");
                    return null;
                }
            }
            catch (Exception e)
            {
                System.Ext.Console.Write("");
                System.Ext.Console.Write(e.Message);
                System.Ext.Console.Write("");
                return null;
            }

            // Performance debugging
            DebugTiming timeDebuger = new DebugTiming();
            long startTime = timeDebuger.ResetStartTime("");

            // Build Get Request
            using(XmlMemoryWriter xmlWriter = XmlMemoryWriter.Create())
            {
                WsWsaHeader header = new WsWsaHeader(
                    WsWellKnownUri.WstNamespaceUri + "/Get",            // Action
                    null,                                               // RelatesTo
                    "urn:uuid:" + serviceUri.AbsolutePath.Substring(1), // To
                    //TODO: should be ROLE???
                    m_version.AnonymousRoleUri,                              // ReplyTo
                    null, null);                                        // From, Any

                WsMessage msg = new WsMessage(header, null, WsPrefix.None, null, null);

                WsSoapMessageWriter smw = new WsSoapMessageWriter(m_version);
                String messageID = smw.WriteSoapMessageStart(xmlWriter, msg);

                // Performance debuging
                timeDebuger.PrintElapsedTime("*****Write Header Took");

                // Performance debuging
                timeDebuger.PrintElapsedTime("*****Write Body Took");

                smw.WriteSoapMessageEnd(xmlWriter);

                // Performance debuging
                timeDebuger.PrintTotalTime(startTime, "***Get Message Build Took");

                // Create an Http client and send Get request
                WsHttpClient httpClient = new WsHttpClient(m_version);

                System.Ext.Console.Write("");
                System.Ext.Console.Write("Sending Get to: " + serviceAddress);

                msg.Body = xmlWriter.ToArray();

                WsMessage getResponse = httpClient.SendRequest(msg, new Uri(serviceAddress));

                // If a get response is received process it and return DpwsMetadata object
                DpwsMetadata metadata = null;
                if (getResponse == null || getResponse.Body == null)
                {
                    return null;
                }
                else
                {
                    DpwsDiscoClientProcessor soapProcessor = new DpwsDiscoClientProcessor(m_version);
                    try
                    {
                        System.Ext.Console.Write(getResponse.Body as byte[]);
                        System.Ext.Console.Write("");

                        metadata = soapProcessor.ProcessGetResponse((byte[])getResponse.Body, messageID);
                    }
                    catch (Exception e)
                    {
                        System.Ext.Console.Write("");
                        System.Ext.Console.Write("Get response parser threw an exception. " + e.Message);
                        return null;
                    }
                }

                return metadata;
            }
        }
        public WsMessage GetResponse(WsMessage req)
        {
            XmlReader   reader = req.Reader;
            WsWsaHeader header = req.Header;

            // Build ProbeMatch
            using(XmlMemoryWriter xmlWriter = XmlMemoryWriter.Create())
            {
                // Write service type namespaces
                WsXmlNamespaces additionalPrefixes = new WsXmlNamespaces();
                if (Device.Host != null)
                {
                    additionalPrefixes = new WsXmlNamespaces();
                    additionalPrefixes.Add(Device.Host.ServiceNamespace);
                }

                WsServiceEndpoints hostedServices = Device.HostedServices;
                int count = hostedServices.Count;
                for (int i = 0; i < count; i++)
                {
                    DpwsHostedService hostedService = (DpwsHostedService)hostedServices[i];

                    // Don't return Mex Service namespace
                    if (hostedService.ServiceTypeName == "Internal")
                        continue;

                    additionalPrefixes.Add(hostedService.ServiceNamespace);
                }

                WsWsaHeader responseHeader = new WsWsaHeader(
                    WsWellKnownUri.WstNamespaceUri + "/GetResponse",                        // Action
                    header.MessageID,                                                       // RelatesTo
                    header.ReplyTo.Address.AbsoluteUri,                                     // To
                    null, null, null);

                WsMessage resp = new WsMessage(responseHeader, null, WsPrefix.Wsx | WsPrefix.Wsdp, additionalPrefixes,
                    new WsAppSequence(Device.AppSequence, Device.SequenceID, Device.MessageID));

                WsSoapMessageWriter smw = new WsSoapMessageWriter(m_version);
                smw.WriteSoapMessageStart(xmlWriter, resp);

                // write body
                xmlWriter.WriteStartElement(WsNamespacePrefix.Wsx, "Metadata", null);

                // Write ThisModel metadata section
                xmlWriter.WriteStartElement(WsNamespacePrefix.Wsx, "MetadataSection", null);
                xmlWriter.WriteAttributeString("Dialect", m_version.WsdpNamespaceUri + "/ThisModel");

                xmlWriter.WriteStartElement(WsNamespacePrefix.Wsdp, "ThisModel", null);
                if (Device.ThisModel.Manufacturer != null && Device.ThisModel.Manufacturer != "")
                {
                    xmlWriter.WriteStartElement(WsNamespacePrefix.Wsdp, "Manufacturer", null);
                    xmlWriter.WriteString(Device.ThisModel.Manufacturer);
                    xmlWriter.WriteEndElement(); // End Manufacturer
                }

                if (Device.ThisModel.ManufacturerUrl != null && Device.ThisModel.ManufacturerUrl != "")
                {
                    xmlWriter.WriteStartElement(WsNamespacePrefix.Wsdp, "ManufacturerUrl", null);
                    xmlWriter.WriteString(Device.ThisModel.ManufacturerUrl);
                    xmlWriter.WriteEndElement(); // End ManufacturerUrl
                }

                if (Device.ThisModel.ModelName != null && Device.ThisModel.ModelName != "")
                {
                    xmlWriter.WriteStartElement(WsNamespacePrefix.Wsdp, "ModelName", null);
                    xmlWriter.WriteString(Device.ThisModel.ModelName);
                    xmlWriter.WriteEndElement(); // End ModelName
                }

                if (Device.ThisModel.ModelNumber != null && Device.ThisModel.ModelNumber != "")
                {
                    xmlWriter.WriteStartElement(WsNamespacePrefix.Wsdp, "ModelNumber", null);
                    xmlWriter.WriteString(Device.ThisModel.ModelNumber);
                    xmlWriter.WriteEndElement(); // End ModelNumber
                }

                if (Device.ThisModel.ModelUrl != null && Device.ThisModel.ModelUrl != "")
                {
                    xmlWriter.WriteStartElement(WsNamespacePrefix.Wsdp, "ModelUrl", null);
                    xmlWriter.WriteString(Device.ThisModel.ModelUrl);
                    xmlWriter.WriteEndElement(); // End ModelUrl
                }

                if (Device.ThisModel.PresentationUrl != null && Device.ThisModel.PresentationUrl != "")
                {
                    xmlWriter.WriteStartElement(WsNamespacePrefix.Wsdp, "PresentationUrl", null);
                    xmlWriter.WriteString(Device.ThisModel.PresentationUrl);
                    xmlWriter.WriteEndElement(); // End PresentationUrl
                }

                if (Device.ThisModel.Any != null)
                {
                    Device.ThisModel.Any.WriteTo(xmlWriter);
                }

                xmlWriter.WriteStartElement(WsNamespacePrefix.Wsdp, "ModelName", null);
                xmlWriter.WriteString(Device.ThisModel.ModelName);
                xmlWriter.WriteEndElement(); // End ModelName

                xmlWriter.WriteEndElement(); // End ThisModel
                xmlWriter.WriteEndElement(); // End MetadataSection

                // Write ThisDevice metadata section
                xmlWriter.WriteStartElement(WsNamespacePrefix.Wsx, "MetadataSection", null);
                xmlWriter.WriteAttributeString("Dialect", m_version.WsdpNamespaceUri + "/ThisDevice");

                xmlWriter.WriteStartElement(WsNamespacePrefix.Wsdp, "ThisDevice", null);
                if (Device.ThisDevice.FriendlyName != null && Device.ThisDevice.FriendlyName != "")
                {
                    xmlWriter.WriteStartElement(WsNamespacePrefix.Wsdp, "FriendlyName", null);
                    xmlWriter.WriteString(Device.ThisDevice.FriendlyName);
                    xmlWriter.WriteEndElement(); // End FriendlyName
                }

                if (Device.ThisDevice.FirmwareVersion != null && Device.ThisDevice.FirmwareVersion != "")
                {
                    xmlWriter.WriteStartElement(WsNamespacePrefix.Wsdp, "FirmwareVersion", null);
                    xmlWriter.WriteString(Device.ThisDevice.FirmwareVersion);
                    xmlWriter.WriteEndElement(); // End FirmwareVersion
                }

                if (Device.ThisDevice.SerialNumber != null && Device.ThisDevice.SerialNumber != "")
                {
                    xmlWriter.WriteStartElement(WsNamespacePrefix.Wsdp, "SerialNumber", null);
                    xmlWriter.WriteString(Device.ThisDevice.SerialNumber);
                    xmlWriter.WriteEndElement(); // End SerialNumber
                }

                if (Device.ThisDevice.Any != null)
                {
                    Device.ThisDevice.Any.WriteTo(xmlWriter);
                }

                xmlWriter.WriteEndElement(); // End ThisDevice
                xmlWriter.WriteEndElement(); // End MetadataSection

                // Write next MetadataSection
                xmlWriter.WriteStartElement(WsNamespacePrefix.Wsx, "MetadataSection", null);
                xmlWriter.WriteAttributeString("Dialect", m_version.WsdpNamespaceUri + "/Relationship");

                // Write Relationship Elements
                xmlWriter.WriteStartElement(WsNamespacePrefix.Wsdp, "Relationship", null);
                xmlWriter.WriteAttributeString("Type", m_version.WsdpNamespaceUri + "/host");

                // List used to maintain service endpoints that have been processed. Because the DPWS spec allows
                // for multiple service types at a single endpoint address, we must make sure we only create
                // a relationship once for all of the types at a service endpoint.
                ArrayList processedEndpointList = new ArrayList();

                // If a Host type exist add it
                if (Device.Host != null)
                {
                    xmlWriter.WriteStartElement(WsNamespacePrefix.Wsdp, "Host", null);
                    WsWsaEndpointRef endpointReference;
                    endpointReference = (WsWsaEndpointRef)Device.Host.EndpointRefs[0];
                    xmlWriter.WriteStartElement(WsNamespacePrefix.Wsa, "EndpointReference", null);
                    xmlWriter.WriteStartElement(WsNamespacePrefix.Wsa, "Address", null);
                    xmlWriter.WriteString(endpointReference.Address.AbsoluteUri);
                    xmlWriter.WriteEndElement(); // End Address
                    xmlWriter.WriteEndElement(); // End EndpointReference

                    // Build list of all service types that share this endpoint address
                    /*
                    string serviceTypes = null;
                    if ((serviceTypes = BuildServiceTypesList(Device.Host, processedEndpointList)) == null)
                        serviceTypes = Device.Host.ServiceNamespace.Prefix + ":" + Device.Host.ServiceTypeName;
                    else
                        serviceTypes = serviceTypes + " " + Device.Host.ServiceNamespace.Prefix + ":" + Device.Host.ServiceTypeName;
                    */
                    
                    // Write service types
                    xmlWriter.WriteStartElement(WsNamespacePrefix.Wsdp, "Types", null);
                    xmlWriter.WriteString(Device.Host.ServiceNamespace.Prefix + ":" + Device.Host.ServiceTypeName);
                    xmlWriter.WriteEndElement(); // End Types

                    // Write Service ID
                    xmlWriter.WriteStartElement(WsNamespacePrefix.Wsdp, "ServiceId", null);
                    xmlWriter.WriteString(Device.Host.ServiceID);
                    xmlWriter.WriteEndElement(); // End ServiceID

                    xmlWriter.WriteEndElement(); // End Hosted

                    // Update processed endpoint list
                    processedEndpointList.Add(Device.Host.EndpointAddress);
                }

                // Add hosted services types
                int serviceCount = hostedServices.Count;
                DpwsHostedService currentService;
                for (int i = 0; i < serviceCount; i++)
                {
                    currentService = (DpwsHostedService)hostedServices[i];

                    // Don't return Mex Service type
                    if (currentService.ServiceTypeName == "Internal")
                        continue;

                    // Build list of all service types that share this endpoint address
                    string serviceTypes = null;
                    if ((serviceTypes = BuildServiceTypesList(currentService, processedEndpointList)) == null)
                        continue;

                    // Write hosted start element
                    xmlWriter.WriteStartElement(WsNamespacePrefix.Wsdp, "Hosted", null);
                    
                    // Write n number of endpoint references
                    int epRefCount = currentService.EndpointRefs.Count;
                    for (int j = 0; j < epRefCount; j++)
                    {
                        xmlWriter.WriteStartElement(WsNamespacePrefix.Wsa, "EndpointReference", null);
                        xmlWriter.WriteStartElement(WsNamespacePrefix.Wsa, "Address", null);
                        xmlWriter.WriteString(currentService.EndpointRefs[j].Address.AbsoluteUri);
                        xmlWriter.WriteEndElement(); // End Address
                        xmlWriter.WriteEndElement(); // End EndpointReference
                    }

                    // Write service types
                    xmlWriter.WriteStartElement(WsNamespacePrefix.Wsdp, "Types", null);
                    xmlWriter.WriteString(currentService.ServiceNamespace.Prefix + ":" + currentService.ServiceTypeName);
                    xmlWriter.WriteEndElement(); // End Types

                    // Write Service ID
                    xmlWriter.WriteStartElement(WsNamespacePrefix.Wsdp, "ServiceId", null);
                    xmlWriter.WriteString(currentService.ServiceID);
                    xmlWriter.WriteEndElement(); // End ServiceID

                    xmlWriter.WriteEndElement(); // End Hosted

                    // Update processed endpoint list
                    processedEndpointList.Add(currentService.EndpointAddress);
                }

                xmlWriter.WriteEndElement(); // End Relastionship
                xmlWriter.WriteEndElement(); // End MetadataSection

                xmlWriter.WriteEndElement(); // End Metadata

                smw.WriteSoapMessageEnd(xmlWriter);

                resp.Body = xmlWriter.ToArray();

                return resp;
            }
        }