/// <summary> /// Resets the match object provided and performs the detection /// using that object rather than creating a new match. /// </summary> /// <param name="targetUserAgent"> /// The user agent string to use as the target /// </param> /// <param name="match"> /// A match object created by a previous match, or via the /// <see cref="CreateMatch"/> method. /// </param> /// <returns> /// The match object passed to the method updated with /// results for the target user agent /// </returns> public Match Match(string targetUserAgent, Match match) { MatchState state; if (_userAgentCache != null && String.IsNullOrEmpty(targetUserAgent) == false) { // Increase the cache requests. Interlocked.Increment(ref _userAgentCache.Requests); if (_userAgentCache._itemsActive.TryGetValue(targetUserAgent, out state) == false) { // The user agent has not been checked previously. Therefore perform // the match and store the results in the cache. MatchNoCache(targetUserAgent, match); // Record the match state in the cache for next time. state = new MatchState(match); _userAgentCache._itemsActive[targetUserAgent] = state; // Increase the cache misses. Interlocked.Increment(ref _userAgentCache.Misses); } else { // The state of a previous match exists so the match should // be configured based on the results of the previous state. match.SetState(state); } _userAgentCache.AddRecent(targetUserAgent, state); } else { // The cache does not exist so call the non caching method. MatchNoCache(targetUserAgent, match); } return(match); }
/// <summary> /// Constructs an instance of <see cref="MatchResult"/> based on the /// source provided. /// </summary> /// <param name="source"></param> internal MatchResult(MatchState source) { _dataSet = source._dataSet; _elapsed = source.Elapsed; _method = source.Method; _nodesEvaluated = source.NodesEvaluated; _rootNodesEvaluated = source.RootNodesEvaluated; _signaturesCompared = source.SignaturesCompared; _signaturesRead = source.SignaturesRead; _stringsRead = source.StringsRead; _closestSignatures = source.ClosestSignatures; _lowestScore = source.LowestScore; _targetUserAgent = source.TargetUserAgent; _targetUserAgentArray = source.TargetUserAgentArray; if (_dataSet is IndirectDataSet) { // The match result will only store the index or offset of the // related entity type in the source dataset to avoid creating // duplicate instances in cases where the data set is an indirect // one and a cache, or no cache is being used. This approach // ensures a consistent memory profile. _signatureIndex = source.Signature != null ? (int?)source.Signature.Index : null; _profileOffsets = source.Profiles.Select(i => i.Index).ToArray(); _nodeOffsets = source.Nodes.Select(i => i.Index).ToArray(); } else { // The entire data set is being held in memory so a direct // reference to the related entity instance can be stored. _signature = source.Signature; _profiles = (Profile[])source.Profiles.Clone(); _nodes = source.Nodes.ToArray(); } }
/// <summary> /// Resets the match object provided and performs the detection /// using that object rather than creating a new match. /// </summary> /// <param name="targetUserAgent"> /// The User-Agent string to use as the target /// </param> /// <param name="state"> /// A match object created by a previous match, or via the /// <see cref="CreateMatch"/> method. /// </param> /// <returns> /// The match object passed to the method updated with /// results for the target User-Agent /// </returns> private MatchResult Match(string targetUserAgent, MatchState state) { MatchResult result; if (targetUserAgent == null) { // Handle null User-Agents as empty strings. targetUserAgent = String.Empty; } if (_userAgentCache != null) { // Fetch the item using the cache. result = _userAgentCache[targetUserAgent, state]; } else { // The cache does not exist so call the non caching method. MatchNoCache(targetUserAgent, state); result = new MatchResult(state); } return(result); }
/// <summary> /// Contructs a new detection match ready to used to identify /// profiles from User-Agents. /// </summary> /// <param name="provider"></param> internal Match(Provider provider) { Provider = provider; State = new MatchState(this); Result = State; }
/// <summary> /// The detection failed and a default match needs to be returned. /// </summary> /// <param name="state">Information about the match state</param> internal static void MatchDefault(MatchState state) { state.Method = MatchMethods.None; state.ExplicitProfiles.Clear(); state.ExplicitProfiles.AddRange(state.DataSet.Components.Select(i => i.DefaultProfile)); }
/// <summary> /// Entry point to the detection process. Provided with a <see cref="Match"/> /// configured with the information about the request. /// </summary> /// <remarks> /// The dataSet may be used by other threads in parallel and is not assumed to be used /// by only one detection process at a time. /// </remarks> /// <param name="state">The match state object to be updated.</param> /// <remarks> /// The memory implementation of the data set will always perform fastest but does /// consume more memory. /// </remarks> internal static void Match(MatchState state) { if (state.DataSet.Disposed) { throw new InvalidOperationException( "Data Set has been disposed and can't be used for match"); } // If the User-Agent is too short then don't try to match and // return defaults. if (state.TargetUserAgentArray.Length == 0 || state.TargetUserAgentArray.Length < state.DataSet.MinUserAgentLength) { // Set the default values. MatchDefault(state); } else { // Starting at the far right evaluate the nodes in the data // set recording matched nodes. Continue until all character // positions have been checked. Evaluate(state); // Can a precise match be found based on the nodes? var signatureIndex = GetExactSignatureIndex(state); if (signatureIndex >= 0) { // Yes a precise match was found. state.Signature = state.DataSet.Signatures[signatureIndex]; state.Method = MatchMethods.Exact; state.LowestScore = 0; } else { // No. So find any other nodes that match if numeric differences // are considered. EvaluateNumeric(state); // Can a precise match be found based on the nodes? signatureIndex = GetExactSignatureIndex(state); if (signatureIndex >= 0) { // Yes a precise match was found. state.Signature = state.DataSet.Signatures[signatureIndex]; state.Method = MatchMethods.Numeric; } else if (state.Nodes.Count > 0) { // Look for the closest signatures to the nodes found. var closestSignatures = GetClosestSignatures(state); #if DEBUG // Validate the list is in ascending order of ranked signature index. var enumerator = closestSignatures.GetEnumerator(); var lastIndex = -1; while (enumerator.MoveNext()) { Debug.Assert(lastIndex < enumerator.Current); lastIndex = enumerator.Current; } #endif // Try finding a signature with identical nodes just // not in exactly the same place. _nearest.EvaluateSignatures(state, closestSignatures); if (state.Signature != null) { // All the sub strings matched, just in different // character positions. state.Method = MatchMethods.Nearest; } else { // Find the closest signatures and compare them // to the target looking at the smallest character // difference. _closest.EvaluateSignatures(state, closestSignatures); state.Method = MatchMethods.Closest; } } } // If there still isn't a signature then set the default. if (state.Signature == null) { MatchDefault(state); } } }
/// <summary> /// Gets the score for the specific node of the signature. /// </summary> /// <param name="state"></param> /// <param name="node"></param> /// <returns></returns> protected abstract int GetScore(MatchState state, Node node);