public override UPnPTestStates Run(ICollection otherSubTests, CdsSubTestArgument arg)
        {
            CpContentDirectory CDS = this.GetCDS(arg._Device);
            _Details = new CdsResult_BrowseSortCriteria();
            this._TestState = UPnPTestStates.Running;
            arg._TestGroup.AddEvent(LogImportance.Remark, this.Name, "\""+this.Name + "\" started.");

            // get the results from the prerequisite tests
            CdsResult_BrowseAll BROWSE_RESULTS = null;
            CdsResult_GetSortCapabilities SORTCAPS = null;
            try
            {
                foreach (ISubTest preTest in otherSubTests)
                {
                    if (preTest.Name == this.PRE_BROWSEALL.Name)
                    {
                        BROWSE_RESULTS = preTest.Details as CdsResult_BrowseAll;
                    }
                    else if (preTest.Name == this.PRE_SORTCAPS.Name)
                    {
                        SORTCAPS = preTest.Details as CdsResult_GetSortCapabilities;
                    }
                }

                if (BROWSE_RESULTS == null)
                {
                    throw new TestException(this._Name + " requires that the \"" + this.PRE_BROWSEALL.Name + "\" test be run as a prerequisite. The results from that test cannot be obtained.", otherSubTests);
                }

                if (SORTCAPS == null)
                {
                    throw new TestException(this._Name + " requires that the \"" + this.PRE_SORTCAPS.Name + "\" test be run as a prerequisite. The results from that test cannot be obtained.", otherSubTests);
                }
            }
            catch (Exception e)
            {
                throw new TestException(this._Name + " requires that the \"" + this.PRE_BROWSEALL.Name + "\" and \"" + this.PRE_SORTCAPS+ "\" tests be run before. An error occurred when attempting to obtain the results of those prerequisites.", otherSubTests, e);
            }
            _Details.BrowseAllResults = BROWSE_RESULTS;
            _Details.SortCapsResults = SORTCAPS;

            UPnPTestStates state = this._TestState;

            if (BROWSE_RESULTS.LargestContainer == null)
            {
                throw new TestException(this.PRE_BROWSEALL.Name + " failed to find the container with the most child objects. " +this._Name+ " requires this value.", BROWSE_RESULTS);
            }

            MediaContainer MC = BROWSE_RESULTS.LargestContainer as MediaContainer;
            if (MC == null)
            {
                throw new TestException(this.PRE_BROWSEALL.Name + " has the largest container as type \"" +BROWSE_RESULTS.LargestContainer.GetType().ToString() +"\" when \"" +this.Name+ "\" requires \"" +typeof(MediaContainer).ToString()+ "\".", BROWSE_RESULTS);
            }

            ArrayList sortFields = new ArrayList();
            if (SORTCAPS.SortCapabilities == "")
            {
                //arg.TestGroup.AddEvent(LogImportance.Remark, this.Name, "\""+this.Name+"\" has no sorting capabilities.");
            }
            else if (SORTCAPS.SortCapabilities == "*")
            {
                sortFields = (ArrayList) BROWSE_RESULTS.PropertyNames.Clone();
            }
            else
            {
                sortFields.AddRange ( GetSortFields(SORTCAPS.SortCapabilities) );
            }

            _Details.ExpectedTotalBrowseRequests = 0;
            _Details.SortFields = sortFields;
            int fieldCount = sortFields.Count;
            IList childList = BROWSE_RESULTS.LargestContainer.CompleteList;
            _Details.ExpectedTotalBrowseRequests = 0;//fieldCount * fieldCount * fieldCount;
            uint inc = (uint) (childList.Count / 3);
            int firstInc = (fieldCount / 3);
            if (firstInc == 0)
            {
                firstInc = 1;
            }
            for (int numFields = 0; numFields < fieldCount; numFields++)
            {
                for (int first = 0; first < fieldCount; first+=firstInc)
                {
                    //for (uint i=0; i < childList.Count; i+=inc)
                    {
                        _Details.ExpectedTotalBrowseRequests++;
                    }
                }
            }
            // add 1 for an unsorted browse
            _Details.ExpectedTotalBrowseRequests++;
            //multiply by 2 because we have 2 rounds to check for consistency in ordered results
            _Details.ExpectedTotalBrowseRequests *= 2;

            //calculate time
            this._ExpectedTestingTime = _Details.ExpectedTotalBrowseRequests * 900;
            arg.ActiveTests.UpdateTimeAndProgress(0);

            if (state <= UPnPTestStates.Running)
            {
                state = UPnPTestStates.Pass;
                try
                {
                    ArrayList round2 = new ArrayList();

                    //perform the standard unsorted browse
                    BrowseInput input = new BrowseInput();
                    input.BrowseFlag = CpContentDirectory.Enum_A_ARG_TYPE_BrowseFlag.BROWSEDIRECTCHILDREN;
                    input.StartingIndex = 0;
                    input.ObjectID = MC.ID;
                    input.RequestedCount = 0;
                    input.Filter = "*";
                    input.SortCriteria = "";

                    CdsBrowseSearchResults br = Browse(input, this, arg, CDS, _Details);
                    Round2 r2 = new Round2();
                    r2.Input = (BrowseInput) input.Clone();
                    r2.PreviousResult = br;
                    round2.Add(r2);

                    for (int numFields = 0; numFields < fieldCount; numFields++)
                    {
                        for (int first = 0; first < fieldCount; first+=firstInc)
                        {

                            ArrayList sortSettings = GetSortSettings(sortFields, first, first);
                            input.SortCriteria = GetSortCriteriaString(sortSettings, numFields+first);
                            arg.ActiveTests.UpdateTimeAndProgress(_Details.TotalBrowseRequests * 900);

                            uint ignored;

                            //use this sorter for to determine the expected order of the media objects
                            IMediaSorter sorter = new MediaSorter(true, input.SortCriteria);
                            IList expectedSorted = MC.BrowseSorted(0, 0, sorter, out ignored);

                            br = Browse(input, this, arg, CDS, _Details);
                            arg.ActiveTests.UpdateTimeAndProgress(_Details.TotalBrowseRequests * 900);

                            this.CompareResultsAgainstExpected(br, expectedSorted, ref state, arg, input, false);

                            r2 = new Round2();
                            r2.Input = (BrowseInput) input.Clone();
                            r2.PreviousResult = br;
                            round2.Add(r2);
                        }
                    }

                    //do round2 - check for consistency in results
                    foreach (Round2 r in round2)
                    {
                        br = Browse(r.Input, this, arg, CDS, _Details);
                        arg.ActiveTests.UpdateTimeAndProgress(_Details.TotalBrowseRequests * 900);
                        this.CompareResultsAgainstExpected(br, r.PreviousResult.MediaObjects, ref state, arg, r.Input, true);
                    }
                }
                catch (TerminateEarly te)
                {
                    string reason = "\"" +this.Name+ "\" terminating early. Reason => " + te.Message;
                    arg._TestGroup.AddEvent(LogImportance.Critical, this.Name, reason);

                    state = UPnPTestStates.Failed;
                }
            }

            // finish up logging
            this._TestState = state;

            StringBuilder sb = new StringBuilder();
            sb.AppendFormat("\"{0}\" completed", this.Name);

            if (this._TestState <= UPnPTestStates.Running)
            {
                throw new TestException("\"" +this.Name+ "\" must have a pass/warn/fail result.", this._TestState);
            }

            switch (this._TestState)
            {
                case UPnPTestStates.Pass:
                    sb.Append(" successfully.");
                    break;
                case UPnPTestStates.Warn:
                    sb.Append(" with warnings.");
                    break;
                case UPnPTestStates.Failed:
                    sb.Append(" with a failed result.");
                    break;
            }

            arg._TestGroup.AddResult(sb.ToString());

            if (this._TestState <= UPnPTestStates.Warn)
            {
                if (_Details.TotalBrowseRequests != _Details.ExpectedTotalBrowseRequests)
                {
                    throw new TestException("TotalBrowseRequests="+_Details.TotalBrowseRequests.ToString()+" ExpectedTotal="+_Details.ExpectedTotalBrowseRequests.ToString(), _Details);
                }
            }

            arg._TestGroup.AddEvent(LogImportance.Remark, this.Name, sb.ToString());

            return this._TestState;
        }
        /// <summary>
        /// Method executes when a control point invokes the ContentDirectory.Search action.
        /// The method will recursively search all descendent objects and determine if
        /// the objects match the search criteria. All objects that match the search
        /// criteria will be included in a flat DIDL-Lite listing of media objects
        /// in the response. Sort criteria and filter criteria are just applied in
        /// the same manner as a browse request.
        /// </summary>
        /// <param name="containerID">Container to search from.</param>
        /// <param name="searchCriteria">Valid CDS search criteria string.</param>
        /// <param name="filter">
        /// Comma separated value list of metadata property names to include in the response.
        /// Return * for all metadata.
        /// </param>
        /// <param name="startingIndex">Given the entire possible response set, return a subset beginning with this index in the result set.</param>
        /// <param name="requestedCount">Given the entire possible response set, return a subset totalling no more than this many.</param>
        /// <param name="sortCriteria">Specify a comma-separated value list of metadata properties, with a + or - char before each property name to indicate ascending/descending order.</param>
        /// <param name="Result">DIDL-Lite response for desired result set.</param>
        /// <param name="numberReturned">Number of media objects returned in the response. 0 if browsing object metadata.</param>
        /// <param name="totalMatches">Total number of media objects in entire result set. 0 if browsing object metadata.</param>
        /// <param name="updateID">The UpdateID of the object - ignore if an object.item entry. Applies only to object.container entries.</param>
        private void SinkCd_Search(System.String containerID, System.String searchCriteria, System.String filter, System.UInt32 startingIndex, System.UInt32 requestedCount, System.String sortCriteria, out System.String Result, out System.UInt32 numberReturned, out System.UInt32 totalMatches, out System.UInt32 updateID)
        {
            try
            {
                numberReturned = 0;
                Result = "";
                totalMatches = 0;
                updateID = 0;

                // Get the container with the ID.
                //

                IDvMedia entry = this.GetCdsEntry(containerID);
                if (entry.IsContainer == false)
                {
                    throw new Error_NoSuchContainer("("+containerID+")");
                }
                DvMediaContainer container = (DvMediaContainer) entry;

                // Issue a search from the container.
                // Search requires a MediaComparer to determine whether an entry matches
                // against the searchCriteria.
                // Sorting is optional, but it requires that we traverse the entire subtree
                // in order to reply properly.
                //
                IList entries;
                if (sortCriteria.Trim() == "")
                {
                    MediaComparer postfix = new MediaComparer(searchCriteria);
                    entries = container.Search(postfix, startingIndex, requestedCount, out totalMatches);
                }
                else
                {
                    MediaSorter sorter = new MediaSorter(true, sortCriteria);
                    MediaComparer postfix = new MediaComparer(searchCriteria);
                    entries = container.SearchSorted(postfix, sorter, startingIndex, requestedCount, out totalMatches);
                }

                for (int rem=0; rem < startingIndex; rem++)
                {
                    entries.RemoveAt(0);
                }

                numberReturned = Convert.ToUInt32(entries.Count);
                updateID = container.UpdateID;

                // Get the XML response for this result set.
                // Be sure to grab the list of base URLs.
                ArrayList properties = GetFilters(filter);
                string[] baseUrls = GetBaseUrlsByInterfaces();
                Result = BuildXmlRepresentation(baseUrls, properties, entries);

            }
            catch (Exception e)
            {
                Exception ne = new Exception("MediaServer.SinkCd_Search()", e);
                throw ne;
            }
            this.m_Stats.Search++;
            this.FireStatsChange();
        }
        public void CompareResultsAgainstExpected(CdsBrowseSearchResults br, IList expectedResults, ref UPnPTestStates state, CdsSubTestArgument arg, BrowseInput input, bool strictOrder)
        {
            if (br.WorstError >= UPnPTestStates.Failed)
            {
                throw new TerminateEarly("\"" + this.Name + "\" is terminating early because " +input.PrintBrowseParams()+ " returned with an error or had problems with the DIDL-Lite.");
            }
            else
            {
                if (br.MediaObjects.Count != expectedResults.Count)
                {
                    throw new TerminateEarly("\""+this.Name+"\" did a " +input.PrintBrowseParams()+ " and it should have returned "+expectedResults.Count+ " media objects. DIDL-Lite contained " +br.MediaObjects.Count+ " media objects. DIDL-Lite => " + br.Result);
                }

                bool warnResults = false;
                for (int i=0; i < br.MediaObjects.Count; i++)
                {

                    IUPnPMedia gotThis = (IUPnPMedia) br.MediaObjects[i];
                    IUPnPMedia expectedMedia = (IUPnPMedia) expectedResults[i];

                    if (gotThis.ID == expectedMedia.ID)
                    {
                        //arg.TestGroup.AddEvent(LogImportance.Remark, this.Name, "\""+this.Name+"\" did a " +input.PrintBrowseParams()+ " and encountered no errors in the results.");
                    }
                    else
                    {
                        bool failed = false;
                        if ((input.SortCriteria == null) || (input.SortCriteria == ""))
                        {
                            failed = true;
                        }
                        else
                        {
                            // Use this sorter to test for value-equality in situations where the expected order didn't match.
                            // We need to do this because two media objects may be value-equivalent according to a sorting
                            // algorithm, in which case there's no way to really distinguish what order they should be in.
                            IMediaSorter sorter2 = new MediaSorter(false, input.SortCriteria);

                            int cmp = sorter2.Compare(gotThis, expectedMedia);
                            if (cmp != 0)
                            {
                                arg.TestGroup.AddEvent(LogImportance.Medium, this.Name, "\""+this.Name+"\" found media object ID=\""+gotThis.ID+"\" when it expected to find \""+expectedMedia.ID+"\" and they are not equal in their sorted order.");
                                warnResults = true;
                            }
                            else
                            {
                                if (strictOrder == false)
                                {
                                    arg.TestGroup.AddEvent(LogImportance.Low, this.Name, "\""+this.Name+"\" found media object ID=\""+gotThis.ID+"\" when it expected to find \""+expectedMedia.ID+"\" but since they are effectively value-equivalent, the ordering is OK.");
                                }
                                else
                                {
                                    failed = true;
                                }
                            }
                        }

                        if (failed)
                        {
                            StringBuilder msg = new StringBuilder();
                            msg.AppendFormat("\"{0}\" did a {1} and the order of object ID's in the result conflicts with previous browse requests.");
                            msg.AppendFormat("\r\n\r\nReceived objects in order by ID: ");
                            int z = 0;
                            foreach (IUPnPMedia em in br.MediaObjects)
                            {
                                if (z > 0)
                                {
                                    msg.Append(",");
                                }
                                msg.AppendFormat("\"{0}\"", em.ID);
                                z++;
                            }
                            msg.Append("\r\n\r\nThe expected order by ID is: ");
                            z = 0;
                            foreach (IUPnPMedia em in expectedResults)
                            {
                                if (z > 0)
                                {
                                    msg.Append(",");
                                }
                                msg.AppendFormat("\"{0}\"", em.ID);
                                z++;
                            }
                            msg.AppendFormat(".\r\n\r\nDIDL-Lite ==> {0}", br.Result);
                            throw new TerminateEarly(msg.ToString());
                        }
                    }
                }

                if (warnResults == false)
                {
                    arg.TestGroup.AddEvent(LogImportance.Remark, this.Name, "\""+this.Name+"\" did a " +input.PrintBrowseParams()+ " and encountered no errors or warnings in the results.");
                }
                else
                {
                    StringBuilder msg = new StringBuilder();
                    msg.AppendFormat("WARNING: \"{0}\" did a {1} and \r\nreceived results in the following order by ID: ", this.Name, input.PrintBrowseParams());
                    int z = 0;
                    foreach (IUPnPMedia em in br.MediaObjects)
                    {
                        if (z > 0)
                        {
                            msg.Append(",");
                        }
                        msg.AppendFormat("\"{0}\"", em.ID);
                        z++;
                    }
                    msg.Append("\r\n\r\nThe expected order by ID is: ");
                    z = 0;
                    foreach (IUPnPMedia em in expectedResults)
                    {
                        if (z > 0)
                        {
                            msg.Append(",");
                        }
                        msg.AppendFormat("\"{0}\"", em.ID);
                        z++;
                    }
                    msg.AppendFormat(".\r\n\r\nDIDL-Lite ==> {0}", br.Result);
                    // warn
                    state = UPnPTestStates.Warn;
                    arg._TestGroup.AddEvent(LogImportance.Medium, this.Name, msg.ToString());
                }
            }
        }
        /// <summary>
        /// Method executes when a control point invokes the ContentDirectory.Browse action.
        /// Depending on the input parameters, the method either returns the metadata
        /// for the specified object, or the method returns the metadata of all child objects
        /// if direct children metadata was requested and the specified object is actually
        /// a container.
        /// </summary>
        /// <param name="objectID">Browse the metadata or the children of this object</param>
        /// <param name="browseFlag">Indicate whether metadata or child object metadata is desired</param>
        /// <param name="filter">Comma separated value list of metadata properties indicates desired metadata properties to include in response, use * for all.</param>
        /// <param name="startingIndex">Given the entire possible response set, return a subset beginning with this index in the result set.</param>
        /// <param name="requestedCount">Given the entire possible response set, return a subset totalling no more than this many.</param>
        /// <param name="sortCriteria">Specify a comma-separated value list of metadata properties, with a + or - char before each property name to indicate ascending/descending order.</param>
        /// <param name="Result">DIDL-Lite response for desired result set.</param>
        /// <param name="numberReturned">Number of media objects returned in the response. 0 if browsing object metadata.</param>
        /// <param name="totalMatches">Total number of media objects in entire result set. 0 if browsing object metadata.</param>
        /// <param name="updateID">The UpdateID of the object - ignore if an object.item entry. Applies only to object.container entries.</param>
        private void SinkCd_Browse(System.String objectID, DvContentDirectory.Enum_A_ARG_TYPE_BrowseFlag browseFlag, System.String filter, System.UInt32 startingIndex, System.UInt32 requestedCount, System.String sortCriteria, out System.String Result, out System.UInt32 numberReturned, out System.UInt32 totalMatches, out System.UInt32 updateID)
        {
            try
            {
                numberReturned = 0;
                Result = "";
                totalMatches = 0;

                if (requestedCount == 0)
                {
                    requestedCount = Convert.ToUInt32(int.MaxValue);
                }

                // Get the item identified by the ID, or throw an exception
                // if the item doesn't exist.
                //
                IDvMedia entry = this.GetCdsEntry(objectID);

                // Issue a browse on the entry if it's a container and we're browing children.
                // Return the results in entries. Apply sorting if appropriate.
                //
                IList entries;
                if ((entry.IsContainer) && (browseFlag == DvContentDirectory.Enum_A_ARG_TYPE_BrowseFlag.BROWSEDIRECTCHILDREN))
                {
                    DvMediaContainer container = (DvMediaContainer) entry;
                    if (sortCriteria.Trim() == "")
                    {
                        entries = container.Browse(startingIndex, requestedCount, out totalMatches);
                    }
                    else
                    {
                        MediaSorter sorter = new MediaSorter(true, sortCriteria);
                        entries = container.BrowseSorted(startingIndex, requestedCount, sorter, out totalMatches);
                    }

                    numberReturned = Convert.ToUInt32(entries.Count);
                    updateID = container.UpdateID;
                }
                else
                {
                    // We're browsing an item or a container's metadata, so simply set the entry to be
                    // the only return value.
                    //
                    entries = new ArrayList();
                    entries.Add(entry);
                    totalMatches = 1;
                    numberReturned = 1;
                    IDvMedia dvMedia = (IDvMedia) entry;
                    DvMediaContainer container = dvMedia as DvMediaContainer;

                    if (container == null)
                    {
                        container = (DvMediaContainer) dvMedia.Parent;
                    }

                    updateID = container.UpdateID;
                }

                // Get the XML response for this result set.
                // Be sure to grab the list of base URLs.
                ArrayList properties = GetFilters(filter);
                string[] baseUrls = GetBaseUrlsByInterfaces();
                Result = BuildXmlRepresentation(baseUrls, properties, entries);
            }
            catch (Exception e)
            {
                Exception ne = new Exception("MediaServerDevice.SinkCd_Browse()", e);
                throw ne;
            }
            this.m_Stats.Browse++;
            this.FireStatsChange();
        }