# Copyright (c) 2022 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details. # frozen_string_literal: true # require 'contrast/components/logger' # require 'contrast/agent/telemetry/events/exceptions/telemetry_exception_event' require 'contrast/utils/duck_utils' module Contrast module Utils # This is the RoutesSent class, which determines whether observed routes can be sent to TeamServer. # Routes that have not been seen (according to the cache) can be sent, as well as any route that # # has been seen but not within the time limit. class RoutesSent # include Contrast::Components::Logger::InstanceMethods ROUTES_LIMIT = 500 TIME_LIMIT_IN_SECONDS = 60 attr_accessor :cache def initialize @cache = {} end # Determine whether the provided route can be sent to TeamServer. # # @param route [Contrast::Agent::Reporting::ObservedRoute] the route # @return [boolean] def sendable? route return false if Contrast::Utils::DuckUtils.empty_duck?(route.signature) return false if Contrast::Utils::DuckUtils.empty_duck?(route.url) route_hash = route.hash_id # If hash doesn't exist in @cache... # - Add hash to @cache (with Time.now) # - Clear oldest entries (if more than ROUTES_LIMIT) # - Return *true* unless cache.key?(route_hash) cache[route_hash] = Time.now remove_oldest_entries! return true end # If hash exists in @cache... # - Return *true* if more than a minute since time recorded for hash # - Return *false* if not than a minute since time recorded for hash return false unless Time.now.to_i - cache.fetch(route_hash, 0).to_i > TIME_LIMIT_IN_SECONDS cache[route_hash] = Time.now true end private def remove_oldest_entries! return if cache.size < ROUTES_LIMIT route_hashes = cache.sort_by { |_, v| -v.tv_nsec }. to_h.keys.slice(0, ROUTES_LIMIT) @cache = cache.slice(*route_hashes) end end end end