# frozen_string_literal: true # Copyright (c) 2018 Yegor Bugayenko # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the 'Software'), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. require 'openssl' require 'score_suffix/score_suffix' require 'time' # Zold score. # # To calculate a score you first have to create a zero score and then # call its next() method: # # first = Score.new(host: 'example.org', invoice: 'PREFIX@0000000000000000') # second = first.next # # More information about the algorithm you can find in the # {White Paper}[https://papers.zold.io/wp.pdf]. # # Author:: Yegor Bugayenko (yegor256@gmail.com) # Copyright:: Copyright (c) 2018 Yegor Bugayenko # License:: MIT module Zold # Score class Score # Default strength for the entire system, in production mode. The larger # the number, the more difficult it is to find the next score for # a node. If the number if too small, the values of the score will be # big and the amount of data to be transferred from node to node will # increase. The number is set empirically. STRENGTH = 8 attr_reader :time, :host, :port, :invoice, :suffixes, :strength, :created # Makes a new object of the class. def initialize(time: Time.now, host:, port: 4096, invoice:, suffixes: [], strength: Score::STRENGTH, created: Time.now) @time = time @host = host @port = port @invoice = invoice @suffixes = suffixes @strength = strength @created = created end # The default no-value score. ZERO = Score.new(time: Time.now, host: 'localhost', invoice: 'NOPREFIX@ffffffffffffffff') # Parses it back from the JSON. def self.parse_json(json) raise "Time in JSON is broken: #{json}" unless json['time'] =~ /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z$/ raise "Host is wrong: #{json}" unless json['host'] =~ /^[0-9a-z\.\-]+$/ raise "Port is wrong: #{json}" unless json['port'].is_a?(Integer) raise "Invoice is wrong: #{json}" unless json['invoice'] =~ /^[a-zA-Z0-9]{8,32}@[a-f0-9]{16}$/ raise "Suffixes not array: #{json}" unless json['suffixes'].is_a?(Array) Score.new( time: Time.parse(json['time']), host: json['host'], port: json['port'], invoice: json['invoice'], suffixes: json['suffixes'], strength: json['strength'] ) end # Pattern to match from string PTN = Regexp.new( '^' + [ '([0-9]+)/(?[0-9]+):', ' (?