<commit_before>require 'socket'
require 'timeout'
require 'securerandom'
require 'torckapi/tracker/base'

module Torckapi
  module Tracker
    # Implementation of http://www.bittorrent.org/beps/bep_0015.html
    class UDP < Base
      def announce info_hash, peer_id=SecureRandom.random_bytes(20)
        super
        perform_request Announce, announce_request_data(info_hash, peer_id), info_hash
      end

      def scrape info_hashes=[]
        super
        perform_request Scrape, scrape_request_data(info_hashes), info_hashes
      end

      private

      CONNECTION_TIMEOUT = 60
      REQUEST_ACTIONS = [Connect = 0, Announce = 1, Scrape = 2].freeze
      RESPONSE_CLASSES = [nil, Torckapi::Response::Announce, Torckapi::Response::Scrape, Torckapi::Response::Error].freeze
      RESPONSE_MIN_LENGTHS = [16, 20, 8, 8].freeze
      RESPONSE_CODES = 0..RESPONSE_CLASSES.length

      def initialize url, options={}
        super
        @state = nil
        @connection_id = nil
        @communicated_at = 0
      end

      def connected?
        @connection_id && @communicated_at.to_i >= Time.now.to_i - CONNECTION_TIMEOUT
      end

      def connecting?
        @state == :connecting
      end

      def perform_request action, data, *args
        response = communicate action, data

        RESPONSE_CLASSES[response[:code]].from_udp(*args, response[:data])
      end

      def announce_request_data info_hash, peer_id
        [[info_hash].pack('H*'), peer_id, [0, 0, 0, 0, 0, 0, -1, 0].pack('Q>3L>4S>')].join
      end

      def scrape_request_data info_hashes
        info_hashes.map { |i| [i].pack('H*') }.join
      end

      def connect
        return if connected? || connecting?

        @state, @connection_id = :connecting, [0x041727101980].pack('Q>')
        response = communicate Connect
        @state, @connection_id = nil, response[:data]
      end

      def communicate action, data=nil
        @socket ||= UDPSocket.new

        tries = 0
        response = nil

        begin
          timeout = @options[:timeout] * (2 ** tries)
          connect
          transaction_id = SecureRandom.random_bytes(4)
          packet = [@connection_id, [action].pack('L>'), transaction_id, data].join

          Timeout::timeout(timeout, CommunicationTimeoutError) do
            @socket.send(packet, 0, @url.host, @url.port)
            response = process_response @socket.recvfrom(65536)[0], transaction_id
            @communicated_at = Time.now
          end
        rescue CommunicationTimeoutError, LittleEndianResponseError => e
          retry if (tries += 1) <= @options[:tries]
        end

        raise CommunicationFailedError unless response

        response
      end

      def process_response response, transaction_id
        check_transaction_id response, transaction_id
        response_code = extract_response_code response
        check_response_length response, response_code

        {code: response_code, data: response[8..-1]}
      end

      def check_transaction_id response, transaction_id
        raise TransactionIdMismatchError, response if transaction_id != response[4..7]
      end

      def extract_response_code response
        response_code, response_code_le = [response[0..3]].flat_map { |x| [x.unpack('L>')[0], x.unpack('L<')[0]] }
<commit_msg>cut off strange timeouts; refactor retries<commit_after>require 'socket'
require 'timeout'
require 'securerandom'
require 'torckapi/tracker/base'

module Torckapi
  module Tracker
    # Implementation of http://www.bittorrent.org/beps/bep_0015.html
    class UDP < Base
      def announce info_hash, peer_id=SecureRandom.random_bytes(20)
        super
        perform_request Announce, announce_request_data(info_hash, peer_id), info_hash
      end

      def scrape info_hashes=[]
        super
        perform_request Scrape, scrape_request_data(info_hashes), info_hashes
      end

      private

      CONNECTION_TIMEOUT = 60
      REQUEST_ACTIONS = [Connect = 0, Announce = 1, Scrape = 2].freeze
      RESPONSE_CLASSES = [nil, Torckapi::Response::Announce, Torckapi::Response::Scrape, Torckapi::Response::Error].freeze
      RESPONSE_MIN_LENGTHS = [16, 20, 8, 8].freeze
      RESPONSE_CODES = 0..RESPONSE_CLASSES.length

      def initialize url, options={}
        super
        @state = nil
        @connection_id = nil
        @communicated_at = 0
      end

      def connected?
        @connection_id && @communicated_at.to_i >= Time.now.to_i - CONNECTION_TIMEOUT
      end

      def connecting?
        @state == :connecting
      end

      def perform_request action, data, *args
        response = communicate action, data

        RESPONSE_CLASSES[response[:code]].from_udp(*args, response[:data])
      end

      def announce_request_data info_hash, peer_id
        [[info_hash].pack('H*'), peer_id, [0, 0, 0, 0, 0, 0, -1, 0].pack('Q>3L>4S>')].join
      end

      def scrape_request_data info_hashes
        info_hashes.map { |i| [i].pack('H*') }.join
      end

      def connect
        return if connected? || connecting?

        @state, @connection_id = :connecting, [0x041727101980].pack('Q>')
        response = communicate Connect
        @state, @connection_id = nil, response[:data]
      end

      def communicate action, data=nil
        @socket ||= UDPSocket.new

        tries = 0
        response = nil

        begin
          connect
          transaction_id = SecureRandom.random_bytes(4)
          packet = [@connection_id, [action].pack('L>'), transaction_id, data].join

          Timeout::timeout(@options[:timeout], CommunicationTimeoutError) do
            @socket.send(packet, 0, @url.host, @url.port)
            response = process_response @socket.recvfrom(65536)[0], transaction_id
            @communicated_at = Time.now
          end
          response
        rescue CommunicationTimeoutError, LittleEndianResponseError => e
          if (tries += 1) <= @options[:tries]
            retry
          else
            raise CommunicationFailedError
          end
        end
      end

      def process_response response, transaction_id
        check_transaction_id response, transaction_id
        response_code = extract_response_code response
        check_response_length response, response_code

        {code: response_code, data: response[8..-1]}
      end

      def check_transaction_id response, transaction_id
        raise TransactionIdMismatchError, response if transaction_id != response[4..7]
      end

      def extract_response_code response
        response_code, response_code_le = [response[0..3]].flat_map { |x| [x.unpack('L>')[0], x.unpack('L<')[0]] }

{'fraction_non_alphanumeric': 0.0986891667922254, 'fraction_numerical': 0.018231128521922556, 'mean_word_length': 2.645249862712795, 'pattern_counts': {'":': 0, '<': 9, '<?xml version=': 0, '>': 19, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1433808', 'n_tokens_mistral': 2113, 'n_tokens_neox': 1962, 'n_words': 457}