├── tmp └── .gitkeep ├── .ruby-version ├── public ├── favicon.ico ├── javascripts │ ├── clipboard.js │ ├── navigation.js │ └── drop_zone.js ├── images │ ├── xorro_p2p_logo.png │ └── clipboard_icon.svg └── css │ ├── whitespace-reset.css │ └── styles.css ├── .gitignore ├── config.yml ├── bin ├── kill_nodes.sh ├── launch_public_super.sh ├── launch_nat_public.sh ├── launch_pf_public.sh ├── launch_leech.sh ├── launch_local_super.sh ├── gcloud30.sh ├── launch_local_range.sh └── launch_local_nodes.sh ├── Rakefile ├── views ├── test.erb ├── get_file.erb ├── upload_file.erb ├── dht.erb ├── my_files.erb ├── node_info.erb ├── routing_table.erb ├── data.erb ├── rpc.erb └── layout.erb ├── Gemfile ├── test ├── network_adapter_test.rb ├── test_helper.rb ├── contact_test.rb ├── binary_4bit_test.rb ├── binary_6bit_test.rb ├── binary_160bit_test.rb ├── kbucket_4bit_test.rb ├── kbucket_6bit_test.rb ├── kbucket_160bit_test.rb ├── kbucket_6bit_8k_test.rb ├── kbucket_160bit_8k_3a_test.rb ├── routing_table_4bit_test.rb ├── routing_table_6bit_test.rb ├── routing_table_160bit_test.rb ├── routing_table_6bit_8k_test.rb ├── node_4bit_test.rb ├── node_6bit_test.rb ├── node_160bit_test.rb ├── routing_table_160bit_8k_test.rb ├── node_6bit_8k_3a_test.rb └── node_160bit_8k_3a_test.rb ├── lib ├── contact.rb ├── storage.rb ├── binary.rb ├── defaults.rb ├── fake_network_adapter.rb ├── kbucket.rb ├── network_adapter.rb ├── routing_table.rb └── node.rb ├── spec.txt ├── README.md └── app.rb /tmp/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.ruby-version: -------------------------------------------------------------------------------- 1 | 2.3.1 -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/javascripts/clipboard.js: -------------------------------------------------------------------------------- 1 | new Clipboard('.copy-btn'); 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | Gemfile.lock 2 | .DS_Store 3 | tmp/* 4 | !tmp/.gitkeep 5 | -------------------------------------------------------------------------------- /config.yml: -------------------------------------------------------------------------------- 1 | username: admin 2 | password: password 3 | node_homes: ~/Desktop/xorro_homes -------------------------------------------------------------------------------- /public/images/xorro_p2p_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xorro-p2p/xorro/HEAD/public/images/xorro_p2p_logo.png -------------------------------------------------------------------------------- /bin/kill_nodes.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | while read p 4 | do 5 | kill -9 $p 6 | done/dev/null 9 | 10 | > tmp/pids.txt 11 | 12 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'rake/testtask' 2 | require_relative 'test/test_helper.rb' 3 | 4 | task default: %w[test] 5 | 6 | Rake::TestTask.new do |t| 7 | t.pattern = "test/*_test.rb" 8 | end 9 | -------------------------------------------------------------------------------- /public/javascripts/navigation.js: -------------------------------------------------------------------------------- 1 | var path = window.location.pathname 2 | if (!path.includes("debug")) { 3 | $("#sidenav > ul > li a[href='" + path + "']").parent().addClass('active'); 4 | } 5 | -------------------------------------------------------------------------------- /views/test.erb: -------------------------------------------------------------------------------- 1 | <% if @result.class == Array %> 2 | <% @result.each do |c| %> 3 |

<%= c.id %> | http://<%= c.ip %>:<%= c.port %>

4 | <% end %> 5 | <% else %> 6 | <%= @result %> 7 | <% end %> 8 | 9 | -------------------------------------------------------------------------------- /views/get_file.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 | 5 |
6 | 7 |
8 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | ruby "2.3.1" 4 | gem "sinatra" 5 | gem "sinatra-contrib" 6 | gem "json" 7 | gem "erubis" 8 | gem "http" 9 | gem "sinatra-flash" 10 | gem 'ngrok-tunnel' 11 | gem 'thin' 12 | gem 'concurrent-ruby', require: 'concurrent' 13 | 14 | group :test, :development do 15 | gem "minitest" 16 | gem "minitest-reporters" 17 | end -------------------------------------------------------------------------------- /test/network_adapter_test.rb: -------------------------------------------------------------------------------- 1 | require_relative 'test_helper.rb' 2 | require_relative '../lib/fake_network_adapter.rb' 3 | 4 | class NetworkAdapterTest < Minitest::Test 5 | def setup 6 | @kn = FakeNetworkAdapter.new 7 | end 8 | 9 | def test_create_network_adapter 10 | assert_instance_of(FakeNetworkAdapter, @kn) 11 | end 12 | 13 | def test_kn_has_nodes 14 | assert_equal([], @kn.nodes) 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /public/images/clipboard_icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /test/test_helper.rb: -------------------------------------------------------------------------------- 1 | require 'minitest/autorun' 2 | require 'minitest/reporters' 3 | require_relative '../lib/defaults.rb' 4 | Defaults::ENVIRONMENT[:files] = "/dev/null" 5 | Defaults::ENVIRONMENT[:shards] = "/dev/null" 6 | Defaults::ENVIRONMENT[:manifests] = "/dev/null" 7 | Defaults::ENVIRONMENT[:bit_length] = 6 8 | Defaults::ENVIRONMENT[:k] = 2 9 | Defaults::ENVIRONMENT[:alpha] = 1 10 | Defaults::ENVIRONMENT[:test] = true 11 | 12 | reporter_options = { color: true } 13 | Minitest::Reporters.use! [Minitest::Reporters::DefaultReporter.new(reporter_options)] 14 | -------------------------------------------------------------------------------- /views/upload_file.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 | 5 | 6 |
7 |
Uploading…
8 |
Done!
9 |
Error! .
10 |
-------------------------------------------------------------------------------- /views/dht.erb: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | 5 | 6 | 7 | <% @node.dht_segment.each do |k,v| %> 8 | 9 | 15 | 20 | 21 | <% end %> 22 |
File IDLocation
10 | 13 | <%= k %> 14 | 16 | <% v.each do |url| %> 17 | 18 | <% end %> 19 |
23 |
24 | -------------------------------------------------------------------------------- /views/my_files.erb: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | 5 | 6 | 7 | <% @node.files.each do |k,v| %> 8 | 9 | 15 | 18 | 19 | <% end %> 20 |
File IDLocation
10 | 13 | <%= k %> 14 | 16 | 17 |
21 |
22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /lib/contact.rb: -------------------------------------------------------------------------------- 1 | require 'json' 2 | 3 | class Contact 4 | attr_reader :id, :ip, :port, :last_seen, :active 5 | 6 | def initialize(options) ### {id: '2342342', ip: '23.24.55.8', port: 80} 7 | @id = options[:id] 8 | @ip = options[:ip] || 'localhost' 9 | @port = options[:port] || 80 10 | @last_seen = Time.now 11 | @active = nil 12 | end 13 | 14 | def activate 15 | @active = true 16 | end 17 | 18 | def update_last_seen 19 | @last_seen = Time.now 20 | end 21 | 22 | def to_json(*options) 23 | as_json.to_json(*options) 24 | end 25 | 26 | private 27 | 28 | def as_json 29 | { 30 | id: @id, 31 | ip: @ip, 32 | port: @port 33 | } 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /test/contact_test.rb: -------------------------------------------------------------------------------- 1 | require_relative 'test_helper.rb' 2 | require_relative '../lib/node.rb' 3 | require_relative "../lib/contact.rb" 4 | require_relative "../lib/fake_network_adapter.rb" 5 | 6 | class ContactTest < Minitest::Test 7 | def setup 8 | @kn = FakeNetworkAdapter.new 9 | @node = Node.new('0', @kn) 10 | @options = { id: @node.id, ip: @node.ip } 11 | end 12 | 13 | def test_create_contact 14 | contact = Contact.new(@options) 15 | assert_instance_of(Contact, contact) 16 | end 17 | 18 | def test_update_last_seen 19 | contact = Contact.new(@options) 20 | last_seen = contact.last_seen 21 | 22 | contact.update_last_seen 23 | assert_operator(last_seen, :<, contact.last_seen) 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /views/node_info.erb: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | 5 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 |
id 6 | <%= @node.id %> 7 | 10 |
ipv4<%= @node.ip %>
port<%= @node.port %>
super<%= @node.is_super %>
superport<%= @superport %>
29 |
30 | -------------------------------------------------------------------------------- /lib/storage.rb: -------------------------------------------------------------------------------- 1 | require 'yaml' 2 | require_relative 'defaults.rb' 3 | 4 | module Storage 5 | def self.file_exists?(file_name='id.yml') 6 | File.exist?(File.join(Defaults::ENVIRONMENT[:xorro_home], file_name)) 7 | end 8 | 9 | def self.valid_node?(file_name='id.yml') 10 | file = YAML.load_file(File.join(Defaults::ENVIRONMENT[:xorro_home], file_name)) 11 | file && file.id && file.id.to_i.between?(0, 2**Defaults::ENVIRONMENT[:bit_length] - 1) 12 | end 13 | 14 | def self.load_file(file_name='id.yml') 15 | YAML.load_file(File.open(File.join(Defaults::ENVIRONMENT[:xorro_home], file_name))) 16 | end 17 | 18 | def self.write_to_disk(node, file_name='id.yml') 19 | return if Defaults::ENVIRONMENT[:test] == true 20 | 21 | File.open(File.join(Defaults::ENVIRONMENT[:xorro_home], file_name), 'w') { |f| f.write(node.to_yaml) } 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /bin/launch_public_super.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | PORT=9999 3 | FQDN=supernode1.xorro-p2p.com 4 | 5 | help_message() { 6 | echo 7 | echo 'looks like you need some help using this tool.' 8 | echo 9 | echo 'usage: "launch_public_super.sh port_number"' 10 | echo 11 | echo "This script is specifically for launching the supernode in an environment where:" 12 | echo "1: DNS name already exists for this supernode - configure it as the FQDN variable in the script" 13 | echo "2: Supernode is either directly on the internet, or port forwarding/NAT is properly set up for the PORT variable" 14 | echo 15 | } 16 | 17 | if [[ $# -ne 0 ]] || [[ $1 == '-h' ]]; then 18 | help_message 19 | exit 0 20 | fi 21 | 22 | launch_public_super() { 23 | PORT=$PORT SUPERPORT='' SUPER=true FQDN=$FQDN nohup ruby app.rb >> tmp/nohup.out & 24 | echo $! >> tmp/pids.txt 25 | } 26 | 27 | launch_aws_super -------------------------------------------------------------------------------- /bin/launch_nat_public.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | PORT=9999 4 | SUPERPORT=9999 5 | SUPERIP=supernode1.xorro-p2p.com 6 | 7 | help_message() { 8 | echo 9 | echo 'looks like you need some help using this tool.' 10 | echo 11 | echo 'usage: "launch_public.sh local_port_number"' 12 | echo 13 | echo "This script does the following:" 14 | echo "--Launches local node/sinatra instance on port number passed in." 15 | echo "Creates ngrok tunnel to localhost." 16 | echo "Starts communications with default supernode in AWS cloud on port 3500." 17 | echo 18 | echo "Node can be killed by running bin/kill_nodes.sh" 19 | } 20 | 21 | if [[ $# -ne 0 ]] || [[ $1 == '-h' ]]; then 22 | help_message 23 | exit 0 24 | fi 25 | 26 | launch_nat_public() { 27 | PORT=$PORT SUPERPORT=$SUPERPORT SUPERIP=$SUPERIP NAT=true nohup ruby app.rb >> tmp/nohup.out & 28 | echo $! >> tmp/pids.txt 29 | } 30 | 31 | launch_nat_public -------------------------------------------------------------------------------- /bin/launch_pf_public.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | SUPERPORT=9999 3 | SUPERIP=supernode1.xorro-p2p.com 4 | 5 | help_message() { 6 | echo 7 | echo 'looks like you need some help using this tool.' 8 | echo 9 | echo 'usage: "launch_pf_public.sh port_number FQDN"' 10 | echo 11 | echo "This script is specifically for launching a public facing client node in an environment where either" 12 | echo "A: system is directly on the public internet with a publicly accessible IP address" 13 | echo "B: system is behind a nat/firewall, but port forwarding is properly configured for the port_number passed in as the first argument" 14 | } 15 | 16 | if [[ $# -ne 2 ]] || [[ $1 == '-h' ]]; then 17 | help_message 18 | exit 0 19 | fi 20 | 21 | launch_pf_public() { 22 | PORT=$1 SUPERPORT=$SUPERPORT SUPERIP=$SUPERIP SUPER=false FQDN=$2 nohup ruby app.rb >> tmp/nohup.out & 23 | echo $! >> tmp/pids.txt 24 | } 25 | 26 | launch_pf_public $@ -------------------------------------------------------------------------------- /bin/launch_leech.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | SUPERPORT=9999 3 | PORT=9999 4 | SUPERIP=supernode1.xorro-p2p.com 5 | IP=`dig +short myip.opendns.com @resolver1.opendns.com` 6 | 7 | ## takes one argument -- port to run leech node on. 8 | 9 | 10 | help_message() { 11 | echo 12 | echo 'looks like you need some help using this tool.' 13 | echo 14 | echo 'usage: "launch_leech.sh port_number"' 15 | echo 16 | echo "This script is specifically for launching a client node that is behind a NAT/Firewall that:" 17 | echo " A: does not want to contribute to network," 18 | echo " B: does not have any 3rd party tunnelling set up (Ngrok)" 19 | 20 | } 21 | 22 | if [[ $# -ne 0 ]] || [[ $1 == '-h' ]]; then 23 | help_message 24 | exit 0 25 | fi 26 | 27 | launch_leech() { 28 | PORT=$PORT SUPERPORT=$SUPERPORT SUPERIP=$SUPERIP SUPER=false LEECH=true FQDN=$IP nohup ruby app.rb >> tmp/nohup.out & 29 | echo $! >> tmp/pids.txt 30 | } 31 | 32 | launch_leech -------------------------------------------------------------------------------- /public/css/whitespace-reset.css: -------------------------------------------------------------------------------- 1 | /* 2 | ---------------------------------------- 3 | Tantek Celik's Whitepsace Reset 4 | Author: Tantek Celik, Shane Riley 5 | Version: (CC) 2010 Some Rights Reserved - http://creativecommons.org/licenses/by/2.0 6 | Description: Resets default styling of common browsers to a common base 7 | ---------------------------------------- 8 | */ 9 | 10 | ul,ol { list-style: none; } 11 | h1,h2,h3,h4,h5,h6,pre,code { font-size: 1em; } 12 | ul,ol,li,h1,h2,h3,h4,h5,h6,pre,form,body,html,p,blockquote,fieldset,input,dl,dt,dd, figure, figcaption { 13 | margin: 0; 14 | padding: 0; } 15 | a img,:link img,:visited img, fieldset { border: none; } 16 | address { font-style: normal; } 17 | header, section, article, nav, footer, hgroup, details, summary, figure, main { display: block; } 18 | mark { 19 | color: inherit; 20 | background: transparent; } 21 | abbr { border: none; } 22 | summary::-webkit-details-marker { display: none; } 23 | -------------------------------------------------------------------------------- /views/routing_table.erb: -------------------------------------------------------------------------------- 1 |
2 |

Total k-buckets: <%= @node.routing_table.buckets.size %>

3 | <% @node.routing_table.buckets.each_with_index do |b,x| %> 4 |
5 |
6 |
7 | Bucket 8 |
9 |
10 | <%= x %> 11 |
12 |
13 | Size 14 |
15 |
16 | <%= b.size %> 17 |
18 |
19 | Contacts 20 |
21 |
22 |
    23 | <% b.contacts. each do |c| %> 24 |
  • id: <%= c.id %>
  • 25 |
  • ip: <%= c.ip %>:<%= c.port %>
  • 26 |
  • last seen: <%= c.last_seen %>
  • 27 |
    28 | <% end %> 29 |
30 |
31 |
32 |
33 | <% end %> 34 |
35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /lib/binary.rb: -------------------------------------------------------------------------------- 1 | require 'digest/sha1' 2 | require_relative 'defaults' 3 | 4 | module Binary 5 | def self.xor_distance(id_string1, id_string2) 6 | id_string1.to_i ^ id_string2.to_i 7 | end 8 | 9 | def self.shared_prefix_bit_length(id_string1, id_string2) 10 | return Defaults::ENVIRONMENT[:bit_length] if id_string1 == id_string2 11 | distance = xor_distance(id_string1, id_string2) 12 | Defaults::ENVIRONMENT[:bit_length] - distance.to_s(2).size 13 | end 14 | 15 | def self.sha(str) 16 | Digest::SHA1.hexdigest(str) 17 | end 18 | 19 | def self.shared_prefix_bit_length_map(source_node_id, array) 20 | array.map do |item| 21 | shared_prefix_bit_length(source_node_id, item.id) 22 | end 23 | end 24 | 25 | def self.select_closest_xor(id, array) 26 | xors = array.map do |el| 27 | el.id.to_i ^ id.to_i 28 | end 29 | idx = xors.index(xors.min) 30 | idx ? array[idx] : nil 31 | end 32 | 33 | def self.xor_distance_map(id, array) 34 | array.map { |obj| xor_distance(id, obj.id) } 35 | end 36 | 37 | def self.sort_by_xor!(id, array) 38 | array.sort! do |x, y| 39 | xor_distance(x.id, id) <=> xor_distance(y.id, id) 40 | end 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /views/data.erb: -------------------------------------------------------------------------------- 1 |
2 |

Shards

3 | 4 | 5 | 6 | 7 | 8 | <% @node.shards.each do |k,v| %> 9 | 10 | 16 | 19 | 20 | <% end %> 21 |
File IDLocation
11 | 14 | <%= k %> 15 | 17 | 18 |
22 |
23 |

Manifests

24 | 25 | 26 | 27 | 28 | 29 | <% @node.manifests.each do |k,v| %> 30 | 31 | 37 | 40 | 41 | <% end %> 42 |
File IDLocation
32 | 35 | <%= k %> 36 | 38 | 39 |
43 |
44 | -------------------------------------------------------------------------------- /bin/launch_local_super.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ## launches single supernode on local machine, 4 | ## take 1 argument: port that sinatra web app will run on. 5 | ## no nat translation or communications with network are assumed 6 | 7 | help_message() { 8 | echo 9 | echo 'looks like you need some help using this tool.' 10 | echo 11 | echo 'usage: "launch_super.sh port_number"' 12 | echo 13 | echo "Only one port number is accepted. A supernode will be launched with a Sinatra webserver running on that port" 14 | echo "The process will be backgrounded, and the PID written to pids.txt" 15 | echo 16 | echo "You can a range of launch client nodes using launch_range, passing in the supernode port, client starting port, and client ending port." 17 | echo 18 | echo "You can quit the processes in bulk using kill_nodes.sh, which iterates through pids.txt," 19 | echo "kills each process, then overwrites the file" 20 | echo 21 | echo "Alternatively you can locate each pid using" 22 | echo "lsof -i \$port_number" 23 | echo "and kill it manually:" 24 | echo "kill \$pid" 25 | echo "however this could result in inconsistencies with your pids.txt file." 26 | echo 27 | } 28 | 29 | if [[ $# -ne 1 ]] || [[ $1 == '-h' ]]; then 30 | help_message 31 | exit 0 32 | fi 33 | 34 | launch_local_super() { 35 | PORT=$1 SUPERPORT='' SUPER=true nohup ruby app.rb >> tmp/nohup.out & 36 | echo $! >> tmp/pids.txt 37 | } 38 | 39 | launch_local_super $@ -------------------------------------------------------------------------------- /lib/defaults.rb: -------------------------------------------------------------------------------- 1 | require 'yaml' 2 | 3 | module Defaults 4 | ENVIRONMENT = { 5 | k: 8, 6 | bit_length: 160, 7 | alpha: 3 8 | } 9 | 10 | def self.setup(port, node_homes_path) 11 | node_homes = File.expand_path(node_homes_path) 12 | safe_mkdir(node_homes) 13 | create_node_home(node_homes, port) 14 | end 15 | 16 | def self.create_node_home(node_homes, port) 17 | node_home = File.join(node_homes, port.to_s) 18 | safe_mkdir(node_home) 19 | create_subfolders(node_home) 20 | ENVIRONMENT[:xorro_home] = node_home 21 | end 22 | 23 | def self.create_subfolders(node_home) 24 | files = File.join(node_home, "/files") 25 | manifests = File.join(node_home, "/manifests") 26 | shards = File.join(node_home, "/shards") 27 | 28 | [files, manifests, shards].each do |f| 29 | safe_mkdir(f) 30 | ENVIRONMENT[File.basename(f).to_sym] = f 31 | ### :shards, :files, :manifests 32 | end 33 | end 34 | 35 | def self.create_node(network, port) 36 | if Storage.file_exists? && Storage.valid_node? 37 | node = Storage.load_file 38 | node.generate_file_cache 39 | else 40 | node = Node.new(new_id, network, port) 41 | end 42 | node.set_super 43 | node.sync 44 | node 45 | end 46 | 47 | def self.new_id 48 | rand(2**ENVIRONMENT[:bit_length]).to_s 49 | end 50 | 51 | def self.safe_mkdir(dir) 52 | Dir.mkdir(dir) unless Dir.exist?(dir) 53 | end 54 | end 55 | -------------------------------------------------------------------------------- /lib/fake_network_adapter.rb: -------------------------------------------------------------------------------- 1 | class FakeNetworkAdapter 2 | attr_reader :nodes 3 | 4 | def initialize 5 | @nodes = [] 6 | end 7 | 8 | def store(file_id, address, recipient_contact, sender_contact) 9 | recipient_node = get_node_by_contact(recipient_contact) 10 | recipient_node.receive_store(file_id, address, sender_contact) 11 | node = get_node_by_contact(sender_contact) 12 | node.ping(recipient_contact) 13 | nil 14 | end 15 | 16 | def find_node(query_id, recipient_contact, sender_contact) 17 | recipient_node = get_node_by_contact(recipient_contact) 18 | closest_nodes = recipient_node.receive_find_node(query_id, sender_contact) 19 | node = get_node_by_contact(sender_contact) 20 | node.ping(recipient_contact) 21 | closest_nodes 22 | end 23 | 24 | def find_value(file_id, recipient_contact, sender_contact) 25 | recipient_node = get_node_by_contact(recipient_contact) 26 | node = get_node_by_contact(sender_contact) 27 | node.ping(recipient_contact) 28 | recipient_node.receive_find_value(file_id, sender_contact) 29 | end 30 | 31 | def ping(contact, sender_contact) 32 | recipient_node = get_node_by_contact(contact) 33 | 34 | if recipient_node 35 | recipient_node.receive_ping(sender_contact) 36 | contact.update_last_seen 37 | return true 38 | else 39 | return false 40 | end 41 | end 42 | 43 | def info 44 | nil 45 | end 46 | 47 | def check_resource_status(_address) 48 | 200 49 | end 50 | 51 | private 52 | 53 | def get_node_by_contact(contact) 54 | @nodes.find { |n| n.id == contact.id } 55 | end 56 | end 57 | -------------------------------------------------------------------------------- /bin/gcloud30.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | DOMAIN=xorro-p2p.com 4 | SUPERDNSNAME=supernode1.$DOMAIN 5 | CLIENTPREFIX=client 6 | SUPERPORT=9999 7 | 8 | help_message() { 9 | echo 10 | echo 'looks like you need some help using this tool.' 11 | echo 12 | echo 'usage: "gcloud30.sh"' 13 | echo 14 | echo "This script is specifically for launching the xorro demo environment: supernode on gcloud/aws server with 30 non-super nodes in same network." 15 | echo "DNS records for all nodes launched needs to be set up in advance" 16 | echo "DNS record and supernode IP/port will be hard coded into nodes @ip ivar." 17 | echo 18 | } 19 | 20 | if [[ $# -ne 0 ]] || [[ $1 == '-h' ]]; then 21 | help_message 22 | exit 0 23 | fi 24 | 25 | launch_gc_super() { 26 | #supernode broadcasts on superport, but has no separate superport that it checks in with 27 | PORT=$SUPERPORT SUPERPORT='' SUPER=true FQDN=$SUPERDNSNAME nohup ruby app.rb >> tmp/nohup.out & 28 | echo $! >> tmp/pids.txt 29 | } 30 | 31 | launch_gc_range() { 32 | ## iterate through range, creating non-super (client) node on port (9000 + range[p]), named 'client' + $p + '.xorro-p2p.com' 33 | for p in $(seq $1 $2) 34 | do 35 | port=$(expr 9000 + $p) 36 | name=$CLIENTPREFIX$p.$DOMAIN 37 | launch_gc_client $port $name 38 | sleep 0.25 39 | done 40 | } 41 | 42 | launch_gc_client() { 43 | #client broadcasts on unique port, checks in with supernode on $SUPERPORT 44 | PORT=$1 SUPERPORT=$SUPERPORT SUPERIP=$SUPERDNSNAME SUPER=false FQDN=$2 nohup ruby app.rb >> tmp/nohup.out & 45 | echo $! >> tmp/pids.txt 46 | } 47 | 48 | launch_gc_super 49 | sleep 3 50 | launch_gc_range 1 30 -------------------------------------------------------------------------------- /public/javascripts/drop_zone.js: -------------------------------------------------------------------------------- 1 | var $form = $('.box'); 2 | var $fileInput = $('.box_file'); 3 | var droppedFiles = false; 4 | 5 | $form.on('drag dragstart dragend dragover dragenter dragleave drop', function(e) { 6 | e.preventDefault(); 7 | e.stopPropagation(); 8 | }) 9 | .on('dragover dragenter', function() { 10 | $form.addClass('is-dragover'); 11 | }) 12 | .on('dragleave dragend drop', function() { 13 | $form.removeClass('is-dragover'); 14 | }) 15 | .on('drop', function(e) { 16 | droppedFiles = e.originalEvent.dataTransfer.files; 17 | $form.trigger('submit'); 18 | }); 19 | 20 | $fileInput.change(function (e) { 21 | droppedFiles = $fileInput[0].files 22 | $form.trigger('submit'); 23 | }); 24 | 25 | $form.on('submit', function(e) { 26 | if ($form.hasClass('is-uploading')) return false; 27 | 28 | $form.addClass('is-uploading').removeClass('is-error'); 29 | 30 | e.preventDefault(); 31 | 32 | for (var i = 0, f; f = droppedFiles[i]; i++) { 33 | var reader = new FileReader(); 34 | 35 | reader.onload = (function(theFile) { 36 | return function(e) { 37 | $.ajax({ 38 | type: $form.attr('method'), 39 | url: $form.attr('action'), 40 | data: { name: theFile.name, data: e.target.result }, 41 | complete: function() { 42 | $form.removeClass('is-uploading'); 43 | }, 44 | success: function(data) { 45 | $form.addClass('is-success'); 46 | }, 47 | error: function() { 48 | // Log the error, show an alert, whatever works for you 49 | } 50 | }); 51 | }; 52 | })(f); 53 | reader.readAsDataURL(f); 54 | } 55 | }); -------------------------------------------------------------------------------- /lib/kbucket.rb: -------------------------------------------------------------------------------- 1 | require_relative 'defaults.rb' 2 | require_relative 'binary.rb' 3 | 4 | class KBucket 5 | attr_reader :splittable, :contacts, :node 6 | 7 | def initialize(node) 8 | @node = node 9 | @contacts = [] 10 | @splittable = true 11 | end 12 | 13 | include Enumerable 14 | def each 15 | @contacts.each do |c| 16 | yield c 17 | end 18 | end 19 | 20 | # candidate for removal if new contact is being added to a full bucket 21 | def head 22 | @contacts.first 23 | end 24 | 25 | def tail 26 | @contacts.last 27 | end 28 | 29 | def full? 30 | @contacts.size == Defaults::ENVIRONMENT[:k] 31 | end 32 | 33 | # is this the bucket that has the longest shared_bits_length? 34 | def hasnt_been_split? 35 | @splittable 36 | end 37 | 38 | def find_contact_by_id(id) 39 | @contacts.find do |c| 40 | c.id == id 41 | end 42 | end 43 | 44 | def redistributable?(node_id, index) 45 | shared_bit_lengths = Binary.shared_prefix_bit_length_map(node_id, @contacts) 46 | 47 | has_moveable_value = shared_bit_lengths.any? do |bit_length| 48 | bit_length > index 49 | end 50 | 51 | has_moveable_value 52 | end 53 | 54 | def make_unsplittable 55 | @splittable = false 56 | @node.sync 57 | end 58 | 59 | def add(contact) 60 | @contacts.push contact 61 | @node.sync 62 | end 63 | 64 | def attempt_eviction(new_contact) 65 | if @node.ping(head) 66 | head.update_last_seen 67 | sort_by_seen 68 | @node.sync 69 | else 70 | delete(head) 71 | add(new_contact) 72 | end 73 | end 74 | 75 | def delete(contact) 76 | return unless @contacts.include? contact 77 | @contacts.delete contact 78 | @node.sync 79 | end 80 | 81 | def sort_by_seen 82 | @contacts.sort_by!(&:last_seen) 83 | end 84 | 85 | def size 86 | contacts.size 87 | end 88 | end 89 | -------------------------------------------------------------------------------- /test/binary_4bit_test.rb: -------------------------------------------------------------------------------- 1 | require_relative 'test_helper.rb' 2 | require_relative "../lib/binary.rb" 3 | require_relative "../lib/contact.rb" 4 | 5 | class BinaryTest < Minitest::Test 6 | def setup 7 | Defaults::ENVIRONMENT[:bit_length] = 4 8 | Defaults::ENVIRONMENT[:k] = 2 9 | end 10 | 11 | def test_xor_distance 12 | assert_equal(1, Binary.xor_distance('0', '1')) 13 | assert_equal(2, Binary.xor_distance('0', '2')) 14 | assert_equal(3, Binary.xor_distance('1', '2')) 15 | assert_equal(8, Binary.xor_distance('0', '8')) 16 | assert_equal(9, Binary.xor_distance('1', '8')) 17 | assert_equal(10, Binary.xor_distance('2', '8')) 18 | end 19 | 20 | def test_shared_prefix_bit_length 21 | # assert_equal(3, Binary.shared_prefix_bit_length('0', '1')) 22 | assert_equal(2, Binary.shared_prefix_bit_length('1', '2')) 23 | assert_equal(3, Binary.shared_prefix_bit_length('2', '3')) 24 | assert_equal(1, Binary.shared_prefix_bit_length('3', '4')) 25 | assert_equal(3, Binary.shared_prefix_bit_length('4', '5')) 26 | assert_equal(2, Binary.shared_prefix_bit_length('7', '5')) 27 | assert_equal(0, Binary.shared_prefix_bit_length('7', '8')) 28 | assert_equal(0, Binary.shared_prefix_bit_length('15', '0')) 29 | end 30 | 31 | def test_sha 32 | assert_equal('aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434d', Binary.sha('hello')) 33 | assert_equal('356a192b7913b04c54574d18c28d46e6395428ab', Binary.sha('1')) 34 | assert_equal(40, Binary.sha(rand(333).to_s).size) 35 | assert_equal(Binary.sha('should be the same'), Binary.sha('should be the same')) 36 | end 37 | 38 | def test_select_closest_xor 39 | array = ['1', '2', '3', '4', '5'].map { |i| Contact.new(id: i) } 40 | assert_equal(Binary.select_closest_xor('0', array).id, '1') 41 | assert_equal(Binary.select_closest_xor('5', array).id, '5') 42 | assert_equal(Binary.select_closest_xor('15', array).id, '5') 43 | end 44 | 45 | def test_sort_by_xor! 46 | array = ['1', '2', '3', '4', '5'] 47 | shuffled = array.shuffle.map { |i| Contact.new(id: i) } 48 | result = Binary.sort_by_xor!('0', shuffled).map(&:id) 49 | assert_equal(array, result) 50 | end 51 | end 52 | -------------------------------------------------------------------------------- /test/binary_6bit_test.rb: -------------------------------------------------------------------------------- 1 | require_relative 'test_helper.rb' 2 | require_relative "../lib/binary.rb" 3 | require_relative "../lib/contact.rb" 4 | 5 | class BinaryTest6 < Minitest::Test 6 | def setup 7 | Defaults::ENVIRONMENT[:bit_length] = 6 8 | Defaults::ENVIRONMENT[:k] = 2 9 | end 10 | 11 | def test_xor_distance 12 | assert_equal(1, Binary.xor_distance('0', '1')) 13 | assert_equal(2, Binary.xor_distance('0', '2')) 14 | assert_equal(3, Binary.xor_distance('1', '2')) 15 | assert_equal(8, Binary.xor_distance('0', '8')) 16 | assert_equal(9, Binary.xor_distance('1', '8')) 17 | assert_equal(10, Binary.xor_distance('2', '8')) 18 | end 19 | 20 | def test_shared_prefix_bit_length 21 | assert_equal(5, Binary.shared_prefix_bit_length('0', '1')) 22 | assert_equal(6, Binary.shared_prefix_bit_length('1', '1')) 23 | assert_equal(4, Binary.shared_prefix_bit_length('1', '2')) 24 | assert_equal(5, Binary.shared_prefix_bit_length('2', '3')) 25 | assert_equal(3, Binary.shared_prefix_bit_length('3', '4')) 26 | assert_equal(5, Binary.shared_prefix_bit_length('4', '5')) 27 | assert_equal(4, Binary.shared_prefix_bit_length('7', '5')) 28 | assert_equal(2, Binary.shared_prefix_bit_length('7', '8')) 29 | assert_equal(0, Binary.shared_prefix_bit_length('63', '0')) 30 | end 31 | 32 | def test_sha 33 | assert_equal('aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434d', Binary.sha('hello')) 34 | assert_equal('356a192b7913b04c54574d18c28d46e6395428ab', Binary.sha('1')) 35 | assert_equal(40, Binary.sha(rand(333).to_s).size) 36 | assert_equal(Binary.sha('should be the same'), Binary.sha('should be the same')) 37 | end 38 | 39 | def test_select_closest_xor 40 | array = ['1', '2', '3', '4', '5'].map { |i| Contact.new(id: i) } 41 | assert_equal(Binary.select_closest_xor('0', array).id, '1') 42 | assert_equal(Binary.select_closest_xor('5', array).id, '5') 43 | assert_equal(Binary.select_closest_xor('15', array).id, '5') 44 | end 45 | 46 | def test_sort_by_xor! 47 | array = ['1', '2', '3', '4', '5'] 48 | shuffled = array.shuffle.map { |i| Contact.new(id: i) } 49 | result = Binary.sort_by_xor!('0', shuffled).map(&:id) 50 | assert_equal(array, result) 51 | end 52 | end 53 | -------------------------------------------------------------------------------- /bin/launch_local_range.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ## launches range of nodes on localhost.. 4 | ## no nat translation or communications outside of localhost are supported 5 | ## 6 | ## takes 3 arguments 7 | ## 1st argument: port of pre-existing supernode on running on localhost -- use launch_local_super.sh to get this. 8 | ## 2nd argument: port of node at start of range 9 | ## 3rd argument: port of node at end of range 10 | ## 11 | ## end result will be a fleet of nodes running on localhost, 1 running on each port within specified range 12 | ## each node will be communicating with a pre-existing localhost supernode 13 | 14 | help_message() { 15 | echo 16 | echo 'looks like you need some help using this tool.' 17 | echo 18 | echo 'usage: "launch_range.sh super_node_port_number starting_port_number ending_port_number"' 19 | echo 20 | echo "Script takes 3 arguments" 21 | echo 22 | echo "The first argument is the port of an ALREADY RUNNING super_node" 23 | echo 24 | echo "The second and third arguments are starting and ending port numbers" 25 | echo "The starting port must be lower than the ending port, and have no overlap with the supernode port" 26 | echo 27 | echo "Client node will be launched on each node within that range inclusive" 28 | echo "Each client will begin communicating with the supernode passed in as the first argument" 29 | echo "The processes will be backgrounded, and the PID written to pids.txt" 30 | 31 | echo "You can quit the processes in bulk using kill_nodes.sh, which iterates through pids.txt," 32 | echo "kills each process, then overwrites the file" 33 | echo 34 | echo "Alternatively you can locate each pid using" 35 | echo "lsof -i \$port_number" 36 | echo "and kill it manually:" 37 | echo "kill \$pid" 38 | echo "however this could result in inconsistencies with your pids.txt file." 39 | echo 40 | } 41 | 42 | launch_range() { 43 | SUPERPORT=$1 44 | for port in $(seq $2 $3) 45 | do 46 | launch_node $port 47 | done 48 | } 49 | 50 | launch_node() { 51 | PORT=$1 SUPERPORT=$SUPERPORT SUPER=false nohup ruby app.rb >> tmp/nohup.out & 52 | echo $! >> tmp/pids.txt 53 | } 54 | 55 | 56 | if [[ $# -ne 3 ]] || [[ $1 == '-h' ]] || [[ $2 -gt $3 ]]; then 57 | help_message 58 | exit 0 59 | fi 60 | 61 | 62 | launch_range $@ -------------------------------------------------------------------------------- /test/binary_160bit_test.rb: -------------------------------------------------------------------------------- 1 | require_relative 'test_helper.rb' 2 | require_relative "../lib/binary.rb" 3 | require_relative "../lib/contact.rb" 4 | 5 | class BinaryTest160 < Minitest::Test 6 | def setup 7 | Defaults::ENVIRONMENT[:bit_length] = 160 8 | Defaults::ENVIRONMENT[:k] = 2 9 | end 10 | 11 | def test_xor_distance 12 | assert_equal(1, Binary.xor_distance('0', '1')) 13 | assert_equal(2, Binary.xor_distance('0', '2')) 14 | assert_equal(3, Binary.xor_distance('1', '2')) 15 | assert_equal(8, Binary.xor_distance('0', '8')) 16 | assert_equal(9, Binary.xor_distance('1', '8')) 17 | assert_equal(10, Binary.xor_distance('2', '8')) 18 | end 19 | 20 | def test_shared_prefix_bit_length 21 | no_shared_id = ((2**Defaults::ENVIRONMENT[:bit_length]) - 1).to_s 22 | 23 | assert_equal(159, Binary.shared_prefix_bit_length('0', '1')) 24 | assert_equal(160, Binary.shared_prefix_bit_length('1', '1')) 25 | assert_equal(158, Binary.shared_prefix_bit_length('1', '2')) 26 | assert_equal(159, Binary.shared_prefix_bit_length('2', '3')) 27 | assert_equal(157, Binary.shared_prefix_bit_length('3', '4')) 28 | assert_equal(159, Binary.shared_prefix_bit_length('4', '5')) 29 | assert_equal(158, Binary.shared_prefix_bit_length('7', '5')) 30 | assert_equal(156, Binary.shared_prefix_bit_length('7', '8')) 31 | assert_equal(0, Binary.shared_prefix_bit_length(no_shared_id, '0')) 32 | end 33 | 34 | def test_sha 35 | assert_equal('aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434d', Binary.sha('hello')) 36 | assert_equal('356a192b7913b04c54574d18c28d46e6395428ab', Binary.sha('1')) 37 | assert_equal(40, Binary.sha(rand(333).to_s).size) 38 | assert_equal(Binary.sha('should be the same'), Binary.sha('should be the same')) 39 | end 40 | 41 | def test_select_closest_xor 42 | array = ['1', '2', '3', '4', '5'].map { |i| Contact.new(id: i) } 43 | assert_equal(Binary.select_closest_xor('0', array).id, '1') 44 | assert_equal(Binary.select_closest_xor('5', array).id, '5') 45 | assert_equal(Binary.select_closest_xor('15', array).id, '5') 46 | end 47 | 48 | def test_sort_by_xor! 49 | array = ['1', '2', '3', '4', '5'] 50 | shuffled = array.shuffle.map { |i| Contact.new(id: i) } 51 | result = Binary.sort_by_xor!('0', shuffled).map(&:id) 52 | assert_equal(array, result) 53 | end 54 | end 55 | -------------------------------------------------------------------------------- /spec.txt: -------------------------------------------------------------------------------- 1 | Node 2 | - @ip 3 | - @id 4 | - .. 5 | - RoutingTable 6 | - HashTable 7 | 8 | - join_network 9 | - create an id if there isn't one 10 | - get TBDNetwork::SUPER_NODES (???) 11 | - pick one of the super nodes as the bootstrap node 12 | - ping the bootstrap node 13 | - RPC "iterative_find_node" on the bootstrap node, passing its own contact 14 | 15 | - iterative_find_node 16 | - call "find_node" on itself 17 | - returns k contacts 18 | - find the closest of the k contacts to the id 19 | - if the closest is itself? 20 | - return itself 21 | - else 22 | - ping the closest node 23 | - RPC "find_node" on that closest node 24 | - iterative_find_value 25 | - input: resource_hash 26 | - call its own "find_value" 27 | - returns: k contacts 28 | - find the closest of the k contacts to the value 29 | - if the closest itself? 30 | - return itself 31 | - else 32 | - ping that node 33 | - RPC "find_value" on that node 34 | - iterative_store 35 | - compute the hash of the file content(or passed in) 36 | - call find_node with the hash above 37 | - return k contacts 38 | - find the closest of the k contacts to the value 39 | - if the closest itself? 40 | - call "store" on self, the originating node as the contact 41 | - else 42 | - ping that node 43 | - RPC "store" on that node, the originating node as the contact, passing along the content hash 44 | - store 45 | - receives a contact (the node that originated "iterative_store"), an hash (hash of the file content), the data (a URL to get it) 46 | - insert k/v into the the current node's HashTable 47 | - find_value 48 | - takes a hash 49 | - same as "find_node" 50 | - find_node 51 | - takes a hash 52 | - return k contacts that's closest to the hash 53 | - ping 54 | - takes a contact 55 | - returns a boolean (alive or not) 56 | - if true 57 | update / insert the contact into its routing table 58 | - else 59 | evict the contact from its routing table 60 | - receive_ping 61 | - takes a contact 62 | - upsert the contact into its routing table 63 | 64 | - replication 65 | - error paths (nodes down) 66 | - allow setting alpha 67 | - ping can be a random true/false (90% true / 10% false) 68 | 69 | RoutingTable 70 | - node_id 71 | - buckets 72 | - bucket: 73 | - [contacts] 74 | 75 | 76 | contact -> node 77 | -------------------------------------------------------------------------------- /bin/launch_local_nodes.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ## launches supernode on localhost, and range of nodes on localhost.. 4 | ## no nat translation or communications outside of localhost are supported 5 | ## 6 | ## takes 3 arguments 7 | ## 1st argument: port of new supernode to be launched 8 | ## 2nd argument: port of node at start of range 9 | ## 3rd argument: port of node at end of range 10 | ## 11 | ## end result will be a fleet of nodes running on localhost, 1 running on each port within specified range 12 | ## each node will be communicating with the newly launched supernode 13 | 14 | help_message() { 15 | echo 16 | echo 'looks like you need some help using this tool.' 17 | echo 18 | echo 'usage: "launch_nodes.sh super_node_port_number starting_port_number ending_port_number"' 19 | echo "Script takes 3 arguments" 20 | echo 21 | echo "The first argument is the port of your super_node, it will be launched first" 22 | echo 23 | echo "The second and third arguments are starting and ending port numbers" 24 | echo "The starting port must be lower than the ending port, and have no overlap with the supernode port" 25 | echo 26 | echo "Client node will be launched on each node within that range inclusive" 27 | echo "Each client will begin communicating with the supernode passed in as the first argument" 28 | echo 29 | echo "The processes will be backgrounded, and the PID written to pids.txt" 30 | echo "You can quit the processes in bulk using kill_nodes.sh, which iterates through pids.txt," 31 | echo "kills each process, then overwrites the file" 32 | echo 33 | echo "Alternatively you can locate each pid using" 34 | echo "lsof -i \$port_number" 35 | echo "and kill it manually:" 36 | echo "kill \$pid" 37 | echo "however this could result in inconsistencies with your pids.txt file." 38 | echo 39 | } 40 | 41 | launch_nodes() { 42 | launch_super $1 43 | sleep 1 44 | SUPERPORT=$1 45 | for port in $(seq $2 $3) 46 | do 47 | launch_node $port 48 | done 49 | } 50 | 51 | launch_super() { 52 | PORT=$1 SUPERPORT='' SUPER=true nohup ruby app.rb >> tmp/nohup.out & 53 | echo $! >> tmp/pids.txt 54 | } 55 | 56 | launch_node() { 57 | PORT=$1 SUPERPORT=$SUPERPORT SUPER=false nohup ruby app.rb >> tmp/nohup.out & 58 | echo $! >> tmp/pids.txt 59 | } 60 | 61 | if [[ $# -ne 3 ]] || [[ $1 == '-h' ]] || [[ $2 -gt $3 ]]; then 62 | help_message 63 | exit 0 64 | fi 65 | 66 | launch_nodes $@ -------------------------------------------------------------------------------- /views/rpc.erb: -------------------------------------------------------------------------------- 1 |
2 |

Iterative find node

3 |
4 | 5 | 6 |
7 | 8 |
9 | 10 |
11 |

Iterative find value

12 |
13 | 14 | 15 |
16 | 17 |
18 | 19 |
20 |

Iterative store

21 |
22 | 23 | 24 | 25 | 26 |
27 | 28 |
29 | 30 |
31 |

Send RPC Store

32 |
33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 |
44 | 45 |
46 | 47 |
48 |

Send Ping

49 |
50 | 51 | 52 | 53 | 54 | 55 | 56 |
57 | 58 |
-------------------------------------------------------------------------------- /views/layout.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | <% if @refresh %> 6 | <%= @refresh %> 7 | <% end %> 8 | Xorro P2P | <%= @title %> 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 |

19 | 20 |

21 |
22 |

id: <%= @node.id %>

23 |

ipv4: <%= @node.ip %>

24 |

port: <%= @node.port %>

25 |
26 |
27 |
28 |
29 | 62 |
63 |
64 |
65 |

<%= @title %>

66 |
67 | <% flash.keys.each do |type| %> 68 |
69 | <%= flash[type] %> 70 | × 71 |
72 | <% end %> 73 | <%= yield %> 74 |
75 |
76 | 77 | 78 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /lib/network_adapter.rb: -------------------------------------------------------------------------------- 1 | require 'http' 2 | require_relative 'contact.rb' 3 | 4 | class NetworkAdapter 5 | attr_reader :nodes 6 | include Singleton 7 | 8 | def initialize 9 | @nodes = [] 10 | end 11 | 12 | def store(file_id, address, recipient_contact, sender_contact) 13 | info_hash = hashify(sender_contact, file_id: file_id, address: address) 14 | begin 15 | response = call_rpc(recipient_contact, 'store', info_hash) 16 | rescue 17 | response = false 18 | end 19 | response 20 | end 21 | 22 | def find_node(query_id, recipient_contact, sender_contact) 23 | info_hash = hashify(sender_contact, node_id: query_id) 24 | begin 25 | response = call_rpc(recipient_contact, 'find_node', info_hash) 26 | closest_nodes = JSON.parse(response) 27 | rescue 28 | closest_nodes = [] 29 | end 30 | contactify!(closest_nodes) 31 | end 32 | 33 | def find_value(file_id, recipient_contact, sender_contact) 34 | info_hash = hashify(sender_contact, file_id: file_id) 35 | begin 36 | response = call_rpc(recipient_contact, 'find_value', info_hash) 37 | result = JSON.parse(response) 38 | rescue 39 | result = {} 40 | end 41 | if result['contacts'] 42 | contactify!(result['contacts']) 43 | end 44 | result 45 | end 46 | 47 | def ping(recipient_contact, sender_contact) 48 | info_hash = hashify(sender_contact) 49 | begin 50 | response = call_rpc(recipient_contact, 'ping', info_hash) 51 | rescue 52 | return false 53 | end 54 | response.code == 200 55 | end 56 | 57 | def info(url, port) 58 | begin 59 | response = call_get_info(url, port) 60 | rescue 61 | response = '{}' 62 | end 63 | response 64 | end 65 | 66 | def get(url) 67 | begin 68 | response = HTTP.get(url) 69 | rescue 70 | response = false 71 | end 72 | response 73 | end 74 | 75 | def check_resource_status(url) 76 | begin 77 | response = HTTP.head(url).code 78 | rescue 79 | response = false 80 | end 81 | response 82 | end 83 | 84 | public_class_method :allocate 85 | 86 | private 87 | 88 | def contactify!(array) 89 | array.map! do |contact| 90 | Contact.new(id: contact['id'], ip: contact['ip'], port: contact['port'].to_i) 91 | end 92 | end 93 | 94 | def hashify(sender, options = {}) 95 | { id: sender.id, 96 | ip: sender.ip, 97 | port: sender.port }.merge(options) 98 | end 99 | 100 | def call_rpc(recipient_contact, path, info_hash) 101 | url = recipient_contact.ip 102 | port = recipient_contact.port 103 | HTTP.post('http://' + url + ':' + port.to_s + "/rpc/#{path}", form: info_hash) 104 | end 105 | 106 | def call_get_info(url, port) 107 | HTTP.get('http://' + url + ':' + port.to_s + '/rpc/info') 108 | end 109 | end 110 | -------------------------------------------------------------------------------- /lib/routing_table.rb: -------------------------------------------------------------------------------- 1 | require_relative 'defaults.rb' 2 | require_relative 'binary.rb' 3 | require_relative 'kbucket.rb' 4 | 5 | class RoutingTable 6 | attr_reader :node, :node_id, :buckets 7 | 8 | def initialize(current_node) 9 | @node = current_node 10 | @node_id = current_node.id 11 | @buckets = [KBucket.new(@node)] 12 | end 13 | 14 | def insert(contact) 15 | return if contact.id == @node_id 16 | 17 | bucket = find_closest_bucket(contact.id) 18 | duplicate_contact = bucket.find_contact_by_id(contact.id) 19 | 20 | if duplicate_contact 21 | duplicate_contact.update_last_seen 22 | @node.sync 23 | return 24 | end 25 | 26 | if bucket.full? 27 | process_full_bucket(bucket, contact) 28 | else 29 | bucket.add(contact) 30 | end 31 | end 32 | 33 | def find_closest_bucket(id) 34 | idx = Binary.shared_prefix_bit_length(@node_id, id) 35 | buckets[idx] || buckets.last 36 | end 37 | 38 | def find_closest_contacts(id, sender_contact = nil, quantity = Defaults::ENVIRONMENT[:k]) 39 | closest_bucket = find_closest_bucket(id) 40 | results = [] 41 | 42 | bucket_idx = @buckets.index(closest_bucket) 43 | further_bucket_idx = bucket_idx - 1 44 | 45 | fill_closest_contacts(results, bucket_idx, sender_contact, true, quantity) 46 | fill_closest_contacts(results, further_bucket_idx, sender_contact, false, quantity) 47 | 48 | results 49 | end 50 | 51 | def create_bucket 52 | buckets.push KBucket.new(@node) 53 | end 54 | 55 | # redistribute contacts between buckets.last and a newly created bucket 56 | def redistribute_contacts 57 | old_idx = buckets.size - 2 58 | old_bucket = buckets[old_idx] 59 | new_bucket = buckets.last 60 | 61 | movers = old_bucket.contacts.select do |c| 62 | Binary.shared_prefix_bit_length(@node_id, c.id) > old_idx 63 | end 64 | 65 | movers.each do |m| 66 | old_bucket.delete(m) 67 | new_bucket.contacts.push(m) 68 | end 69 | end 70 | 71 | private 72 | 73 | def process_full_bucket(bucket, contact) 74 | if bucket_splittable?(bucket, contact) 75 | split(bucket) 76 | insert(contact) 77 | else 78 | bucket.attempt_eviction(contact) 79 | end 80 | end 81 | 82 | def fill_closest_contacts(results, start_idx, sender_contact, to_right_side, quantity) 83 | mover = to_right_side ? 1 : -1 84 | 85 | until results.size == quantity || start_idx == buckets.size || start_idx < 0 86 | current_bucket = @buckets[start_idx] 87 | 88 | current_bucket.each do |contact| 89 | ingest_contact(results, contact, quantity, sender_contact) 90 | end 91 | 92 | start_idx += mover 93 | end 94 | end 95 | 96 | def ingest_contact(results, contact, quantity, sender) 97 | if sender 98 | results.push(contact) if results.size < quantity && contact.id != sender.id 99 | elsif results.size < quantity 100 | results.push(contact) 101 | end 102 | end 103 | 104 | def bucket_splittable?(bucket, contact) 105 | bucket.hasnt_been_split? && 106 | room_for_another_bucket? && 107 | (node_is_closer(contact.id, bucket) || bucket.redistributable?(@node_id, @buckets.index(bucket))) 108 | end 109 | 110 | def node_is_closer(contact_id, bucket) 111 | Binary.shared_prefix_bit_length(@node_id, contact_id) > @buckets.index(bucket) 112 | end 113 | 114 | def room_for_another_bucket? 115 | buckets.size < Defaults::ENVIRONMENT[:bit_length] 116 | end 117 | 118 | def split(bucket) 119 | bucket.make_unsplittable 120 | create_bucket 121 | redistribute_contacts 122 | @node.sync 123 | end 124 | end 125 | -------------------------------------------------------------------------------- /test/kbucket_4bit_test.rb: -------------------------------------------------------------------------------- 1 | require_relative 'test_helper.rb' 2 | require_relative '../lib/node.rb' 3 | require_relative "../lib/routing_table.rb" 4 | require_relative "../lib/kbucket.rb" 5 | require_relative "../lib/contact.rb" 6 | require_relative "../lib/fake_network_adapter.rb" 7 | 8 | class KBucketTest < Minitest::Test 9 | def setup 10 | Defaults::ENVIRONMENT[:bit_length] = 4 11 | Defaults::ENVIRONMENT[:k] = 2 12 | @kn = FakeNetworkAdapter.new 13 | @node = Node.new('0', @kn) 14 | @bucket = KBucket.new(@node) 15 | @contact = @node.to_contact 16 | end 17 | 18 | def test_create_bucket 19 | assert_instance_of(KBucket, @bucket) 20 | assert_equal([], @bucket.contacts) 21 | assert_equal(true, @bucket.splittable) 22 | end 23 | 24 | def test_add_contact 25 | @bucket.add(@contact) 26 | 27 | assert_equal(1, @bucket.contacts.size) 28 | end 29 | 30 | def test_delete_contact 31 | @bucket.add(@contact) 32 | @bucket.delete(@bucket.contacts[0]) 33 | 34 | assert_equal(0, @bucket.contacts.size) 35 | end 36 | 37 | def test_delete_contact_that_is_not_included 38 | @bucket.add(@contact) 39 | contact = Node.new('1', @kn).to_contact 40 | @bucket.delete(contact) 41 | 42 | assert_equal(1, @bucket.contacts.size) 43 | end 44 | 45 | def test_head_tail_one_contact 46 | @bucket.add(@contact) 47 | 48 | assert_equal(@bucket.contacts[0], @bucket.head) 49 | assert_equal(@bucket.contacts[0], @bucket.tail) 50 | end 51 | 52 | def test_head_tail_two_contacts 53 | @bucket.add(@contact) 54 | @bucket.add(id: '1', ip: '') 55 | 56 | assert_equal(@bucket.contacts[0], @bucket.head) 57 | assert_equal(@bucket.contacts[1], @bucket.tail) 58 | end 59 | 60 | def test_bucket_is_full 61 | @bucket.add(@contact) 62 | @bucket.add(id: '1', ip: '') 63 | 64 | assert(@bucket.full?) 65 | end 66 | 67 | def test_bucket_is_not_full 68 | @bucket.add(@contact) 69 | 70 | refute(@bucket.full?) 71 | end 72 | 73 | def test_find_contact_by_id 74 | @bucket.add(@contact) 75 | found_contact = @bucket.find_contact_by_id(@node.id) 76 | 77 | assert_equal(@bucket.contacts[0], found_contact) 78 | end 79 | 80 | def test_find_contact_by_id_no_match 81 | @bucket.add(@contact) 82 | found_contact = @bucket.find_contact_by_id('1') 83 | 84 | assert_nil(found_contact) 85 | end 86 | 87 | def test_make_unsplittable 88 | @bucket.make_unsplittable 89 | 90 | refute(@bucket.splittable) 91 | end 92 | 93 | def test_is_redistributable 94 | @bucket.add(Node.new('15', @kn).to_contact) 95 | @bucket.add(Node.new('7', @kn).to_contact) 96 | 97 | result = @bucket.redistributable?('0', 0) 98 | assert(result) 99 | end 100 | 101 | def test_is_not_redistributable 102 | @bucket.add(Node.new('15', @kn).to_contact) 103 | @bucket.add(Node.new('14', @kn).to_contact) 104 | 105 | result = @bucket.redistributable?('0', 0) 106 | refute(result) 107 | end 108 | 109 | def test_sort_by_seen 110 | @bucket.add(@contact) 111 | @bucket.add(Node.new('7', @kn).to_contact) 112 | 113 | @bucket.head.update_last_seen 114 | @bucket.sort_by_seen 115 | 116 | assert_equal('7', @bucket.head.id) 117 | end 118 | 119 | def test_attempt_eviction_pingable 120 | @bucket.add(Node.new('15', @kn).to_contact) 121 | @bucket.add(Node.new('14', @kn).to_contact) 122 | 123 | @bucket.attempt_eviction(Contact.new(id: '13', ip: '')) 124 | 125 | assert_equal('15', @bucket.tail.id) 126 | assert_equal('14', @bucket.head.id) 127 | end 128 | 129 | def test_attempt_eviction_not_pingable 130 | @bucket.add(Node.new('15', @kn).to_contact) 131 | @bucket.add(Node.new('14', @kn).to_contact) 132 | 133 | @kn.nodes.delete_at(1) 134 | @bucket.attempt_eviction(Node.new('13', @kn).to_contact) 135 | 136 | assert_equal('13', @bucket.tail.id) 137 | assert_equal('14', @bucket.head.id) 138 | end 139 | end 140 | -------------------------------------------------------------------------------- /test/kbucket_6bit_test.rb: -------------------------------------------------------------------------------- 1 | require_relative 'test_helper.rb' 2 | require_relative '../lib/node.rb' 3 | require_relative "../lib/routing_table.rb" 4 | require_relative "../lib/kbucket.rb" 5 | require_relative "../lib/contact.rb" 6 | require_relative "../lib/fake_network_adapter.rb" 7 | 8 | class KBucketTest6 < Minitest::Test 9 | def setup 10 | Defaults::ENVIRONMENT[:bit_length] = 6 11 | Defaults::ENVIRONMENT[:k] = 2 12 | @kn = FakeNetworkAdapter.new 13 | @node = Node.new('0', @kn) 14 | @bucket = KBucket.new(@node) 15 | @contact = @node.to_contact 16 | end 17 | 18 | def test_create_bucket 19 | assert_instance_of(KBucket, @bucket) 20 | assert_equal([], @bucket.contacts) 21 | assert_equal(true, @bucket.splittable) 22 | end 23 | 24 | def test_add_contact 25 | @bucket.add(@contact) 26 | 27 | assert_equal(1, @bucket.contacts.size) 28 | end 29 | 30 | def test_delete_contact 31 | @bucket.add(@contact) 32 | @bucket.delete(@bucket.contacts[0]) 33 | 34 | assert_equal(0, @bucket.contacts.size) 35 | end 36 | 37 | def test_delete_contact_that_is_not_included 38 | @bucket.add(@contact) 39 | contact = Node.new('1', @kn).to_contact 40 | @bucket.delete(contact) 41 | 42 | assert_equal(1, @bucket.contacts.size) 43 | end 44 | 45 | def test_head_tail_one_contact 46 | @bucket.add(@contact) 47 | 48 | assert_equal(@bucket.contacts[0], @bucket.head) 49 | assert_equal(@bucket.contacts[0], @bucket.tail) 50 | end 51 | 52 | def test_head_tail_two_contacts 53 | @bucket.add(@contact) 54 | @bucket.add(id: '1', ip: '') 55 | 56 | assert_equal(@bucket.contacts[0], @bucket.head) 57 | assert_equal(@bucket.contacts[1], @bucket.tail) 58 | end 59 | 60 | def test_bucket_is_full 61 | @bucket.add(@contact) 62 | @bucket.add(id: '1', ip: '') 63 | 64 | assert(@bucket.full?) 65 | end 66 | 67 | def test_bucket_is_not_full 68 | @bucket.add(@contact) 69 | 70 | refute(@bucket.full?) 71 | end 72 | 73 | def test_find_contact_by_id 74 | @bucket.add(@contact) 75 | found_contact = @bucket.find_contact_by_id(@node.id) 76 | 77 | assert_equal(@bucket.contacts[0], found_contact) 78 | end 79 | 80 | def test_find_contact_by_id_no_match 81 | @bucket.add(@contact) 82 | found_contact = @bucket.find_contact_by_id('1') 83 | 84 | assert_nil(found_contact) 85 | end 86 | 87 | def test_make_unsplittable 88 | @bucket.make_unsplittable 89 | 90 | refute(@bucket.splittable) 91 | end 92 | 93 | def test_is_redistributable 94 | @bucket.add(Node.new('15', @kn).to_contact) 95 | @bucket.add(Node.new('7', @kn).to_contact) 96 | 97 | result = @bucket.redistributable?('0', 0) 98 | assert(result) 99 | end 100 | 101 | def test_is_not_redistributable 102 | @bucket.add(Node.new('63', @kn).to_contact) 103 | @bucket.add(Node.new('62', @kn).to_contact) 104 | 105 | result = @bucket.redistributable?('0', 0) 106 | refute(result) 107 | end 108 | 109 | def test_sort_by_seen 110 | @bucket.add(@contact) 111 | @bucket.add(Node.new('7', @kn).to_contact) 112 | 113 | @bucket.head.update_last_seen 114 | @bucket.sort_by_seen 115 | 116 | assert_equal('7', @bucket.head.id) 117 | end 118 | 119 | def test_attempt_eviction_pingable 120 | @bucket.add(Node.new('15', @kn).to_contact) 121 | @bucket.add(Node.new('14', @kn).to_contact) 122 | 123 | @bucket.attempt_eviction(Contact.new(id: '13', ip: '')) 124 | 125 | assert_equal('15', @bucket.tail.id) 126 | assert_equal('14', @bucket.head.id) 127 | end 128 | 129 | def test_attempt_eviction_not_pingable 130 | @bucket.add(Node.new('15', @kn).to_contact) 131 | @bucket.add(Node.new('14', @kn).to_contact) 132 | 133 | @kn.nodes.delete_at(1) 134 | @bucket.attempt_eviction(Node.new('13', @kn).to_contact) 135 | 136 | assert_equal('13', @bucket.tail.id) 137 | assert_equal('14', @bucket.head.id) 138 | end 139 | end 140 | -------------------------------------------------------------------------------- /test/kbucket_160bit_test.rb: -------------------------------------------------------------------------------- 1 | require_relative 'test_helper.rb' 2 | require_relative '../lib/node.rb' 3 | require_relative "../lib/routing_table.rb" 4 | require_relative "../lib/kbucket.rb" 5 | require_relative "../lib/contact.rb" 6 | require_relative "../lib/fake_network_adapter.rb" 7 | 8 | class KBucketTest160 < Minitest::Test 9 | def setup 10 | Defaults::ENVIRONMENT[:bit_length] = 160 11 | Defaults::ENVIRONMENT[:k] = 2 12 | @kn = FakeNetworkAdapter.new 13 | @node = Node.new('0', @kn) 14 | @bucket = KBucket.new(@node) 15 | @contact = @node.to_contact 16 | @largest = 2**Defaults::ENVIRONMENT[:bit_length] 17 | end 18 | 19 | def test_create_bucket 20 | assert_instance_of(KBucket, @bucket) 21 | assert_equal([], @bucket.contacts) 22 | assert_equal(true, @bucket.splittable) 23 | end 24 | 25 | def test_add_contact 26 | @bucket.add(@contact) 27 | 28 | assert_equal(1, @bucket.contacts.size) 29 | end 30 | 31 | def test_delete_contact 32 | @bucket.add(@contact) 33 | @bucket.delete(@bucket.contacts[0]) 34 | 35 | assert_equal(0, @bucket.contacts.size) 36 | end 37 | 38 | def test_delete_contact_that_is_not_included 39 | @bucket.add(@contact) 40 | contact = Node.new('1', @kn).to_contact 41 | @bucket.delete(contact) 42 | 43 | assert_equal(1, @bucket.contacts.size) 44 | end 45 | 46 | def test_head_tail_one_contact 47 | @bucket.add(@contact) 48 | 49 | assert_equal(@bucket.contacts[0], @bucket.head) 50 | assert_equal(@bucket.contacts[0], @bucket.tail) 51 | end 52 | 53 | def test_head_tail_two_contacts 54 | @bucket.add(@contact) 55 | @bucket.add(id: '1', ip: '') 56 | 57 | assert_equal(@bucket.contacts[0], @bucket.head) 58 | assert_equal(@bucket.contacts[1], @bucket.tail) 59 | end 60 | 61 | def test_bucket_is_full 62 | @bucket.add(@contact) 63 | @bucket.add(id: '1', ip: '') 64 | 65 | assert(@bucket.full?) 66 | end 67 | 68 | def test_bucket_is_not_full 69 | @bucket.add(@contact) 70 | 71 | refute(@bucket.full?) 72 | end 73 | 74 | def test_find_contact_by_id 75 | @bucket.add(@contact) 76 | found_contact = @bucket.find_contact_by_id(@node.id) 77 | 78 | assert_equal(@bucket.contacts[0], found_contact) 79 | end 80 | 81 | def test_find_contact_by_id_no_match 82 | @bucket.add(@contact) 83 | found_contact = @bucket.find_contact_by_id('1') 84 | 85 | assert_nil(found_contact) 86 | end 87 | 88 | def test_make_unsplittable 89 | @bucket.make_unsplittable 90 | 91 | refute(@bucket.splittable) 92 | end 93 | 94 | def test_is_redistributable 95 | @bucket.add(Node.new('15', @kn).to_contact) 96 | @bucket.add(Node.new('7', @kn).to_contact) 97 | 98 | result = @bucket.redistributable?('0', 0) 99 | assert(result) 100 | end 101 | 102 | def test_is_not_redistributable 103 | no_shared_id = (@largest - 1).to_s 104 | no_shared_id2 = (@largest - 2).to_s 105 | 106 | @bucket.add(Node.new(no_shared_id, @kn).to_contact) 107 | @bucket.add(Node.new(no_shared_id2, @kn).to_contact) 108 | 109 | result = @bucket.redistributable?('0', 0) 110 | refute(result) 111 | end 112 | 113 | def test_sort_by_seen 114 | @bucket.add(@contact) 115 | @bucket.add(Node.new('7', @kn).to_contact) 116 | 117 | @bucket.head.update_last_seen 118 | @bucket.sort_by_seen 119 | 120 | assert_equal('7', @bucket.head.id) 121 | end 122 | 123 | def test_attempt_eviction_pingable 124 | @bucket.add(Node.new('15', @kn).to_contact) 125 | @bucket.add(Node.new('14', @kn).to_contact) 126 | 127 | @bucket.attempt_eviction(Contact.new(id: '13', ip: '')) 128 | 129 | assert_equal('15', @bucket.tail.id) 130 | assert_equal('14', @bucket.head.id) 131 | end 132 | 133 | def test_attempt_eviction_not_pingable 134 | @bucket.add(Node.new('15', @kn).to_contact) 135 | @bucket.add(Node.new('14', @kn).to_contact) 136 | 137 | @kn.nodes.delete_at(1) 138 | @bucket.attempt_eviction(Node.new('13', @kn).to_contact) 139 | 140 | assert_equal('13', @bucket.tail.id) 141 | assert_equal('14', @bucket.head.id) 142 | end 143 | end 144 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Xorro P2P 2 | 3 | Xorro P2P is a Ruby implementation of a Bit-Torrent-Like Peer to Peer file transfer system. Xorro is experimental, proof of concept software, and not intended as a replacement for existing P2P solutions. 4 | 5 | 6 | ## Features 7 | 8 | * A Full implementation of a Kademlia Distributed Hash Table. 9 | 10 | * File sharing by drag/drop into browser - shards and manifest are autogenerated and broadcasted onto network. 11 | 12 | * Files are retreived by requesting the file ID -- manifest file is automatically downloaded, shards are retreived and reassembled. 13 | 14 | * Contents of local routing table and DHT segment can be inspected via web UI. 15 | 16 | 17 | 18 | ## Getting Started 19 | 20 | ### Dependencies 21 | 22 | ##### Ruby 2.3.1 23 | 24 | ##### Networking 25 | While anyone can download Xorro, launch a node, and retreive files from the Xorro network, contributing to the network (hosting files available to others) requires one of the following: 26 | 27 | 1. Node is directly on the public internet with a public IP, no NAT or Firewall blocking public access. 28 | 2. Node is behind a NAT/Firewall but port forwarding is set up so that the node's port is publicly accessible. 29 | 3. Node is behind a NAT/Firewall with no port forwarding, but a 3rd party tunneling tool is installed. 30 | 31 | Ngrok is a tool that allows you to expose a local webserver behind a NAT firewall to the internet via secure tunnel. Xorro supports Ngrok to enable NAT translated communications. 32 | 33 | Download Ngrok, unzip the executable, and copy it to somewhere in your `$PATH` ie: `/usr/local/bin/ngrok` 34 | 35 | [Get ngrok here.](https://ngrok.com/download) 36 | 37 | Once ngrok is installed, you can optionally create a free account at [ngrok.com](https://dashboard.ngrok.com/get-started) and install your authtoken. The free account raises Ngrok's request/minute limit, allowing you to contribute to the network. 38 | 39 | ### Installation 40 | 1. Download the repository: `git clone https://github.com/xorro-p2p/xorro.git` 41 | 2. `cd` into the `xorro` directory and run `bundle install`. 42 | 43 | ### Configuration 44 | 1. Change your username and password in `config.yml` 45 | 2. Upon first launch, Xorro will create the `xorro_homes` folder at the location specified in `config.yml`. The default is ~/Desktop/xorro_homes. Feel free to change this. 46 | 47 | 48 | ## Usage 49 | ### Launching the Application 50 | 51 | Xorro ships with a number of launch/kill scripts located in `bin`. 52 | 53 | To launch a Xorro Node from behind a NAT/Firewall and retreive files from the global P2P network, run: 54 | 55 | ``` 56 | bin/launch_leech.sh 57 | ``` 58 | 59 | To launch a public Xorro Node on a host directly on the internet, or behind a firewall with port forwarding run: 60 | 61 | ``` 62 | bin/launch_pf_public.sh 9999 ### or other tcp port number 63 | ``` 64 | 65 | To launch a Xorro Node on a host behind a firewall, using Ngrok for tunneling: 66 | ``` 67 | bin/launch_nat_public.sh 9999 ### or other tcp port number 68 | ``` 69 | 70 | Your admin control panel is now accessible at `http://localhost/9999`. Authenticate with the credentials you set in `config.yml`. 71 | 72 | 73 | ### Adding a file 74 | Add a file to the network by navigating to 'Upload File' in the sidebar, then drag an image, pdf, or mp3 into the browser window. Your file will be sharded in the background, after which the shards and manifest will be broadcast to peer nodes. Your file is visible on the 'My Files' page, and the shards/manifests are visible on the Debug/Data page. 75 | 76 | 77 | ### Retreiving a file 78 | To retreive a file from the network, navigate to the 'Get File' page and enter the ID of the file you would like. 79 | 80 | Don't know the ID of any files on the Xorro network? Try this one: 81 | `1376592690108572977913416942036588071274772272329` 82 | 83 | After successful retreival, the file will appear on the 'My Files' page and can be viewed or downloaded 84 | 85 | ### Shutting Down 86 | 87 | To shut down your Xorro node -- run `bin/kill_nodes.sh`. 88 | 89 | 90 | ### Other scripts and environment variables 91 | 92 | Development of Xorro required the flexibility to rapidly launch fleets of nodes in a local environment, each with specific environment variables set at launch. The rest of the scripts beneath `/bin` are written to standardize and streamline this process. 93 | 94 | Some relevant environment variables for node launch: 95 | 96 | 1. `PORT`: http port that Sinatra Web UI will run on. 97 | 2. `SUPERIP`: IPV4 address or fully qualified domain name of supernode to be contacted at boot. 98 | 3. `SUPERPORT`: TCP Port used to contact supernode at boot. 99 | 4. `SUPER`: true or false -- is the node a supernode? 100 | 5. `NAT`: true or false -- is this node behind NAT firewall? 101 | 6. `FQDN`: Fully Qualified Domain Name or public IP at which the node is reachable. 102 | 7. `LEECH`: true or false -- this disables the broadcast of location info about any hosted files. 103 | 104 | -------------------------------------------------------------------------------- /test/kbucket_6bit_8k_test.rb: -------------------------------------------------------------------------------- 1 | require_relative 'test_helper.rb' 2 | require_relative '../lib/node.rb' 3 | require_relative "../lib/routing_table.rb" 4 | require_relative "../lib/kbucket.rb" 5 | require_relative "../lib/contact.rb" 6 | require_relative "../lib/fake_network_adapter.rb" 7 | 8 | class KBucketTest6bit8k < Minitest::Test 9 | def setup 10 | Defaults::ENVIRONMENT[:bit_length] = 6 11 | Defaults::ENVIRONMENT[:k] = 8 12 | @kn = FakeNetworkAdapter.new 13 | @node = Node.new('0', @kn) 14 | @bucket = KBucket.new(@node) 15 | @contact = @node.to_contact 16 | end 17 | 18 | def test_create_bucket 19 | assert_instance_of(KBucket, @bucket) 20 | assert_equal([], @bucket.contacts) 21 | assert_equal(true, @bucket.splittable) 22 | end 23 | 24 | def test_add_contact 25 | @bucket.add(@contact) 26 | 27 | assert_equal(1, @bucket.contacts.size) 28 | end 29 | 30 | def test_delete_contact 31 | @bucket.add(@contact) 32 | @bucket.delete(@bucket.contacts[0]) 33 | 34 | assert_equal(0, @bucket.contacts.size) 35 | end 36 | 37 | def test_delete_contact_that_is_not_included 38 | @bucket.add(@contact) 39 | contact = Node.new('1', @kn).to_contact 40 | @bucket.delete(contact) 41 | 42 | assert_equal(1, @bucket.contacts.size) 43 | end 44 | 45 | def test_head_tail_one_contact 46 | @bucket.add(@contact) 47 | 48 | assert_equal(@bucket.contacts[0], @bucket.head) 49 | assert_equal(@bucket.contacts[0], @bucket.tail) 50 | end 51 | 52 | def test_head_tail_two_contacts 53 | @bucket.add(@contact) 54 | @bucket.add(id: '1', ip: '') 55 | 56 | assert_equal(@bucket.contacts[0], @bucket.head) 57 | assert_equal(@bucket.contacts[1], @bucket.tail) 58 | end 59 | 60 | def test_bucket_is_full 61 | @bucket.add(@contact) 62 | @bucket.add(id: '1', ip: '') 63 | @bucket.add(id: '2', ip: '') 64 | @bucket.add(id: '3', ip: '') 65 | @bucket.add(id: '4', ip: '') 66 | @bucket.add(id: '5', ip: '') 67 | @bucket.add(id: '6', ip: '') 68 | @bucket.add(id: '7', ip: '') 69 | 70 | assert(@bucket.full?) 71 | end 72 | 73 | def test_bucket_is_not_full 74 | @bucket.add(@contact) 75 | 76 | refute(@bucket.full?) 77 | end 78 | 79 | def test_find_contact_by_id 80 | @bucket.add(@contact) 81 | found_contact = @bucket.find_contact_by_id(@node.id) 82 | 83 | assert_equal(@bucket.contacts[0], found_contact) 84 | end 85 | 86 | def test_find_contact_by_id_no_match 87 | @bucket.add(@contact) 88 | found_contact = @bucket.find_contact_by_id('1') 89 | 90 | assert_nil(found_contact) 91 | end 92 | 93 | def test_make_unsplittable 94 | @bucket.make_unsplittable 95 | 96 | refute(@bucket.splittable) 97 | end 98 | 99 | def test_is_redistributable 100 | @bucket.add(Node.new('63', @kn).to_contact) 101 | @bucket.add(Node.new('62', @kn).to_contact) 102 | @bucket.add(Node.new('61', @kn).to_contact) 103 | @bucket.add(Node.new('60', @kn).to_contact) 104 | @bucket.add(Node.new('59', @kn).to_contact) 105 | @bucket.add(Node.new('58', @kn).to_contact) 106 | @bucket.add(Node.new('57', @kn).to_contact) 107 | @bucket.add(Node.new('31', @kn).to_contact) 108 | 109 | result = @bucket.redistributable?('0', 0) 110 | assert(result) 111 | end 112 | 113 | def test_is_not_redistributable 114 | @bucket.add(Node.new('63', @kn).to_contact) 115 | @bucket.add(Node.new('62', @kn).to_contact) 116 | @bucket.add(Node.new('61', @kn).to_contact) 117 | @bucket.add(Node.new('60', @kn).to_contact) 118 | @bucket.add(Node.new('59', @kn).to_contact) 119 | @bucket.add(Node.new('58', @kn).to_contact) 120 | @bucket.add(Node.new('57', @kn).to_contact) 121 | @bucket.add(Node.new('56', @kn).to_contact) 122 | 123 | result = @bucket.redistributable?('0', 0) 124 | refute(result) 125 | end 126 | 127 | def test_sort_by_seen 128 | @bucket.add(@contact) 129 | @bucket.add(Node.new('7', @kn).to_contact) 130 | 131 | @bucket.head.update_last_seen 132 | @bucket.sort_by_seen 133 | 134 | assert_equal('7', @bucket.head.id) 135 | end 136 | 137 | def test_attempt_eviction_pingable 138 | @bucket.add(Node.new('63', @kn).to_contact) 139 | @bucket.add(Node.new('62', @kn).to_contact) 140 | @bucket.add(Node.new('61', @kn).to_contact) 141 | @bucket.add(Node.new('60', @kn).to_contact) 142 | @bucket.add(Node.new('59', @kn).to_contact) 143 | @bucket.add(Node.new('58', @kn).to_contact) 144 | @bucket.add(Node.new('57', @kn).to_contact) 145 | @bucket.add(Node.new('56', @kn).to_contact) 146 | 147 | @bucket.attempt_eviction(Contact.new(id: '55', ip: '')) 148 | 149 | assert_equal('63', @bucket.tail.id) 150 | assert_equal('62', @bucket.head.id) 151 | end 152 | 153 | def test_attempt_eviction_not_pingable 154 | @bucket.add(Node.new('63', @kn).to_contact) 155 | @bucket.add(Node.new('62', @kn).to_contact) 156 | @bucket.add(Node.new('61', @kn).to_contact) 157 | @bucket.add(Node.new('60', @kn).to_contact) 158 | @bucket.add(Node.new('59', @kn).to_contact) 159 | @bucket.add(Node.new('58', @kn).to_contact) 160 | @bucket.add(Node.new('57', @kn).to_contact) 161 | @bucket.add(Node.new('56', @kn).to_contact) 162 | 163 | @kn.nodes.delete_at(1) 164 | @bucket.attempt_eviction(Node.new('55', @kn).to_contact) 165 | 166 | assert_equal('55', @bucket.tail.id) 167 | assert_equal('62', @bucket.head.id) 168 | end 169 | end 170 | -------------------------------------------------------------------------------- /test/kbucket_160bit_8k_3a_test.rb: -------------------------------------------------------------------------------- 1 | require_relative 'test_helper.rb' 2 | require_relative '../lib/node.rb' 3 | require_relative "../lib/routing_table.rb" 4 | require_relative "../lib/kbucket.rb" 5 | require_relative "../lib/contact.rb" 6 | require_relative "../lib/fake_network_adapter.rb" 7 | 8 | class KBucketTest160bit8k < Minitest::Test 9 | def setup 10 | Defaults::ENVIRONMENT[:bit_length] = 160 11 | Defaults::ENVIRONMENT[:k] = 8 12 | Defaults::ENVIRONMENT[:alpha] = 3 13 | @kn = FakeNetworkAdapter.new 14 | @node = Node.new('0', @kn) 15 | @bucket = KBucket.new(@node) 16 | @contact = @node.to_contact 17 | @largest = 2**Defaults::ENVIRONMENT[:bit_length] 18 | end 19 | 20 | def test_create_bucket 21 | assert_instance_of(KBucket, @bucket) 22 | assert_equal([], @bucket.contacts) 23 | assert_equal(true, @bucket.splittable) 24 | end 25 | 26 | def test_add_contact 27 | @bucket.add(@contact) 28 | 29 | assert_equal(1, @bucket.contacts.size) 30 | end 31 | 32 | def test_delete_contact 33 | @bucket.add(@contact) 34 | @bucket.delete(@bucket.contacts[0]) 35 | 36 | assert_equal(0, @bucket.contacts.size) 37 | end 38 | 39 | def test_delete_contact_that_is_not_included 40 | @bucket.add(@contact) 41 | contact = Node.new('1', @kn).to_contact 42 | @bucket.delete(contact) 43 | 44 | assert_equal(1, @bucket.contacts.size) 45 | end 46 | 47 | def test_head_tail_one_contact 48 | @bucket.add(@contact) 49 | 50 | assert_equal(@bucket.contacts[0], @bucket.head) 51 | assert_equal(@bucket.contacts[0], @bucket.tail) 52 | end 53 | 54 | def test_head_tail_two_contacts 55 | @bucket.add(@contact) 56 | @bucket.add(id: '1', ip: '') 57 | 58 | assert_equal(@bucket.contacts[0], @bucket.head) 59 | assert_equal(@bucket.contacts[1], @bucket.tail) 60 | end 61 | 62 | def test_bucket_is_full 63 | @bucket.add(@contact) 64 | @bucket.add(id: '1', ip: '') 65 | @bucket.add(id: '2', ip: '') 66 | @bucket.add(id: '3', ip: '') 67 | @bucket.add(id: '4', ip: '') 68 | @bucket.add(id: '5', ip: '') 69 | @bucket.add(id: '6', ip: '') 70 | @bucket.add(id: '7', ip: '') 71 | 72 | assert(@bucket.full?) 73 | end 74 | 75 | def test_bucket_is_not_full 76 | @bucket.add(@contact) 77 | 78 | refute(@bucket.full?) 79 | end 80 | 81 | def test_find_contact_by_id 82 | @bucket.add(@contact) 83 | found_contact = @bucket.find_contact_by_id(@node.id) 84 | 85 | assert_equal(@bucket.contacts[0], found_contact) 86 | end 87 | 88 | def test_find_contact_by_id_no_match 89 | @bucket.add(@contact) 90 | found_contact = @bucket.find_contact_by_id('1') 91 | 92 | assert_nil(found_contact) 93 | end 94 | 95 | def test_make_unsplittable 96 | @bucket.make_unsplittable 97 | 98 | refute(@bucket.splittable) 99 | end 100 | 101 | def test_is_redistributable 102 | no_shared_id = (@largest - 1).to_s 103 | no_shared_id2 = (@largest - 2).to_s 104 | no_shared_id3 = (@largest - 3).to_s 105 | no_shared_id4 = (@largest - 4).to_s 106 | no_shared_id5 = (@largest - 5).to_s 107 | no_shared_id6 = (@largest - 6).to_s 108 | no_shared_id7 = (@largest - 7).to_s 109 | shared_bits_id = '1' 110 | 111 | @bucket.add(Node.new(no_shared_id, @kn).to_contact) 112 | @bucket.add(Node.new(no_shared_id2, @kn).to_contact) 113 | @bucket.add(Node.new(no_shared_id3, @kn).to_contact) 114 | @bucket.add(Node.new(no_shared_id4, @kn).to_contact) 115 | @bucket.add(Node.new(no_shared_id5, @kn).to_contact) 116 | @bucket.add(Node.new(no_shared_id6, @kn).to_contact) 117 | @bucket.add(Node.new(no_shared_id7, @kn).to_contact) 118 | @bucket.add(Node.new(shared_bits_id, @kn).to_contact) 119 | 120 | result = @bucket.redistributable?('0', 0) 121 | assert(result) 122 | end 123 | 124 | def test_is_not_redistributable 125 | no_shared_id = (@largest - 1).to_s 126 | no_shared_id2 = (@largest - 2).to_s 127 | no_shared_id3 = (@largest - 3).to_s 128 | no_shared_id4 = (@largest - 4).to_s 129 | no_shared_id5 = (@largest - 5).to_s 130 | no_shared_id6 = (@largest - 6).to_s 131 | no_shared_id7 = (@largest - 7).to_s 132 | no_shared_id8 = (@largest - 8).to_s 133 | 134 | @bucket.add(Node.new(no_shared_id, @kn).to_contact) 135 | @bucket.add(Node.new(no_shared_id2, @kn).to_contact) 136 | @bucket.add(Node.new(no_shared_id3, @kn).to_contact) 137 | @bucket.add(Node.new(no_shared_id4, @kn).to_contact) 138 | @bucket.add(Node.new(no_shared_id5, @kn).to_contact) 139 | @bucket.add(Node.new(no_shared_id6, @kn).to_contact) 140 | @bucket.add(Node.new(no_shared_id7, @kn).to_contact) 141 | @bucket.add(Node.new(no_shared_id8, @kn).to_contact) 142 | 143 | result = @bucket.redistributable?('0', 0) 144 | refute(result) 145 | end 146 | 147 | def test_sort_by_seen 148 | @bucket.add(@contact) 149 | @bucket.add(Node.new('7', @kn).to_contact) 150 | 151 | @bucket.head.update_last_seen 152 | @bucket.sort_by_seen 153 | 154 | assert_equal('7', @bucket.head.id) 155 | end 156 | 157 | def test_attempt_eviction_pingable 158 | @bucket.add(Node.new('15', @kn).to_contact) 159 | @bucket.add(Node.new('14', @kn).to_contact) 160 | 161 | @bucket.attempt_eviction(Contact.new(id: '13', ip: '')) 162 | 163 | assert_equal('15', @bucket.tail.id) 164 | assert_equal('14', @bucket.head.id) 165 | end 166 | 167 | def test_attempt_eviction_not_pingable 168 | @bucket.add(Node.new('15', @kn).to_contact) 169 | @bucket.add(Node.new('14', @kn).to_contact) 170 | 171 | @kn.nodes.delete_at(1) 172 | @bucket.attempt_eviction(Node.new('13', @kn).to_contact) 173 | 174 | assert_equal('13', @bucket.tail.id) 175 | assert_equal('14', @bucket.head.id) 176 | end 177 | end 178 | -------------------------------------------------------------------------------- /public/css/styles.css: -------------------------------------------------------------------------------- 1 | @import url("whitespace-reset.css"); 2 | 3 | html, body { 4 | height: 100%; 5 | } 6 | 7 | body { 8 | min-width: 1420px; 9 | background: white; 10 | color: #363636; 11 | font-family: 'Helvetica'; 12 | font-size: 14px; 13 | font-weight: 100; 14 | line-height: 1.2em; 15 | letter-spacing: 0.05em; 16 | } 17 | 18 | h2 { 19 | padding: 15px 0 10px 0; 20 | margin: 0 0 20px 0; 21 | font-size: 30px; 22 | font-weight: 300; 23 | color: #246C87; 24 | border-bottom: 1px solid #C7C7C7; 25 | } 26 | 27 | h3 { 28 | margin: 0 0 15px 0; 29 | font-size: 20px; 30 | } 31 | 32 | h4 { 33 | margin: 0 0 15px 0; 34 | font-size: 18px; 35 | } 36 | 37 | dd { 38 | padding: 0 0 0 20px; 39 | margin-left: 0 40 | } 41 | body > header { 42 | height: 80px; 43 | padding: 12px 0 0 20px; 44 | background: #246C87; 45 | } 46 | 47 | body > header h1 { 48 | display: inline-block; 49 | width: 300px; 50 | margin: 0 0 0 0; 51 | } 52 | 53 | body > header h1 img { 54 | width: 265px; 55 | } 56 | 57 | #node-info { 58 | float: right; 59 | margin-right: 20px; 60 | color: white; 61 | } 62 | 63 | #node-info p { 64 | font-size: .9em; 65 | line-height: 20px; 66 | letter-spacing: 0.08em; 67 | } 68 | 69 | main { 70 | min-height: 100%; 71 | clear: both; 72 | } 73 | 74 | #sidenav { 75 | float: left; 76 | width: 285px; 77 | height: 100vh; 78 | padding: 25px 15px 30px 15px; 79 | background: #F4F4F4; 80 | color: #246C87; 81 | box-sizing: border-box; 82 | } 83 | 84 | #sidenav a { 85 | color: #246C87; 86 | text-decoration: none; 87 | } 88 | 89 | #sidenav ul { 90 | list-style: none; 91 | } 92 | 93 | #content { 94 | overflow: hidden; 95 | max-width: 1100px; 96 | padding: 25px 30px; 97 | } 98 | 99 | div.forms { 100 | float: left; 101 | width: 280px 102 | } 103 | 104 | div.forms form { 105 | border: 1px solid white; 106 | padding: 5px 107 | } 108 | 109 | div.list { 110 | float: left; 111 | margin-left: 40px; 112 | } 113 | nav a { 114 | text-decoration: none; 115 | border: 1px solid white; 116 | border-radius: 5px; 117 | padding: 5px; 118 | } 119 | 120 | nav ul { 121 | display: block; 122 | width: 700px; 123 | margin: 0 auto; 124 | } 125 | 126 | nav ul li{ 127 | list-style-type: none; 128 | display: inline-block; 129 | margin: 0 20px; 130 | } 131 | 132 | nav h3 { 133 | font-weight: lighter; 134 | display: inline-block 135 | } 136 | 137 | nav dl { 138 | display: inline-block 139 | } 140 | 141 | nav dl dd, nav dl dt { 142 | display: inline-block 143 | } 144 | 145 | div.files, div.dht, div.buckets, div.node { 146 | width: 1000px; 147 | } 148 | 149 | div.buckets ul { 150 | width: 630px 151 | } 152 | 153 | div.buckets li { 154 | list-style-type: none; 155 | text-align: left; 156 | margin-left: 60px 157 | } 158 | 159 | div.buckets dl { 160 | padding: 10px 161 | } 162 | 163 | div.contact { 164 | display: inline-block; 165 | } 166 | 167 | form { 168 | width: 500px; 169 | } 170 | 171 | .rpc-form { 172 | padding: 0 0 15px 0; 173 | margin: 10px 0 25px 0; 174 | border-bottom: 1px solid #C7C7C7; 175 | } 176 | 177 | label { 178 | display: block; 179 | margin: 15px 0 10px 0; 180 | } 181 | 182 | input[type="submit"] { 183 | color: white; 184 | background-color: #246C87; 185 | } 186 | 187 | table { 188 | margin-bottom: 30px; 189 | } 190 | 191 | td, th{ 192 | width: 460px; 193 | padding: 12px 10px; 194 | text-align: left; 195 | } 196 | 197 | td div { 198 | width: 450px; 199 | white-space: nowrap; 200 | overflow: hidden; 201 | text-overflow: ellipsis; 202 | } 203 | 204 | dt, dd { 205 | text-align: left; 206 | } 207 | 208 | dt { 209 | width: 80px; 210 | } 211 | 212 | dt::after { 213 | content: ":"; 214 | } 215 | 216 | a { 217 | color: #246C87; 218 | } 219 | 220 | a:hover { 221 | color: #419CBE; 222 | } 223 | 224 | .table>tbody>tr>td, 225 | .table>tbody>tr>th, 226 | .table>tfoot>tr>td, 227 | .table>tfoot>tr>th, 228 | .table>thead>tr>td, 229 | .table>thead>tr>th { 230 | vertical-align: middle; 231 | } 232 | 233 | #content dt { 234 | float: left; 235 | clear: left; 236 | font-weight: bold; 237 | } 238 | 239 | .copy-btn { 240 | margin-right: 5px; 241 | border-radius: 3px; 242 | border: 1px solid silver; 243 | background: #F1F1F1; 244 | } 245 | 246 | .copy-btn img { 247 | width: 16px; 248 | } 249 | 250 | .box { 251 | width: 800px; 252 | color: #456571; 253 | font-size: 16px; 254 | background-color: #c8dadf; 255 | position: relative; 256 | padding: 150px 20px; 257 | text-align: center; 258 | outline: 1px dashed #92b0b3; 259 | outline-offset: -10px; 260 | -webkit-transition: outline-offset .15s ease-in-out, background-color .15s linear; 261 | -moz-transition: outline-offset .15s ease-in-out, background-color .15s linear; 262 | transition: outline-offset .15s ease-in-out, background-color .15s linear; 263 | } 264 | 265 | .box_dragndrop, 266 | .box_uploading, 267 | .box_success, 268 | .box_error, 269 | .box_button { 270 | display: none; 271 | } 272 | 273 | .box_dragndrop { 274 | cursor: pointer; 275 | display: inline; 276 | } 277 | 278 | .box.is-dragover { 279 | outline: 1px dashed black; 280 | background-color: white; 281 | outline-offset: -15px; 282 | } 283 | 284 | .box.is-uploading .box_input { 285 | visibility: none; 286 | } 287 | .box.is-uploading .box_uploading { 288 | display: block; 289 | } 290 | 291 | .box_file { 292 | width: 0.1px; 293 | height: 0.1px; 294 | opacity: 0; 295 | overflow: hidden; 296 | position: absolute; 297 | z-index: -1; 298 | } 299 | 300 | .alert-box { 301 | width: 600px; 302 | padding: 10px; 303 | background: #fdce0e; 304 | margin: 0 0 15px 0; 305 | border-radius: 4px; 306 | } 307 | 308 | .close { 309 | line-height: 0.6; 310 | } 311 | 312 | .nav-pills > li.active > a, 313 | .nav-pills > li.active > a:focus, 314 | .nav-pills > li.active > a:hover { 315 | background-color: #246C87; 316 | color: white !important; 317 | } 318 | -------------------------------------------------------------------------------- /test/routing_table_4bit_test.rb: -------------------------------------------------------------------------------- 1 | require_relative 'test_helper.rb' 2 | require_relative '../lib/node.rb' 3 | require_relative "../lib/routing_table.rb" 4 | require_relative "../lib/kbucket.rb" 5 | require_relative "../lib/contact.rb" 6 | require_relative "../lib/fake_network_adapter.rb" 7 | 8 | class RoutingTableTest < Minitest::Test 9 | def setup 10 | Defaults::ENVIRONMENT[:bit_length] = 4 11 | Defaults::ENVIRONMENT[:k] = 2 12 | @kn = FakeNetworkAdapter.new 13 | @node = Node.new('0', @kn) 14 | @routing_table = @node.routing_table 15 | end 16 | 17 | def test_create_routing_table 18 | assert_equal(1, @routing_table.buckets.size) 19 | end 20 | 21 | def test_insert_node_with_duplicate_id 22 | new_node = Node.new('0', @kn) 23 | @routing_table.insert(new_node) 24 | assert_equal(0, @routing_table.buckets[0].size) 25 | end 26 | 27 | def test_insert_if_bucket_not_full 28 | node15 = Node.new('15', @kn) 29 | 30 | @routing_table.insert(node15) 31 | 32 | assert_equal(1, @routing_table.buckets.size) 33 | assert_equal(1, @routing_table.buckets[0].contacts.size) 34 | end 35 | 36 | def test_insert_find_closest_bucket_with_one_bucket 37 | result = @routing_table.find_closest_bucket('1') 38 | assert_equal(result, @routing_table.buckets[0]) 39 | end 40 | 41 | def test_insert_find_closest_bucket_with_two_buckets_no_shared_bits 42 | @routing_table.create_bucket 43 | result = @routing_table.find_closest_bucket('15') 44 | 45 | assert_equal(result, @routing_table.buckets[0]) 46 | end 47 | 48 | def test_insert_find_closest_bucket_with_two_buckets_one_shared_bit 49 | @routing_table.create_bucket 50 | result = @routing_table.find_closest_bucket('7') 51 | 52 | assert_equal(result, @routing_table.buckets[1]) 53 | end 54 | 55 | def test_insert_find_closest_bucket_with_two_buckets_no_exact_shared_bits 56 | @routing_table.create_bucket 57 | result = @routing_table.find_closest_bucket('1') 58 | 59 | assert_equal(result, @routing_table.buckets[1]) 60 | end 61 | 62 | def test_insert_find_closest_bucket_with_k_buckets_no_exact_shared_bits 63 | 3.times do 64 | @routing_table.create_bucket 65 | end 66 | 67 | result2 = @routing_table.find_closest_bucket('2') 68 | result7 = @routing_table.find_closest_bucket('7') 69 | result15 = @routing_table.find_closest_bucket('15') 70 | 71 | assert_equal(result2, @routing_table.buckets[2]) 72 | assert_equal(result7, @routing_table.buckets[1]) 73 | assert_equal(result15, @routing_table.buckets[0]) 74 | end 75 | 76 | def test_insert_if_bucket_full_and_splittable_diff_xor_distance 77 | # result is buckets.size = 2 78 | node14 = Node.new('14', @kn) 79 | node15 = Node.new('15', @kn) 80 | node7 = Node.new('7', @kn) 81 | 82 | @routing_table.insert(node14.to_contact) 83 | @routing_table.insert(node15.to_contact) 84 | @routing_table.insert(node7.to_contact) 85 | 86 | assert_equal(2, @routing_table.buckets.size) 87 | end 88 | 89 | def test_insert_if_bucket_full_and_splittable_smaller_distance_insert_first 90 | node7 = Node.new('7', @kn) 91 | node6 = Node.new('6', @kn) 92 | node13 = Node.new('13', @kn) 93 | 94 | @routing_table.insert(node7.to_contact) 95 | @routing_table.insert(node6.to_contact) 96 | @routing_table.insert(node13.to_contact) 97 | 98 | assert_equal(2, @routing_table.buckets.size) 99 | end 100 | 101 | def test_insert_if_bucket_full_and_splittable_same_xor_distance 102 | node14 = Node.new('14', @kn) 103 | node15 = Node.new('15', @kn) 104 | node13 = Node.new('13', @kn) 105 | 106 | @routing_table.insert(node14.to_contact) 107 | @routing_table.insert(node15.to_contact) 108 | @routing_table.insert(node13.to_contact) 109 | 110 | assert_equal(1, @routing_table.buckets.size) 111 | end 112 | 113 | def test_insert_if_bucket_full_and_splittable_same_xor_distance_bucket_redistributable 114 | node7 = Node.new('7', @kn) 115 | node15 = Node.new('15', @kn) 116 | node13 = Node.new('13', @kn) 117 | 118 | @routing_table.insert(node7.to_contact) 119 | @routing_table.insert(node15.to_contact) 120 | @routing_table.insert(node13.to_contact) 121 | 122 | assert_equal(2, @routing_table.buckets.size) 123 | end 124 | 125 | def test_redistribute_one_bucket_to_two 126 | node15 = Node.new('15', @kn) 127 | node3 = Node.new('3', @kn) 128 | 129 | @routing_table.insert(node15.to_contact) 130 | @routing_table.insert(node3.to_contact) 131 | 132 | @routing_table.create_bucket 133 | 134 | @routing_table.redistribute_contacts 135 | 136 | assert_equal(1, @routing_table.buckets[0].contacts.size) 137 | assert_equal(1, @routing_table.buckets[1].contacts.size) 138 | end 139 | 140 | def test_insert_if_bucket_full_and_splittable_but_contains_at_least_1_closer_element 141 | node3 = Node.new('3', @kn) 142 | node15 = Node.new('15', @kn) 143 | node13 = Node.new('13', @kn) 144 | 145 | @routing_table.insert(node3) 146 | @routing_table.insert(node15) 147 | @routing_table.insert(node13) 148 | 149 | assert_equal(2, @routing_table.buckets.size) 150 | end 151 | 152 | def test_insert_if_bucket_full_and_not_splittable 153 | node15 = Node.new('15', @kn) 154 | node14 = Node.new('14', @kn) 155 | 156 | @routing_table.insert(node15.to_contact) 157 | @routing_table.insert(node14.to_contact) 158 | 159 | node7 = Node.new('7', @kn) 160 | node6 = Node.new('6', @kn) 161 | 162 | @routing_table.insert(node7.to_contact) 163 | @routing_table.insert(node6.to_contact) 164 | 165 | node13 = Node.new('13', @kn) 166 | @routing_table.insert(node13.to_contact) 167 | 168 | assert_equal(2, @routing_table.buckets.size) 169 | end 170 | 171 | def test_insert_if_bucket_full_and_not_splittable_and_head_node_live 172 | node15 = Node.new('15', @kn) 173 | node14 = Node.new('14', @kn) 174 | 175 | @routing_table.insert(node15.to_contact) 176 | @routing_table.insert(node14.to_contact) 177 | 178 | node7 = Node.new('7', @kn) 179 | node6 = Node.new('6', @kn) 180 | 181 | @routing_table.insert(node7.to_contact) 182 | @routing_table.insert(node6.to_contact) 183 | 184 | node13 = Node.new('13', @kn) 185 | @routing_table.insert(node13.to_contact) 186 | 187 | assert_equal('15', @routing_table.buckets[0].tail.id) 188 | assert_equal('14', @routing_table.buckets[0].head.id) 189 | assert_equal(2, @routing_table.buckets.size) 190 | end 191 | 192 | def test_insert_if_bucket_full_and_not_splittable_and_head_node_not_live 193 | node15 = Node.new('15', @kn) 194 | node14 = Node.new('14', @kn) 195 | 196 | @routing_table.insert(node15.to_contact) 197 | @routing_table.insert(node14.to_contact) 198 | 199 | node7 = Node.new('7', @kn) 200 | node6 = Node.new('6', @kn) 201 | 202 | @routing_table.insert(node7.to_contact) 203 | @routing_table.insert(node6.to_contact) 204 | 205 | @kn.nodes.delete_at(1) 206 | 207 | node13 = Node.new('13', @kn) 208 | @routing_table.insert(node13.to_contact) 209 | 210 | assert_equal('13', @routing_table.buckets[0].tail.id) 211 | assert_equal('14', @routing_table.buckets[0].head.id) 212 | assert_equal(2, @routing_table.buckets.size) 213 | end 214 | end 215 | -------------------------------------------------------------------------------- /app.rb: -------------------------------------------------------------------------------- 1 | require 'sinatra/base' 2 | require 'sinatra/config_file' 3 | require 'sinatra/reloader' 4 | require 'sinatra/multi_route' 5 | require 'sinatra/flash' 6 | require 'ngrok/tunnel' 7 | require 'json' 8 | require 'erubis' 9 | require 'thin' 10 | require 'yaml' 11 | require 'concurrent' 12 | require_relative 'lib/defaults.rb' 13 | require_relative 'lib/node.rb' 14 | require_relative 'lib/network_adapter.rb' 15 | require_relative 'lib/contact.rb' 16 | require_relative 'lib/defaults.rb' 17 | require_relative 'lib/storage.rb' 18 | 19 | 20 | class XorroNode < Sinatra::Base 21 | register Sinatra::MultiRoute 22 | register Sinatra::Flash 23 | register Sinatra::ConfigFile 24 | config_file 'config.yml' 25 | 26 | set :bind, '0.0.0.0' 27 | enable :sessions 28 | 29 | network = NetworkAdapter.instance 30 | 31 | Defaults.setup(settings.port, settings.node_homes) 32 | 33 | NODE = Defaults.create_node(network, ENV['NAT'] == 'true' ? 80 : settings.port) 34 | NODE.activate(settings.port) 35 | 36 | refresh_task = Concurrent::TimerTask.new(execution_interval: 3600, timeout_interval: 3600) do 37 | NODE.buckets_refresh 38 | end 39 | refresh_task.execute 40 | 41 | rebroadcast_task = Concurrent::TimerTask.new(execution_interval: 4200, timeout_interval: 4200) do 42 | NODE.broadcast 43 | end 44 | rebroadcast_task.execute 45 | 46 | helpers do 47 | def protected! 48 | return if authorized? 49 | headers['WWW-Authenticate'] = 'Basic realm="Restricted Area"' 50 | halt 401, "Not authorized\n" 51 | end 52 | 53 | def authorized? 54 | creds = [settings.username, settings.password] 55 | @auth ||= Rack::Auth::Basic::Request.new(request.env) 56 | @auth.provided? and @auth.basic? and @auth.credentials and @auth.credentials == creds 57 | end 58 | end 59 | 60 | ### RPC Routes 61 | 62 | get '/rpc/info' do 63 | NODE.to_contact.to_json 64 | end 65 | 66 | post '/rpc/store' do 67 | file_id = params[:file_id] 68 | address = params[:address] 69 | contact = Contact.new(id: params[:id], ip: params[:ip], port: params[:port].to_i) 70 | NODE.receive_store(file_id, address, contact) 71 | status 200 72 | end 73 | 74 | post '/rpc/find_node' do 75 | node_id = params[:node_id] 76 | contact = Contact.new(id: params[:id], ip: params[:ip], port: params[:port].to_i) 77 | result = NODE.receive_find_node(node_id, contact) 78 | result.to_json 79 | end 80 | 81 | post '/rpc/find_value' do 82 | file_id = params[:file_id] 83 | contact = Contact.new(id: params[:id], ip: params[:ip], port: params[:port].to_i) 84 | result = NODE.receive_find_value(file_id, contact) 85 | result.to_json 86 | end 87 | 88 | post '/rpc/ping' do 89 | contact = Contact.new(id: params[:id], ip: params[:ip], port: params[:port].to_i) 90 | NODE.receive_ping(contact) 91 | status 200 92 | end 93 | 94 | ### File retreival routes 95 | 96 | get '/files/:filename' do 97 | send_file File.join(File.expand_path(Defaults::ENVIRONMENT[:files]), params[:filename]) 98 | end 99 | 100 | get '/manifests/:filename' do 101 | send_file File.join(File.expand_path(Defaults::ENVIRONMENT[:manifests]), params[:filename]) 102 | end 103 | 104 | get '/shards/:filename' do 105 | send_file File.join(File.expand_path(Defaults::ENVIRONMENT[:shards]), params[:filename]) 106 | end 107 | 108 | ### UI ROUTES 109 | 110 | get '/upload_file' do 111 | protected! 112 | @title = "Upload File" 113 | @node = NODE 114 | erb :upload_file 115 | end 116 | 117 | get '/get_file' do 118 | protected! 119 | @title = "Get File" 120 | @node = NODE 121 | erb :get_file 122 | end 123 | 124 | get '/', '/my_files' do 125 | protected! 126 | @title = "My Files" 127 | @refresh = '' 128 | @node = NODE 129 | @superport = @node.superport || 'none' 130 | erb :my_files 131 | end 132 | 133 | post '/get_file' do 134 | query_id = params[:file_id] 135 | file_url = NODE.files[query_id] 136 | 137 | if file_url && File.exist?(Defaults::ENVIRONMENT[:files] + "/" + File.basename(file_url)) 138 | redirect URI.escape(file_url) 139 | else 140 | result = nil 141 | 142 | if NODE.dht_segment[query_id] 143 | result = NODE.select_address(query_id) 144 | end 145 | 146 | if result.nil? 147 | result = NODE.iterative_find_value(query_id) 148 | end 149 | 150 | if result && result.is_a?(String) 151 | Thread.new { NODE.get(result) } 152 | flash[:notice] = "Your file should be downloaded shortly." 153 | redirect "/" 154 | else 155 | @node = NODE 156 | flash[:notice] = "Your file could not be found." 157 | redirect "/get_file" 158 | end 159 | end 160 | end 161 | 162 | post '/upload_file' do 163 | start = params[:data].index(',') + 1 164 | file_data = params[:data][start..-1] 165 | decode_base64_content = Base64.decode64(file_data) 166 | NODE.save_file(params[:name], decode_base64_content) 167 | status 200 168 | end 169 | 170 | # debugging rpc control methods. 171 | get '/debug/node_info' do 172 | protected! 173 | @title = "Node Information" 174 | @node = NODE 175 | @superport = @node.superport || 'none' 176 | erb :node_info 177 | end 178 | 179 | get '/debug/data' do 180 | protected! 181 | @title = "Data Cache" 182 | @node = NODE 183 | @superport = @node.superport || 'none' 184 | erb :data 185 | end 186 | 187 | get '/debug/routing_table' do 188 | protected! 189 | @title = "Routing Table" 190 | @node = NODE 191 | @superport = @node.superport || 'none' 192 | erb :routing_table 193 | end 194 | 195 | get '/debug/dht' do 196 | protected! 197 | @title = "DHT Segment" 198 | @node = NODE 199 | @superport = @node.superport || 'none' 200 | erb :dht 201 | end 202 | get '/debug/rpc' do 203 | protected! 204 | @title = "RPC Debugging" 205 | @node = NODE 206 | @superport = @node.superport || 'none' 207 | erb :rpc 208 | end 209 | 210 | # these initiate an rpc call from the current node to other nodes 211 | post '/debug/rpc/send_find_node' do 212 | query_id = params[:query_id] 213 | @result = NODE.iterative_find_node(query_id) 214 | @node = NODE 215 | erb :test 216 | end 217 | 218 | post '/debug/rpc/send_find_value' do 219 | query_id = params[:file_id] 220 | @result = NODE.iterative_find_value(query_id) 221 | @node = NODE 222 | erb :test 223 | end 224 | 225 | post '/debug/rpc/send_rpc_store' do 226 | key = params[:key] 227 | data = params[:data] 228 | 229 | contact = Contact.new(id: params[:id], ip: params[:ip], port: params[:port]) 230 | NODE.store(key, data, contact) 231 | redirect '/debug/rpc' 232 | end 233 | 234 | post '/debug/rpc/send_it_store' do 235 | key = params[:key] 236 | data = params[:data] 237 | NODE.iterative_store(key, data) 238 | 239 | redirect '/debug/rpc' 240 | end 241 | 242 | post '/debug/rpc/send_rpc_ping' do 243 | contact = Contact.new(id: params[:id], ip: params[:ip], port: params[:port].to_i) 244 | NODE.ping(contact) 245 | redirect '/debug/rpc' 246 | end 247 | 248 | run! if app_file == $0 249 | end 250 | -------------------------------------------------------------------------------- /test/routing_table_6bit_test.rb: -------------------------------------------------------------------------------- 1 | require_relative 'test_helper.rb' 2 | require_relative '../lib/node.rb' 3 | require_relative "../lib/routing_table.rb" 4 | require_relative "../lib/kbucket.rb" 5 | require_relative "../lib/contact.rb" 6 | require_relative "../lib/fake_network_adapter.rb" 7 | 8 | class RoutingTableTest6 < Minitest::Test 9 | def setup 10 | Defaults::ENVIRONMENT[:bit_length] = 6 11 | Defaults::ENVIRONMENT[:k] = 2 12 | @kn = FakeNetworkAdapter.new 13 | @node = Node.new('0', @kn) 14 | @routing_table = @node.routing_table 15 | end 16 | 17 | def test_create_routing_table 18 | assert_equal(1, @routing_table.buckets.size) 19 | end 20 | 21 | def test_insert_node_with_duplicate_id 22 | new_node = Node.new('0', @kn) 23 | 24 | @routing_table.insert(new_node) 25 | assert_equal(0, @routing_table.buckets[0].size) 26 | end 27 | 28 | def test_insert_if_bucket_not_full 29 | node15 = Node.new('15', @kn) 30 | 31 | @routing_table.insert(node15) 32 | 33 | assert_equal(1, @routing_table.buckets.size) 34 | assert_equal(1, @routing_table.buckets[0].contacts.size) 35 | end 36 | 37 | def test_insert_find_closest_bucket_with_one_bucket 38 | result = @routing_table.find_closest_bucket('1') 39 | assert_equal(result, @routing_table.buckets[0]) 40 | end 41 | 42 | def test_insert_find_closest_bucket_with_two_buckets_no_shared_bits 43 | @routing_table.create_bucket 44 | result = @routing_table.find_closest_bucket('15') 45 | 46 | assert_equal(result, @routing_table.buckets[1]) 47 | end 48 | 49 | def test_insert_find_closest_bucket_with_two_buckets_one_shared_bit 50 | @routing_table.create_bucket 51 | result = @routing_table.find_closest_bucket('7') 52 | 53 | assert_equal(result, @routing_table.buckets[1]) 54 | end 55 | 56 | def test_insert_find_closest_bucket_with_two_buckets_no_exact_shared_bits 57 | @routing_table.create_bucket 58 | result = @routing_table.find_closest_bucket('1') 59 | 60 | assert_equal(result, @routing_table.buckets[1]) 61 | end 62 | 63 | def test_insert_find_closest_bucket_with_k_buckets_no_exact_shared_bits 64 | 3.times do 65 | @routing_table.create_bucket 66 | end 67 | 68 | result2 = @routing_table.find_closest_bucket('2') 69 | result7 = @routing_table.find_closest_bucket('7') 70 | result15 = @routing_table.find_closest_bucket('15') 71 | 72 | assert_equal(result2, @routing_table.buckets.last) 73 | assert_equal(result7, @routing_table.buckets.last) 74 | assert_equal(result15, @routing_table.buckets[2]) 75 | end 76 | 77 | def test_insert_if_bucket_full_and_splittable_diff_xor_distance 78 | node14 = Node.new('14', @kn) 79 | node15 = Node.new('15', @kn) 80 | node7 = Node.new('7', @kn) 81 | 82 | @routing_table.insert(node14.to_contact) 83 | @routing_table.insert(node15.to_contact) 84 | @routing_table.insert(node7.to_contact) 85 | 86 | assert_equal(4, @routing_table.buckets.size) 87 | end 88 | 89 | def test_insert_if_bucket_full_and_splittable_smaller_distance_insert_first 90 | node7 = Node.new('7', @kn) 91 | node6 = Node.new('6', @kn) 92 | node13 = Node.new('13', @kn) 93 | node62 = Node.new('62', @kn) 94 | 95 | @routing_table.insert(node7.to_contact) 96 | @routing_table.insert(node6.to_contact) 97 | @routing_table.insert(node13.to_contact) 98 | @routing_table.insert(node62.to_contact) 99 | 100 | assert_equal(4, @routing_table.buckets.size) 101 | end 102 | 103 | def test_insert_if_bucket_full_and_splittable_same_xor_distance 104 | node63 = Node.new('63', @kn) 105 | node62 = Node.new('62', @kn) 106 | node61 = Node.new('61', @kn) 107 | 108 | @routing_table.insert(node63.to_contact) 109 | @routing_table.insert(node62.to_contact) 110 | @routing_table.insert(node61.to_contact) 111 | 112 | assert_equal(1, @routing_table.buckets.size) 113 | end 114 | 115 | def test_insert_if_bucket_full_and_splittable_same_xor_distance_bucket_redistributable 116 | node7 = Node.new('7', @kn) 117 | node63 = Node.new('63', @kn) 118 | node62 = Node.new('62', @kn) 119 | 120 | @routing_table.insert(node7.to_contact) 121 | @routing_table.insert(node63.to_contact) 122 | @routing_table.insert(node62.to_contact) 123 | 124 | assert_equal(2, @routing_table.buckets.size) 125 | end 126 | 127 | def test_redistribute_one_bucket_to_two 128 | node63 = Node.new('63', @kn) 129 | node3 = Node.new('3', @kn) 130 | 131 | @routing_table.insert(node63.to_contact) 132 | @routing_table.insert(node3.to_contact) 133 | 134 | @routing_table.create_bucket 135 | 136 | @routing_table.redistribute_contacts 137 | 138 | assert_equal(1, @routing_table.buckets[0].contacts.size) 139 | assert_equal(1, @routing_table.buckets[1].contacts.size) 140 | end 141 | 142 | def test_insert_if_bucket_full_and_splittable_but_contains_at_least_1_closer_element 143 | node3 = Node.new('3', @kn) 144 | node63 = Node.new('63', @kn) 145 | node62 = Node.new('62', @kn) 146 | 147 | @routing_table.insert(node3) 148 | @routing_table.insert(node63) 149 | @routing_table.insert(node62) 150 | 151 | assert_equal(2, @routing_table.buckets.size) 152 | end 153 | 154 | def test_insert_if_bucket_full_and_not_splittable 155 | node63 = Node.new('63', @kn) 156 | node62 = Node.new('62', @kn) 157 | 158 | @routing_table.insert(node63.to_contact) 159 | @routing_table.insert(node62.to_contact) 160 | 161 | node7 = Node.new('7', @kn) 162 | node6 = Node.new('6', @kn) 163 | 164 | @routing_table.insert(node7.to_contact) 165 | @routing_table.insert(node6.to_contact) 166 | 167 | node13 = Node.new('61', @kn) 168 | @routing_table.insert(node13.to_contact) 169 | 170 | assert_equal(2, @routing_table.buckets.size) 171 | end 172 | 173 | def test_insert_if_bucket_full_and_not_splittable_and_head_node_live 174 | node63 = Node.new('63', @kn) 175 | node62 = Node.new('62', @kn) 176 | 177 | @routing_table.insert(node63.to_contact) 178 | @routing_table.insert(node62.to_contact) 179 | 180 | node7 = Node.new('7', @kn) 181 | node6 = Node.new('6', @kn) 182 | 183 | @routing_table.insert(node7.to_contact) 184 | @routing_table.insert(node6.to_contact) 185 | 186 | node61 = Node.new('61', @kn) 187 | @routing_table.insert(node61.to_contact) 188 | 189 | assert_equal('63', @routing_table.buckets[0].tail.id) 190 | assert_equal('62', @routing_table.buckets[0].head.id) 191 | assert_equal(2, @routing_table.buckets.size) 192 | end 193 | 194 | def test_insert_if_bucket_full_and_not_splittable_and_head_node_not_live 195 | node63 = Node.new('63', @kn) 196 | node62 = Node.new('62', @kn) 197 | 198 | @routing_table.insert(node63.to_contact) 199 | @routing_table.insert(node62.to_contact) 200 | 201 | node7 = Node.new('7', @kn) 202 | node6 = Node.new('6', @kn) 203 | 204 | @routing_table.insert(node7.to_contact) 205 | @routing_table.insert(node6.to_contact) 206 | 207 | @kn.nodes.delete_at(1) 208 | 209 | node61 = Node.new('61', @kn) 210 | @routing_table.insert(node61.to_contact) 211 | 212 | assert_equal('61', @routing_table.buckets[0].tail.id) 213 | assert_equal('62', @routing_table.buckets[0].head.id) 214 | assert_equal(2, @routing_table.buckets.size) 215 | end 216 | end 217 | -------------------------------------------------------------------------------- /test/routing_table_160bit_test.rb: -------------------------------------------------------------------------------- 1 | require_relative 'test_helper.rb' 2 | require_relative '../lib/node.rb' 3 | require_relative "../lib/routing_table.rb" 4 | require_relative "../lib/kbucket.rb" 5 | require_relative "../lib/contact.rb" 6 | require_relative "../lib/fake_network_adapter.rb" 7 | 8 | class RoutingTableTest160 < Minitest::Test 9 | def setup 10 | Defaults::ENVIRONMENT[:bit_length] = 160 11 | Defaults::ENVIRONMENT[:k] = 2 12 | @kn = FakeNetworkAdapter.new 13 | @node = Node.new('0', @kn) 14 | @routing_table = @node.routing_table 15 | @two_to159 = 2**(Defaults::ENVIRONMENT[:bit_length] - 1) 16 | end 17 | 18 | def test_create_routing_table 19 | assert_equal(1, @routing_table.buckets.size) 20 | end 21 | 22 | def test_insert_node_with_duplicate_id 23 | new_node = Node.new('0', @kn) 24 | 25 | @routing_table.insert(new_node) 26 | assert_equal(0, @routing_table.buckets[0].size) 27 | end 28 | 29 | def test_insert_if_bucket_not_full 30 | node15 = Node.new('15', @kn) 31 | 32 | @routing_table.insert(node15) 33 | 34 | assert_equal(1, @routing_table.buckets.size) 35 | assert_equal(1, @routing_table.buckets[0].contacts.size) 36 | end 37 | 38 | def test_insert_find_closest_bucket_with_one_bucket_with_closest_shared_bit_length 39 | result = @routing_table.find_closest_bucket('1') 40 | assert_equal(result, @routing_table.buckets[0]) 41 | end 42 | 43 | def test_insert_find_closest_bucket_with_two_buckets_no_shared_bit_length 44 | @routing_table.create_bucket 45 | 46 | no_shared_id = @two_to159.to_s 47 | 48 | result = @routing_table.find_closest_bucket(no_shared_id) 49 | 50 | assert_equal(result, @routing_table.buckets[0]) 51 | end 52 | 53 | def test_insert_find_closest_bucket_with_two_buckets_no_shared_bit_length_bug 54 | @routing_table.create_bucket 55 | 56 | no_shared_id = (2**Defaults::ENVIRONMENT[:bit_length] - 1).to_s 57 | 58 | result = @routing_table.find_closest_bucket(no_shared_id) 59 | 60 | assert_equal(result, @routing_table.buckets[0]) 61 | end 62 | 63 | def test_insert_find_closest_bucket_with_two_buckets_one_shared_bit 64 | @routing_table.create_bucket 65 | one_shared_id = (2 ** (Defaults::ENVIRONMENT[:bit_length] - 2)).to_s 66 | 67 | result = @routing_table.find_closest_bucket(one_shared_id) 68 | 69 | assert_equal(result, @routing_table.buckets[1]) 70 | end 71 | 72 | def test_insert_find_closest_bucket_with_two_buckets_with_most_shared_bits 73 | @routing_table.create_bucket 74 | result = @routing_table.find_closest_bucket('1') 75 | 76 | assert_equal(result, @routing_table.buckets[1]) 77 | end 78 | 79 | def test_insert_find_closest_bucket_with_full_buckets_with_arbitrary_shared_bits 80 | total_buckets = Defaults::ENVIRONMENT[:bit_length] - 1 # create bucket equal to k 81 | 82 | total_buckets.times do 83 | @routing_table.create_bucket 84 | end 85 | 86 | result2 = @routing_table.find_closest_bucket('2') 87 | position = Binary.shared_prefix_bit_length('0', '2') 88 | result7 = @routing_table.find_closest_bucket('7') 89 | position2 = Binary.shared_prefix_bit_length('0', '7') 90 | result15 = @routing_table.find_closest_bucket('15') 91 | position3 = Binary.shared_prefix_bit_length('0', '15') 92 | 93 | assert_equal(result2, @routing_table.buckets[position]) 94 | 95 | assert_equal(result7, @routing_table.buckets[position2]) 96 | 97 | assert_equal(result15, @routing_table.buckets[position3]) 98 | end 99 | 100 | def test_insert_if_bucket_full_and_splittable_diff_xor_distance 101 | node_id = '7' 102 | 103 | node14 = Node.new('14', @kn) 104 | node15 = Node.new('15', @kn) 105 | node7 = Node.new(node_id, @kn) 106 | 107 | index_of_inserted_bucket = Binary.shared_prefix_bit_length('0', node_id) 108 | 109 | @routing_table.insert(node14.to_contact) 110 | @routing_table.insert(node15.to_contact) 111 | @routing_table.insert(node7.to_contact) 112 | 113 | assert_equal(index_of_inserted_bucket + 1, @routing_table.buckets.size) 114 | end 115 | 116 | def test_insert_if_bucket_full_and_splittable_smaller_distance_insert_first 117 | node7 = Node.new('7', @kn) # 7 and 6 are same distance 118 | node6 = Node.new('6', @kn) # 7 and 6 are same distance 119 | node13 = Node.new('13', @kn) # this id is further 120 | 121 | index1 = Binary.shared_prefix_bit_length('0', '7') 122 | index2 = Binary.shared_prefix_bit_length('0', '13') 123 | @routing_table.insert(node7.to_contact) 124 | @routing_table.insert(node6.to_contact) 125 | @routing_table.insert(node13.to_contact) 126 | 127 | index_of_last_bucket = Binary.shared_prefix_bit_length('0', '7') 128 | 129 | assert_equal(2, @routing_table.buckets[index1].contacts.size) 130 | assert_equal(1, @routing_table.buckets[index2].contacts.size) 131 | assert_equal(index_of_last_bucket + 1, @routing_table.buckets.size) 132 | end 133 | 134 | def test_insert_if_bucket_full_and_splittable_same_xor_distance 135 | node14 = Node.new('14', @kn) 136 | node15 = Node.new('15', @kn) 137 | node13 = Node.new('13', @kn) 138 | 139 | index_of_last_bucket = Binary.shared_prefix_bit_length('0', '14') 140 | 141 | @routing_table.insert(node14.to_contact) 142 | @routing_table.insert(node15.to_contact) 143 | @routing_table.insert(node13.to_contact) 144 | 145 | assert_equal(2, @routing_table.buckets[index_of_last_bucket].contacts.size) 146 | assert_equal(index_of_last_bucket + 1, @routing_table.buckets.size) 147 | end 148 | 149 | def test_insert_if_bucket_full_and_splittable_same_xor_distance_bucket_redistributable 150 | node7 = Node.new('7', @kn) 151 | node15 = Node.new('15', @kn) 152 | node13 = Node.new('13', @kn) 153 | 154 | @routing_table.insert(node7.to_contact) 155 | @routing_table.insert(node15.to_contact) 156 | @routing_table.insert(node13.to_contact) 157 | 158 | index_of_last_bucket = Binary.shared_prefix_bit_length('0', '7') 159 | 160 | assert_equal(1, @routing_table.buckets[index_of_last_bucket].contacts.size) 161 | assert_equal(2, @routing_table.buckets[index_of_last_bucket - 1].contacts.size) 162 | assert_equal(index_of_last_bucket + 1, @routing_table.buckets.size) 163 | end 164 | 165 | def test_redistribute_one_bucket_to_two 166 | no_shared_id = @two_to159.to_s 167 | node_no_shared = Node.new(no_shared_id, @kn) 168 | node3 = Node.new('3', @kn) 169 | 170 | @routing_table.insert(node_no_shared.to_contact) 171 | @routing_table.insert(node3.to_contact) 172 | 173 | @routing_table.create_bucket 174 | 175 | @routing_table.redistribute_contacts 176 | 177 | assert_equal(1, @routing_table.buckets[0].contacts.size) 178 | assert_equal(1, @routing_table.buckets[1].contacts.size) 179 | end 180 | 181 | def test_insert_if_bucket_full_and_splittable_but_contains_at_least_1_closer_element 182 | no_shared_id = @two_to159.to_s 183 | 184 | node3 = Node.new('3', @kn) 185 | node_no_shared = Node.new(no_shared_id, @kn) 186 | node13 = Node.new('13', @kn) 187 | 188 | @routing_table.insert(node3) 189 | @routing_table.insert(node_no_shared) 190 | @routing_table.insert(node13) 191 | 192 | assert_equal(2, @routing_table.buckets.size) 193 | end 194 | 195 | def test_insert_if_bucket_full_and_not_splittable 196 | no_shared_id = @two_to159.to_s 197 | no_shared_id2 = (@two_to159 + 1).to_s 198 | no_shared_id3 = (@two_to159 + 2).to_s 199 | 200 | node_no_shared_1 = Node.new(no_shared_id, @kn) 201 | node_no_shared_2 = Node.new(no_shared_id2, @kn) 202 | 203 | @routing_table.insert(node_no_shared_1.to_contact) 204 | @routing_table.insert(node_no_shared_2.to_contact) 205 | 206 | node7 = Node.new('7', @kn) 207 | node6 = Node.new('6', @kn) 208 | 209 | @routing_table.insert(node7.to_contact) 210 | @routing_table.insert(node6.to_contact) 211 | 212 | node_no_shared_3 = Node.new(no_shared_id3, @kn) 213 | @routing_table.insert(node_no_shared_3.to_contact) 214 | 215 | assert_equal(2, @routing_table.buckets[0].contacts.size) 216 | assert_equal(2, @routing_table.buckets.size) 217 | end 218 | 219 | def test_insert_if_bucket_full_and_not_splittable_and_head_node_live 220 | no_shared_id = @two_to159.to_s 221 | no_shared_id2 = (@two_to159 + 1).to_s 222 | no_shared_id3 = (@two_to159 + 2).to_s 223 | 224 | node_no_shared_1 = Node.new(no_shared_id, @kn) 225 | node_no_shared_2 = Node.new(no_shared_id2, @kn) 226 | 227 | @routing_table.insert(node_no_shared_1.to_contact) 228 | @routing_table.insert(node_no_shared_2.to_contact) 229 | 230 | node7 = Node.new('7', @kn) 231 | node6 = Node.new('6', @kn) 232 | 233 | @routing_table.insert(node7.to_contact) 234 | @routing_table.insert(node6.to_contact) 235 | 236 | node_no_shared_3 = Node.new(no_shared_id3, @kn) 237 | @routing_table.insert(node_no_shared_3.to_contact) 238 | 239 | assert_equal(no_shared_id, @routing_table.buckets[0].tail.id) 240 | assert_equal(no_shared_id2, @routing_table.buckets[0].head.id) 241 | assert_equal(2, @routing_table.buckets.size) 242 | end 243 | 244 | def test_insert_if_bucket_full_and_not_splittable_and_head_node_not_live 245 | no_shared_id = @two_to159.to_s 246 | no_shared_id2 = (@two_to159 + 1).to_s 247 | no_shared_id3 = (@two_to159 + 2).to_s 248 | 249 | node_no_shared_1 = Node.new(no_shared_id, @kn) 250 | node_no_shared_2 = Node.new(no_shared_id2, @kn) 251 | 252 | @routing_table.insert(node_no_shared_1.to_contact) 253 | @routing_table.insert(node_no_shared_2.to_contact) 254 | 255 | node7 = Node.new('7', @kn) 256 | node6 = Node.new('6', @kn) 257 | 258 | @routing_table.insert(node7.to_contact) 259 | @routing_table.insert(node6.to_contact) 260 | 261 | @kn.nodes.delete_at(1) 262 | 263 | node_no_shared_3 = Node.new(no_shared_id3, @kn) 264 | @routing_table.insert(node_no_shared_3.to_contact) 265 | 266 | assert_equal(no_shared_id3, @routing_table.buckets[0].tail.id) 267 | assert_equal(no_shared_id2, @routing_table.buckets[0].head.id) 268 | assert_equal(2, @routing_table.buckets.size) 269 | end 270 | end 271 | -------------------------------------------------------------------------------- /test/routing_table_6bit_8k_test.rb: -------------------------------------------------------------------------------- 1 | require_relative 'test_helper.rb' 2 | require_relative '../lib/node.rb' 3 | require_relative "../lib/routing_table.rb" 4 | require_relative "../lib/kbucket.rb" 5 | require_relative "../lib/contact.rb" 6 | require_relative "../lib/fake_network_adapter.rb" 7 | 8 | class RoutingTableTest6bit8k < Minitest::Test 9 | def setup 10 | Defaults::ENVIRONMENT[:bit_length] = 6 11 | Defaults::ENVIRONMENT[:k] = 8 12 | @kn = FakeNetworkAdapter.new 13 | @node = Node.new('0', @kn) 14 | @routing_table = @node.routing_table 15 | # [32-63] [16-31] [8-15] [4-7] [2-3] [1] 16 | end 17 | 18 | def test_create_routing_table 19 | assert_equal(1, @routing_table.buckets.size) 20 | end 21 | 22 | def test_insert_node_with_duplicate_id 23 | new_node = Node.new('0', @kn) 24 | 25 | @routing_table.insert(new_node) 26 | assert_equal(0, @routing_table.buckets[0].size) 27 | end 28 | 29 | def test_insert_if_bucket_not_full 30 | node15 = Node.new('15', @kn) 31 | 32 | @routing_table.insert(node15) 33 | 34 | assert_equal(1, @routing_table.buckets.size) 35 | assert_equal(1, @routing_table.buckets[0].contacts.size) 36 | end 37 | 38 | def test_insert_find_closest_bucket_with_one_bucket 39 | result = @routing_table.find_closest_bucket('1') 40 | assert_equal(result, @routing_table.buckets[0]) 41 | end 42 | 43 | def test_insert_find_closest_bucket_with_two_buckets_no_shared_bits 44 | @routing_table.create_bucket 45 | result = @routing_table.find_closest_bucket('60') 46 | 47 | assert_equal(result, @routing_table.buckets[0]) 48 | end 49 | 50 | def test_insert_find_closest_bucket_with_two_buckets_one_shared_bit 51 | @routing_table.create_bucket 52 | result = @routing_table.find_closest_bucket('31') 53 | 54 | assert_equal(result, @routing_table.buckets[1]) 55 | end 56 | 57 | def test_insert_find_closest_bucket_with_two_buckets_no_exact_shared_bits 58 | @routing_table.create_bucket 59 | result = @routing_table.find_closest_bucket('1') 60 | 61 | assert_equal(result, @routing_table.buckets[1]) 62 | end 63 | 64 | def test_insert_find_closest_bucket_with_k_buckets_no_exact_shared_bits 65 | 5.times do 66 | @routing_table.create_bucket 67 | end 68 | 69 | result1 = @routing_table.find_closest_bucket('1') 70 | result7 = @routing_table.find_closest_bucket('7') 71 | result63 = @routing_table.find_closest_bucket('63') 72 | 73 | assert_equal(result1, @routing_table.buckets.last) 74 | assert_equal(result7, @routing_table.buckets[3]) 75 | assert_equal(result63, @routing_table.buckets.first) 76 | end 77 | 78 | def test_insert_if_bucket_full_and_splittable_diff_xor_distance 79 | node32 = Node.new('32', @kn) 80 | node57 = Node.new('57', @kn) 81 | node58 = Node.new('58', @kn) 82 | node59 = Node.new('59', @kn) 83 | node60 = Node.new('60', @kn) 84 | node61 = Node.new('61', @kn) 85 | node62 = Node.new('62', @kn) 86 | node63 = Node.new('63', @kn) 87 | 88 | node7 = Node.new('7', @kn) 89 | 90 | @routing_table.insert(node32.to_contact) 91 | @routing_table.insert(node57.to_contact) 92 | @routing_table.insert(node58.to_contact) 93 | @routing_table.insert(node59.to_contact) 94 | @routing_table.insert(node60.to_contact) 95 | @routing_table.insert(node61.to_contact) 96 | @routing_table.insert(node62.to_contact) 97 | @routing_table.insert(node63.to_contact) 98 | 99 | @routing_table.insert(node7.to_contact) 100 | 101 | assert_equal(2, @routing_table.buckets.size) 102 | end 103 | 104 | def test_insert_if_bucket_full_and_splittable_smaller_distance_insert_first 105 | node16 = Node.new('16', @kn) 106 | node17 = Node.new('17', @kn) 107 | node58 = Node.new('58', @kn) 108 | node59 = Node.new('59', @kn) 109 | node60 = Node.new('60', @kn) 110 | node61 = Node.new('61', @kn) 111 | node62 = Node.new('62', @kn) 112 | node63 = Node.new('63', @kn) 113 | 114 | node40 = Node.new('40', @kn) 115 | 116 | @routing_table.insert(node16.to_contact) 117 | @routing_table.insert(node17.to_contact) 118 | @routing_table.insert(node58.to_contact) 119 | @routing_table.insert(node59.to_contact) 120 | @routing_table.insert(node60.to_contact) 121 | @routing_table.insert(node61.to_contact) 122 | @routing_table.insert(node62.to_contact) 123 | @routing_table.insert(node63.to_contact) 124 | 125 | @routing_table.insert(node40.to_contact) 126 | 127 | assert_equal(2, @routing_table.buckets.size) 128 | assert_equal(2, @routing_table.buckets.last.size) 129 | end 130 | 131 | def test_insert_if_bucket_full_and_splittable_same_xor_distance 132 | node32 = Node.new('32', @kn) 133 | node57 = Node.new('57', @kn) 134 | node58 = Node.new('58', @kn) 135 | node59 = Node.new('59', @kn) 136 | node60 = Node.new('60', @kn) 137 | node61 = Node.new('61', @kn) 138 | node62 = Node.new('62', @kn) 139 | node63 = Node.new('63', @kn) 140 | 141 | node40 = Node.new('40', @kn) 142 | 143 | @routing_table.insert(node32.to_contact) 144 | @routing_table.insert(node57.to_contact) 145 | @routing_table.insert(node58.to_contact) 146 | @routing_table.insert(node59.to_contact) 147 | @routing_table.insert(node60.to_contact) 148 | @routing_table.insert(node61.to_contact) 149 | @routing_table.insert(node62.to_contact) 150 | @routing_table.insert(node63.to_contact) 151 | 152 | @routing_table.insert(node40.to_contact) 153 | 154 | assert_equal(1, @routing_table.buckets.size) 155 | end 156 | 157 | def test_insert_if_bucket_full_and_splittable_same_xor_distance_bucket_redistributable 158 | node7 = Node.new('7', @kn) 159 | node57 = Node.new('57', @kn) 160 | node58 = Node.new('58', @kn) 161 | node59 = Node.new('59', @kn) 162 | node60 = Node.new('60', @kn) 163 | node61 = Node.new('61', @kn) 164 | node62 = Node.new('62', @kn) 165 | node63 = Node.new('63', @kn) 166 | 167 | node40 = Node.new('40', @kn) 168 | 169 | @routing_table.insert(node7.to_contact) 170 | @routing_table.insert(node57.to_contact) 171 | @routing_table.insert(node58.to_contact) 172 | @routing_table.insert(node59.to_contact) 173 | @routing_table.insert(node60.to_contact) 174 | @routing_table.insert(node61.to_contact) 175 | @routing_table.insert(node62.to_contact) 176 | @routing_table.insert(node63.to_contact) 177 | 178 | @routing_table.insert(node40.to_contact) 179 | 180 | assert_equal(2, @routing_table.buckets.size) 181 | end 182 | 183 | def test_redistribute_one_bucket_to_two 184 | node32 = Node.new('32', @kn) 185 | node57 = Node.new('57', @kn) 186 | node58 = Node.new('58', @kn) 187 | node59 = Node.new('59', @kn) 188 | node60 = Node.new('60', @kn) 189 | node61 = Node.new('61', @kn) 190 | node62 = Node.new('62', @kn) 191 | 192 | node16 = Node.new('16', @kn) 193 | 194 | @routing_table.insert(node32.to_contact) 195 | @routing_table.insert(node57.to_contact) 196 | @routing_table.insert(node58.to_contact) 197 | @routing_table.insert(node59.to_contact) 198 | @routing_table.insert(node60.to_contact) 199 | @routing_table.insert(node61.to_contact) 200 | @routing_table.insert(node62.to_contact) 201 | 202 | @routing_table.insert(node16.to_contact) 203 | 204 | @routing_table.create_bucket 205 | 206 | @routing_table.redistribute_contacts 207 | 208 | assert_equal(7, @routing_table.buckets[0].contacts.size) 209 | assert_equal(1, @routing_table.buckets[1].contacts.size) 210 | end 211 | 212 | def test_insert_if_bucket_full_and_splittable_but_contains_at_least_1_closer_element 213 | node3 = Node.new('3', @kn) 214 | node32 = Node.new('32', @kn) 215 | node57 = Node.new('57', @kn) 216 | node58 = Node.new('58', @kn) 217 | node59 = Node.new('59', @kn) 218 | node60 = Node.new('60', @kn) 219 | node61 = Node.new('61', @kn) 220 | node62 = Node.new('62', @kn) 221 | 222 | node63 = Node.new('63', @kn) 223 | 224 | @routing_table.insert(node3.to_contact) 225 | @routing_table.insert(node32.to_contact) 226 | @routing_table.insert(node57.to_contact) 227 | @routing_table.insert(node58.to_contact) 228 | @routing_table.insert(node59.to_contact) 229 | @routing_table.insert(node60.to_contact) 230 | @routing_table.insert(node61.to_contact) 231 | @routing_table.insert(node62.to_contact) 232 | 233 | @routing_table.insert(node63.to_contact) 234 | 235 | assert_equal(2, @routing_table.buckets.size) 236 | end 237 | 238 | def test_insert_if_bucket_full_and_not_splittable 239 | node32 = Node.new('32', @kn) 240 | node57 = Node.new('57', @kn) 241 | node58 = Node.new('58', @kn) 242 | node59 = Node.new('59', @kn) 243 | node60 = Node.new('60', @kn) 244 | node61 = Node.new('61', @kn) 245 | node62 = Node.new('62', @kn) 246 | node63 = Node.new('63', @kn) 247 | 248 | @routing_table.insert(node32.to_contact) 249 | @routing_table.insert(node57.to_contact) 250 | @routing_table.insert(node58.to_contact) 251 | @routing_table.insert(node59.to_contact) 252 | @routing_table.insert(node60.to_contact) 253 | @routing_table.insert(node61.to_contact) 254 | @routing_table.insert(node62.to_contact) 255 | @routing_table.insert(node63.to_contact) 256 | 257 | node7 = Node.new('7', @kn) 258 | node6 = Node.new('6', @kn) 259 | 260 | @routing_table.insert(node7.to_contact) 261 | @routing_table.insert(node6.to_contact) 262 | 263 | node40 = Node.new('40', @kn) 264 | @routing_table.insert(node40.to_contact) 265 | 266 | assert_equal(2, @routing_table.buckets.size) 267 | end 268 | 269 | def test_insert_if_bucket_full_and_not_splittable_and_head_node_live 270 | node32 = Node.new('32', @kn) 271 | node57 = Node.new('57', @kn) 272 | node58 = Node.new('58', @kn) 273 | node59 = Node.new('59', @kn) 274 | node60 = Node.new('60', @kn) 275 | node61 = Node.new('61', @kn) 276 | node62 = Node.new('62', @kn) 277 | node63 = Node.new('63', @kn) 278 | 279 | @routing_table.insert(node32.to_contact) 280 | @routing_table.insert(node57.to_contact) 281 | @routing_table.insert(node58.to_contact) 282 | @routing_table.insert(node59.to_contact) 283 | @routing_table.insert(node60.to_contact) 284 | @routing_table.insert(node61.to_contact) 285 | @routing_table.insert(node62.to_contact) 286 | @routing_table.insert(node63.to_contact) 287 | 288 | node7 = Node.new('7', @kn) 289 | node6 = Node.new('6', @kn) 290 | 291 | @routing_table.insert(node7.to_contact) 292 | @routing_table.insert(node6.to_contact) 293 | 294 | node40 = Node.new('40', @kn) 295 | @routing_table.insert(node40.to_contact) 296 | 297 | assert_equal('32', @routing_table.buckets[0].tail.id) 298 | assert_equal('57', @routing_table.buckets[0].head.id) 299 | assert_equal(2, @routing_table.buckets.size) 300 | end 301 | 302 | def test_insert_if_bucket_full_and_not_splittable_and_head_node_not_live 303 | node32 = Node.new('32', @kn) 304 | node57 = Node.new('57', @kn) 305 | node58 = Node.new('58', @kn) 306 | node59 = Node.new('59', @kn) 307 | node60 = Node.new('60', @kn) 308 | node61 = Node.new('61', @kn) 309 | node62 = Node.new('62', @kn) 310 | node63 = Node.new('63', @kn) 311 | 312 | @routing_table.insert(node32.to_contact) 313 | @routing_table.insert(node57.to_contact) 314 | @routing_table.insert(node58.to_contact) 315 | @routing_table.insert(node59.to_contact) 316 | @routing_table.insert(node60.to_contact) 317 | @routing_table.insert(node61.to_contact) 318 | @routing_table.insert(node62.to_contact) 319 | @routing_table.insert(node63.to_contact) 320 | 321 | node7 = Node.new('7', @kn) 322 | node6 = Node.new('6', @kn) 323 | 324 | @routing_table.insert(node7.to_contact) 325 | @routing_table.insert(node6.to_contact) 326 | 327 | @kn.nodes.delete_at(1) 328 | 329 | node40 = Node.new('40', @kn) 330 | @routing_table.insert(node40.to_contact) 331 | 332 | assert_equal('40', @routing_table.buckets[0].tail.id) 333 | assert_equal('57', @routing_table.buckets[0].head.id) 334 | assert_equal(2, @routing_table.buckets.size) 335 | end 336 | end 337 | -------------------------------------------------------------------------------- /test/node_4bit_test.rb: -------------------------------------------------------------------------------- 1 | require_relative 'test_helper.rb' 2 | require_relative "../lib/node.rb" 3 | require_relative "../lib/routing_table.rb" 4 | require_relative "../lib/fake_network_adapter.rb" 5 | require_relative "../lib/kbucket.rb" 6 | 7 | class NodeTest4bit < Minitest::Test 8 | def setup 9 | Defaults::ENVIRONMENT[:bit_length] = 4 10 | Defaults::ENVIRONMENT[:k] = 2 11 | Defaults::ENVIRONMENT[:alpha] = 1 12 | @kn = FakeNetworkAdapter.new 13 | end 14 | 15 | def test_create_node 16 | node = Node.new('0', @kn) 17 | 18 | assert_instance_of(Node, node) 19 | assert_instance_of(RoutingTable, node.routing_table) 20 | end 21 | 22 | def test_join_network 23 | node1 = Node.new('1', @kn) 24 | node2 = Node.new('2', @kn) 25 | 26 | assert_includes(@kn.nodes, node1) 27 | assert_includes(@kn.nodes, node2) 28 | end 29 | 30 | def test_ping_other_node_true_and_false 31 | node0 = Node.new('0', @kn) 32 | node1 = Node.new('1', @kn) 33 | node2 = Node.new('2', @kn) 34 | 35 | assert(node0.ping(node1.to_contact)) 36 | assert(node0.ping(node2.to_contact)) 37 | refute(node0.ping(Contact.new(id: '3', ip: ''))) 38 | end 39 | 40 | def test_ping_dead_node; end 41 | 42 | def test_receive_ping 43 | node0 = Node.new('0', @kn) 44 | node1 = Node.new('1', @kn) 45 | 46 | refute_includes(node0.routing_table.buckets[0].contacts, node1.to_contact) 47 | refute_includes(node1.routing_table.buckets[0].contacts, node0.to_contact) 48 | 49 | node0.ping(node1.to_contact) 50 | 51 | assert_equal(1, node1.routing_table.buckets[0].contacts.size) 52 | assert_equal(1, node0.routing_table.buckets[0].contacts.size) 53 | end 54 | 55 | def test_store 56 | node0 = Node.new('0', @kn) 57 | node1 = Node.new('1', @kn) 58 | 59 | node0.store('key', 'value', node1.to_contact) 60 | assert_equal(['value'], node1.dht_segment['key']) 61 | end 62 | 63 | def test_receive_find_node 64 | node0 = Node.new('0', @kn) 65 | node4 = Node.new('4', @kn) 66 | node5 = Node.new('5', @kn) 67 | node12 = Node.new('12', @kn) 68 | node7 = Node.new('7', @kn) 69 | 70 | node4_contact = node4.to_contact 71 | node5_contact = node5.to_contact 72 | node12_contact = node12.to_contact 73 | 74 | node0.routing_table.insert(node4_contact) 75 | node0.routing_table.insert(node5_contact) 76 | node0.routing_table.insert(node12_contact) 77 | 78 | results = node0.receive_find_node('1', node7.to_contact) 79 | 80 | refute_empty(results) 81 | assert_equal(2, results.size) 82 | assert_includes(results, node4_contact) 83 | assert_includes(results, node5_contact) 84 | end 85 | 86 | def test_receive_find_node_multiple_buckets 87 | # checking to see if results will be taken from multiple buckets 88 | node0 = Node.new('0', @kn) 89 | node15 = Node.new('15', @kn) 90 | node14 = Node.new('14', @kn) 91 | node3 = Node.new('3', @kn) 92 | node7 = Node.new('7', @kn) 93 | 94 | node15_contact = node15.to_contact 95 | node14_contact = node14.to_contact 96 | node3_contact = node3.to_contact 97 | 98 | node0.routing_table.insert(node15_contact) 99 | node0.routing_table.insert(node14_contact) 100 | node0.routing_table.insert(node3_contact) 101 | 102 | results = node0.receive_find_node('1', node7.to_contact) 103 | 104 | refute_empty(results) 105 | assert_equal(2, results.size) 106 | assert_includes(results, node15_contact) 107 | assert_includes(results, node3_contact) 108 | end 109 | 110 | def test_receive_find_node_multiple_buckets_starting_from_back 111 | # checking to see if results will be taken from multiple buckets 112 | node0 = Node.new('0', @kn) 113 | node4 = Node.new('4', @kn) 114 | node5 = Node.new('5', @kn) 115 | node12 = Node.new('12', @kn) 116 | node7 = Node.new('7', @kn) 117 | 118 | node4_contact = node4.to_contact 119 | node5_contact = node5.to_contact 120 | node12_contact = node12.to_contact 121 | 122 | node0.routing_table.insert(node4_contact) 123 | node0.routing_table.insert(node5_contact) 124 | node0.routing_table.insert(node12_contact) 125 | 126 | results = node0.receive_find_node('13', node7.to_contact) 127 | 128 | refute_empty(results) 129 | assert_equal(2, results.size) 130 | assert_includes(results, node12_contact) 131 | assert_includes(results, node4_contact) 132 | end 133 | 134 | def test_receive_find_node_fewer_than_k_results 135 | node0 = Node.new('0', @kn) 136 | node15 = Node.new('15', @kn) 137 | node7 = Node.new('7', @kn) 138 | 139 | node0.routing_table.insert(node15.to_contact) 140 | 141 | results = node0.receive_find_node('1', node7.to_contact) 142 | 143 | refute_empty(results) 144 | assert_equal(1, results.size) 145 | assert_equal('15', results.first.id) 146 | end 147 | 148 | def test_receive_find_node_exclude_requestor 149 | node0 = Node.new('0', @kn) 150 | node15 = Node.new('15', @kn) 151 | node14 = Node.new('14', @kn) 152 | node3 = Node.new('3', @kn) 153 | node7 = Node.new('7', @kn) 154 | 155 | node15_contact = node15.to_contact 156 | node14_contact = node14.to_contact 157 | node3_contact = node3.to_contact 158 | node7_contact = node7.to_contact 159 | 160 | node0.routing_table.insert(node15_contact) 161 | node0.routing_table.insert(node14_contact) 162 | node0.routing_table.insert(node3_contact) 163 | node0.routing_table.insert(node7_contact) 164 | 165 | results = node0.receive_find_node('1', node7_contact) 166 | 167 | refute_includes(results, node7_contact) 168 | assert_equal(2, results.size) 169 | assert_includes(results, node15_contact) 170 | assert_includes(results, node3_contact) 171 | end 172 | 173 | def test_find_node 174 | node7 = Node.new('7', @kn) # the requestor node 175 | node0 = Node.new('0', @kn) # the node that gets the request 176 | node4 = Node.new('4', @kn) 177 | node5 = Node.new('5', @kn) 178 | node12 = Node.new('12', @kn) 179 | 180 | node4_contact = node4.to_contact 181 | node5_contact = node5.to_contact 182 | node12_contact = node12.to_contact 183 | 184 | node0.routing_table.insert(node4_contact) 185 | node0.routing_table.insert(node5_contact) 186 | node0.routing_table.insert(node12_contact) 187 | 188 | results = node7.find_node('1', node0.to_contact) 189 | 190 | refute_empty(results) 191 | assert_equal(2, results.size) 192 | assert_includes(results, node4_contact) 193 | assert_includes(results, node5_contact) 194 | end 195 | 196 | def test_receive_find_value_with_match 197 | # return a address 198 | node0 = Node.new('0', @kn) # node that received request 199 | node0.dht_segment['10'] = ['some_address'] 200 | 201 | node7 = Node.new('7', @kn) # node making the request 202 | 203 | result = node0.receive_find_value('10', node7.to_contact) 204 | 205 | assert_equal('some_address', result['data']) 206 | end 207 | 208 | def test_receive_find_value_with_no_match 209 | # just returns closest nodes 210 | 211 | node0 = Node.new('0', @kn) # node that received request 212 | node0.dht_segment['11'] = 'some_address' 213 | 214 | node4 = Node.new('4', @kn) 215 | node5 = Node.new('5', @kn) 216 | node12 = Node.new('12', @kn) 217 | node13 = Node.new('13', @kn) 218 | node7 = Node.new('7', @kn) 219 | 220 | node4_contact = node4.to_contact 221 | node5_contact = node5.to_contact 222 | node12_contact = node12.to_contact 223 | node13_contact = node13.to_contact 224 | 225 | node0.routing_table.insert(node4_contact) 226 | node0.routing_table.insert(node5_contact) 227 | node0.routing_table.insert(node12_contact) 228 | node0.routing_table.insert(node13_contact) 229 | 230 | results = node0.receive_find_value('10', node7.to_contact) 231 | 232 | refute_empty(results['contacts']) 233 | assert_equal(2, results['contacts'].size) 234 | assert_includes(results['contacts'], node12_contact) 235 | assert_includes(results['contacts'], node13_contact) 236 | end 237 | 238 | def test_find_value_with_match 239 | node0 = Node.new('0', @kn) # node that received request 240 | node0.dht_segment['10'] = ['some_address'] 241 | 242 | node7 = Node.new('7', @kn) # node making the request 243 | 244 | result = node7.find_value('10', node0.to_contact) 245 | 246 | assert_equal('some_address', result['data']) 247 | end 248 | 249 | def test_find_value_with_no_match 250 | node0 = Node.new('0', @kn) # node that received request 251 | node0.dht_segment['11'] = 'some_address' 252 | 253 | node4 = Node.new('4', @kn) 254 | node5 = Node.new('5', @kn) 255 | node12 = Node.new('12', @kn) 256 | node7 = Node.new('7', @kn) 257 | 258 | node4_contact = node4.to_contact 259 | node5_contact = node5.to_contact 260 | node12_contact = node12.to_contact 261 | 262 | node0.routing_table.insert(node4_contact) 263 | node0.routing_table.insert(node5_contact) 264 | node0.routing_table.insert(node12_contact) 265 | 266 | results = node7.find_value('10', node0.to_contact) 267 | 268 | refute_empty(results['contacts']) 269 | assert_equal(2, results['contacts'].size) 270 | assert_includes(results['contacts'], node12_contact) 271 | assert_includes(results['contacts'], node5_contact) 272 | end 273 | 274 | def test_iterative_find_node 275 | node0 = Node.new('0', @kn) 276 | node4 = Node.new('4', @kn) 277 | node5 = Node.new('5', @kn) 278 | node12 = Node.new('12', @kn) 279 | node14 = Node.new('14', @kn) 280 | 281 | node4_contact = node4.to_contact 282 | node5_contact = node5.to_contact 283 | node12_contact = node12.to_contact 284 | node14_contact = node14.to_contact 285 | 286 | node0.routing_table.insert(node4_contact) 287 | node0.routing_table.insert(node5_contact) 288 | node0.routing_table.insert(node12_contact) 289 | 290 | node12.routing_table.insert(node14_contact) 291 | 292 | result = node0.iterative_find_node('15') 293 | assert_instance_of(Array, result) 294 | assert_equal(2, result.size) 295 | assert_includes(result.map(&:id), node14_contact.id) 296 | assert_includes(result.map(&:id), node12_contact.id) 297 | # test that ping adds new contact to our routing table 298 | assert_includes(node0.routing_table.buckets[0].map(&:id), node14_contact.id) 299 | end 300 | 301 | def test_iterative_store 302 | node0 = Node.new('0', @kn) 303 | node4 = Node.new('4', @kn) 304 | node5 = Node.new('5', @kn) 305 | node12 = Node.new('12', @kn) 306 | node14 = Node.new('14', @kn) 307 | 308 | node4_contact = node4.to_contact 309 | node5_contact = node5.to_contact 310 | node12_contact = node12.to_contact 311 | node14_contact = node14.to_contact 312 | 313 | node0.routing_table.insert(node4_contact) 314 | node0.routing_table.insert(node5_contact) 315 | node0.routing_table.insert(node12_contact) 316 | 317 | node12.routing_table.insert(node14_contact) 318 | node0.iterative_store('13', 'some_address') 319 | 320 | assert_equal(['some_address'], node12.dht_segment['13']) 321 | refute(node14.dht_segment['13']) 322 | end 323 | 324 | def test_iterative_find_value_with_match 325 | node0 = Node.new('0', @kn) 326 | node4 = Node.new('4', @kn) 327 | node5 = Node.new('5', @kn) 328 | node12 = Node.new('12', @kn) 329 | node14 = Node.new('14', @kn) 330 | 331 | node4_contact = node4.to_contact 332 | node5_contact = node5.to_contact 333 | node12_contact = node12.to_contact 334 | node14_contact = node14.to_contact 335 | 336 | node0.routing_table.insert(node4_contact) 337 | node0.routing_table.insert(node5_contact) 338 | node0.routing_table.insert(node12_contact) 339 | 340 | node12.routing_table.insert(node14_contact) 341 | node0.store('15', 'some_address', node14_contact) 342 | 343 | result = node0.iterative_find_value('15') 344 | assert_instance_of(String, result) 345 | assert_equal('some_address', result) 346 | # store in second closest node 347 | assert_equal(['some_address'], node12.dht_segment['15']) 348 | end 349 | 350 | def test_iterative_find_value_with_no_match 351 | node0 = Node.new('0', @kn) 352 | node4 = Node.new('4', @kn) 353 | node5 = Node.new('5', @kn) 354 | node12 = Node.new('12', @kn) 355 | node14 = Node.new('14', @kn) 356 | 357 | node4_contact = node4.to_contact 358 | node5_contact = node5.to_contact 359 | node12_contact = node12.to_contact 360 | node14_contact = node14.to_contact 361 | 362 | node0.routing_table.insert(node4_contact) 363 | node0.routing_table.insert(node5_contact) 364 | node0.routing_table.insert(node12_contact) 365 | 366 | node12.routing_table.insert(node14_contact) 367 | node0.store('13', 'some_address', node14_contact) 368 | 369 | result = node0.iterative_find_value('15') 370 | assert_instance_of(Array, result) 371 | assert_equal(2, result.size) 372 | assert_includes(result.map(&:id), node14_contact.id) 373 | assert_includes(result.map(&:id), node12_contact.id) 374 | end 375 | end 376 | -------------------------------------------------------------------------------- /test/node_6bit_test.rb: -------------------------------------------------------------------------------- 1 | require_relative 'test_helper.rb' 2 | require_relative "../lib/node.rb" 3 | require_relative "../lib/routing_table.rb" 4 | require_relative "../lib/fake_network_adapter.rb" 5 | require_relative "../lib/kbucket.rb" 6 | 7 | class NodeTest6 < Minitest::Test 8 | def setup 9 | Defaults::ENVIRONMENT[:bit_length] = 6 10 | Defaults::ENVIRONMENT[:k] = 2 11 | Defaults::ENVIRONMENT[:alpha] = 1 12 | @kn = FakeNetworkAdapter.new 13 | end 14 | 15 | def test_create_node 16 | node = Node.new('0', @kn) 17 | 18 | assert_instance_of(Node, node) 19 | assert_instance_of(RoutingTable, node.routing_table) 20 | end 21 | 22 | def test_join_network 23 | node1 = Node.new('1', @kn) 24 | node2 = Node.new('2', @kn) 25 | 26 | assert_includes(@kn.nodes, node1) 27 | assert_includes(@kn.nodes, node2) 28 | end 29 | 30 | def test_ping_other_node_true_and_false 31 | node0 = Node.new('0', @kn) 32 | node1 = Node.new('1', @kn) 33 | node2 = Node.new('2', @kn) 34 | 35 | assert(node0.ping(node1.to_contact)) 36 | assert(node0.ping(node2.to_contact)) 37 | refute(node0.ping(Contact.new(id: '3', ip: ''))) 38 | end 39 | 40 | def test_ping_dead_node; end 41 | 42 | def test_receive_ping 43 | node0 = Node.new('0', @kn) 44 | node1 = Node.new('1', @kn) 45 | 46 | refute_includes(node0.routing_table.buckets[0].contacts, node1.to_contact) 47 | refute_includes(node1.routing_table.buckets[0].contacts, node0.to_contact) 48 | 49 | node0.ping(node1.to_contact) 50 | 51 | assert_equal(1, node1.routing_table.buckets[0].contacts.size) 52 | assert_equal(1, node0.routing_table.buckets[0].contacts.size) 53 | end 54 | 55 | def test_store 56 | node0 = Node.new('0', @kn) 57 | node1 = Node.new('1', @kn) 58 | 59 | node0.store('key', 'value', node1.to_contact) 60 | assert_equal(['value'], node1.dht_segment['key']) 61 | end 62 | 63 | def test_receive_find_node 64 | node0 = Node.new('0', @kn) 65 | node4 = Node.new('4', @kn) 66 | node5 = Node.new('5', @kn) 67 | node12 = Node.new('12', @kn) 68 | node7 = Node.new('7', @kn) 69 | 70 | node4_contact = node4.to_contact 71 | node5_contact = node5.to_contact 72 | node12_contact = node12.to_contact 73 | 74 | node0.routing_table.insert(node4_contact) 75 | node0.routing_table.insert(node5_contact) 76 | node0.routing_table.insert(node12_contact) 77 | 78 | results = node0.receive_find_node('1', node7.to_contact) 79 | 80 | refute_empty(results) 81 | assert_equal(2, results.size) 82 | assert_includes(results, node4_contact) 83 | assert_includes(results, node5_contact) 84 | end 85 | 86 | def test_receive_find_node_multiple_buckets 87 | # checking to see if results will be taken from multiple buckets 88 | node0 = Node.new('0', @kn) 89 | node15 = Node.new('15', @kn) 90 | node14 = Node.new('14', @kn) 91 | node3 = Node.new('3', @kn) 92 | node7 = Node.new('7', @kn) 93 | 94 | node15_contact = node15.to_contact 95 | node14_contact = node14.to_contact 96 | node3_contact = node3.to_contact 97 | 98 | node0.routing_table.insert(node15_contact) 99 | node0.routing_table.insert(node14_contact) 100 | node0.routing_table.insert(node3_contact) 101 | 102 | results = node0.receive_find_node('1', node7.to_contact) 103 | 104 | refute_empty(results) 105 | assert_equal(2, results.size) 106 | assert_includes(results, node15_contact) 107 | assert_includes(results, node3_contact) 108 | end 109 | 110 | def test_receive_find_node_multiple_buckets_starting_from_back 111 | # checking to see if results will be taken from multiple buckets 112 | node0 = Node.new('0', @kn) 113 | node4 = Node.new('4', @kn) 114 | node5 = Node.new('5', @kn) 115 | node12 = Node.new('12', @kn) 116 | node7 = Node.new('7', @kn) 117 | 118 | node4_contact = node4.to_contact 119 | node5_contact = node5.to_contact 120 | node12_contact = node12.to_contact 121 | 122 | node0.routing_table.insert(node4_contact) 123 | node0.routing_table.insert(node5_contact) 124 | node0.routing_table.insert(node12_contact) 125 | 126 | results = node0.receive_find_node('13', node7.to_contact) 127 | 128 | refute_empty(results) 129 | assert_equal(2, results.size) 130 | assert_includes(results, node12_contact) 131 | assert_includes(results, node4_contact) 132 | end 133 | 134 | def test_receive_find_node_fewer_than_k_results 135 | node0 = Node.new('0', @kn) 136 | node15 = Node.new('15', @kn) 137 | node7 = Node.new('7', @kn) 138 | 139 | node0.routing_table.insert(node15.to_contact) 140 | 141 | results = node0.receive_find_node('1', node7.to_contact) 142 | 143 | refute_empty(results) 144 | assert_equal(1, results.size) 145 | assert_equal('15', results.first.id) 146 | end 147 | 148 | def test_receive_find_node_exclude_requestor 149 | node0 = Node.new('0', @kn) 150 | node15 = Node.new('15', @kn) 151 | node14 = Node.new('14', @kn) 152 | node3 = Node.new('3', @kn) 153 | node7 = Node.new('7', @kn) 154 | 155 | node15_contact = node15.to_contact 156 | node14_contact = node14.to_contact 157 | node3_contact = node3.to_contact 158 | node7_contact = node7.to_contact 159 | 160 | node0.routing_table.insert(node15_contact) 161 | node0.routing_table.insert(node14_contact) 162 | node0.routing_table.insert(node3_contact) 163 | node0.routing_table.insert(node7_contact) 164 | 165 | results = node0.receive_find_node('1', node7_contact) 166 | 167 | refute_includes(results, node7_contact) 168 | assert_equal(2, results.size) 169 | assert_includes(results, node15_contact) 170 | assert_includes(results, node3_contact) 171 | end 172 | 173 | def test_find_node 174 | node7 = Node.new('7', @kn) # the requestor node 175 | node0 = Node.new('0', @kn) # the node that gets the request 176 | node4 = Node.new('4', @kn) 177 | node5 = Node.new('5', @kn) 178 | node12 = Node.new('12', @kn) 179 | 180 | node4_contact = node4.to_contact 181 | node5_contact = node5.to_contact 182 | node12_contact = node12.to_contact 183 | 184 | node0.routing_table.insert(node4_contact) 185 | node0.routing_table.insert(node5_contact) 186 | node0.routing_table.insert(node12_contact) 187 | 188 | results = node7.find_node('1', node0.to_contact) 189 | 190 | refute_empty(results) 191 | assert_equal(2, results.size) 192 | assert_includes(results, node4_contact) 193 | assert_includes(results, node5_contact) 194 | end 195 | 196 | def test_receive_find_value_with_match 197 | # return a address 198 | node0 = Node.new('0', @kn) # node that received request 199 | node0.dht_segment['10'] = ['some_address'] 200 | 201 | node7 = Node.new('7', @kn) # node making the request 202 | 203 | result = node0.receive_find_value('10', node7.to_contact) 204 | 205 | assert_equal('some_address', result['data']) 206 | end 207 | 208 | def test_receive_find_value_with_no_match 209 | # just returns closest nodes 210 | 211 | node0 = Node.new('0', @kn) # node that received request 212 | node0.dht_segment['11'] = ['some_address'] 213 | 214 | node4 = Node.new('4', @kn) 215 | node5 = Node.new('5', @kn) 216 | node12 = Node.new('12', @kn) 217 | node13 = Node.new('13', @kn) 218 | node7 = Node.new('7', @kn) 219 | 220 | node4_contact = node4.to_contact 221 | node5_contact = node5.to_contact 222 | node12_contact = node12.to_contact 223 | node13_contact = node13.to_contact 224 | 225 | node0.routing_table.insert(node4_contact) 226 | node0.routing_table.insert(node5_contact) 227 | node0.routing_table.insert(node12_contact) 228 | node0.routing_table.insert(node13_contact) 229 | 230 | results = node0.receive_find_value('10', node7.to_contact) 231 | 232 | refute_empty(results['contacts']) 233 | assert_equal(2, results['contacts'].size) 234 | assert_includes(results['contacts'], node12_contact) 235 | assert_includes(results['contacts'], node13_contact) 236 | end 237 | 238 | def test_find_value_with_match 239 | node0 = Node.new('0', @kn) # node that received request 240 | node0.dht_segment['10'] = ['some_address'] 241 | 242 | node7 = Node.new('7', @kn) # node making the request 243 | 244 | result = node7.find_value('10', node0.to_contact) 245 | 246 | assert_equal('some_address', result['data']) 247 | end 248 | 249 | def test_find_value_with_no_match 250 | node0 = Node.new('0', @kn) # node that received request 251 | node0.dht_segment['11'] = ['some_address'] 252 | 253 | node4 = Node.new('4', @kn) 254 | node5 = Node.new('5', @kn) 255 | node12 = Node.new('12', @kn) 256 | node7 = Node.new('7', @kn) 257 | 258 | node4_contact = node4.to_contact 259 | node5_contact = node5.to_contact 260 | node12_contact = node12.to_contact 261 | 262 | node0.routing_table.insert(node4_contact) 263 | node0.routing_table.insert(node5_contact) 264 | node0.routing_table.insert(node12_contact) 265 | 266 | results = node7.find_value('10', node0.to_contact) 267 | 268 | refute_empty(results['contacts']) 269 | assert_equal(2, results['contacts'].size) 270 | assert_includes(results['contacts'], node12_contact) 271 | assert_includes(results['contacts'], node5_contact) 272 | end 273 | 274 | def test_iterative_find_node 275 | node0 = Node.new('0', @kn) 276 | node4 = Node.new('4', @kn) 277 | node5 = Node.new('5', @kn) 278 | node12 = Node.new('12', @kn) 279 | node14 = Node.new('14', @kn) 280 | 281 | node4_contact = node4.to_contact 282 | node5_contact = node5.to_contact 283 | node12_contact = node12.to_contact 284 | node14_contact = node14.to_contact 285 | 286 | node0.routing_table.insert(node4_contact) 287 | node0.routing_table.insert(node5_contact) 288 | node0.routing_table.insert(node12_contact) 289 | 290 | node12.routing_table.insert(node14_contact) 291 | 292 | result = node0.iterative_find_node('15') 293 | assert_instance_of(Array, result) 294 | assert_equal(2, result.size) 295 | ids = result.map(&:id) 296 | assert_includes(ids, node14_contact.id) 297 | assert_includes(ids, node12_contact.id) 298 | # test that ping adds new contact to our routing table 299 | assert_includes(node0.routing_table.buckets[2].map(&:id), node14_contact.id) 300 | end 301 | 302 | def test_iterative_store 303 | node0 = Node.new('0', @kn) 304 | node4 = Node.new('4', @kn) 305 | node5 = Node.new('5', @kn) 306 | node12 = Node.new('12', @kn) 307 | node14 = Node.new('14', @kn) 308 | 309 | node4_contact = node4.to_contact 310 | node5_contact = node5.to_contact 311 | node12_contact = node12.to_contact 312 | node14_contact = node14.to_contact 313 | 314 | node0.routing_table.insert(node4_contact) 315 | node0.routing_table.insert(node5_contact) 316 | node0.routing_table.insert(node12_contact) 317 | 318 | node12.routing_table.insert(node14_contact) 319 | node0.iterative_store('13', 'some_address') 320 | 321 | assert_equal(['some_address'], node12.dht_segment['13']) 322 | refute(node14.dht_segment['13']) 323 | end 324 | 325 | def test_iterative_find_value_with_match 326 | node0 = Node.new('0', @kn) 327 | node4 = Node.new('4', @kn) 328 | node5 = Node.new('5', @kn) 329 | node12 = Node.new('12', @kn) 330 | node14 = Node.new('14', @kn) 331 | 332 | node4_contact = node4.to_contact 333 | node5_contact = node5.to_contact 334 | node12_contact = node12.to_contact 335 | node14_contact = node14.to_contact 336 | 337 | node0.routing_table.insert(node4_contact) 338 | node0.routing_table.insert(node5_contact) 339 | node0.routing_table.insert(node12_contact) 340 | 341 | node12.routing_table.insert(node14_contact) 342 | node0.store('15', 'some_address', node14_contact) 343 | 344 | result = node0.iterative_find_value('15') 345 | assert_instance_of(String, result) 346 | assert_equal('some_address', result) 347 | # store in second closest node 348 | assert_equal(['some_address'], node12.dht_segment['15']) 349 | end 350 | 351 | def test_iterative_find_value_with_no_match 352 | node0 = Node.new('0', @kn) 353 | node4 = Node.new('4', @kn) 354 | node5 = Node.new('5', @kn) 355 | node12 = Node.new('12', @kn) 356 | node14 = Node.new('14', @kn) 357 | 358 | node4_contact = node4.to_contact 359 | node5_contact = node5.to_contact 360 | node12_contact = node12.to_contact 361 | node14_contact = node14.to_contact 362 | 363 | node0.routing_table.insert(node4_contact) 364 | node0.routing_table.insert(node5_contact) 365 | node0.routing_table.insert(node12_contact) 366 | 367 | node12.routing_table.insert(node14_contact) 368 | node0.store('13', 'some_address', node14_contact) 369 | 370 | result = node0.iterative_find_value('15') 371 | assert_instance_of(Array, result) 372 | assert_equal(2, result.size) 373 | ids = result.map(&:id) 374 | assert_includes(ids, node14_contact.id) 375 | assert_includes(ids, node12_contact.id) 376 | end 377 | end 378 | -------------------------------------------------------------------------------- /test/node_160bit_test.rb: -------------------------------------------------------------------------------- 1 | require_relative 'test_helper.rb' 2 | require_relative "../lib/node.rb" 3 | require_relative "../lib/routing_table.rb" 4 | require_relative "../lib/fake_network_adapter.rb" 5 | require_relative "../lib/kbucket.rb" 6 | 7 | class NodeTest160 < Minitest::Test 8 | def setup 9 | Defaults::ENVIRONMENT[:bit_length] = 160 10 | Defaults::ENVIRONMENT[:k] = 2 11 | Defaults::ENVIRONMENT[:alpha] = 1 12 | @kn = FakeNetworkAdapter.new 13 | end 14 | 15 | def test_create_node 16 | node = Node.new('0', @kn) 17 | 18 | assert_instance_of(Node, node) 19 | assert_instance_of(RoutingTable, node.routing_table) 20 | end 21 | 22 | def test_join_network 23 | node1 = Node.new('1', @kn) 24 | node2 = Node.new('2', @kn) 25 | 26 | assert_includes(@kn.nodes, node1) 27 | assert_includes(@kn.nodes, node2) 28 | end 29 | 30 | def test_ping_other_node_true_and_false 31 | node0 = Node.new('0', @kn) 32 | node1 = Node.new('1', @kn) 33 | node2 = Node.new('2', @kn) 34 | 35 | assert(node0.ping(node1.to_contact)) 36 | assert(node0.ping(node2.to_contact)) 37 | refute(node0.ping(Contact.new(id: '3', ip: ''))) 38 | end 39 | 40 | def test_receive_ping 41 | node0 = Node.new('0', @kn) 42 | node1 = Node.new('1', @kn) 43 | 44 | refute_includes(node0.routing_table.buckets[0].contacts, node1.to_contact) 45 | refute_includes(node1.routing_table.buckets[0].contacts, node0.to_contact) 46 | 47 | node0.ping(node1.to_contact) 48 | 49 | assert_equal(1, node1.routing_table.buckets[0].contacts.size) 50 | assert_equal(1, node0.routing_table.buckets[0].contacts.size) 51 | end 52 | 53 | def test_store 54 | node0 = Node.new('0', @kn) 55 | node1 = Node.new('1', @kn) 56 | 57 | node0.store('key', 'value', node1.to_contact) 58 | assert_equal(['value'], node1.dht_segment['key']) 59 | end 60 | 61 | def test_receive_find_node 62 | node0 = Node.new('0', @kn) 63 | node4 = Node.new('4', @kn) 64 | node5 = Node.new('5', @kn) 65 | node12 = Node.new('12', @kn) 66 | node7 = Node.new('7', @kn) 67 | 68 | node4_contact = node4.to_contact 69 | node5_contact = node5.to_contact 70 | node12_contact = node12.to_contact 71 | 72 | node0.routing_table.insert(node4_contact) 73 | node0.routing_table.insert(node5_contact) 74 | node0.routing_table.insert(node12_contact) 75 | 76 | results = node0.receive_find_node('1', node7.to_contact) 77 | 78 | refute_empty(results) 79 | assert_equal(2, results.size) 80 | assert_includes(results, node4_contact) 81 | assert_includes(results, node5_contact) 82 | end 83 | 84 | def test_receive_find_node_multiple_buckets 85 | # checking to see if results will be taken from multiple buckets 86 | node0 = Node.new('0', @kn) 87 | node15 = Node.new('15', @kn) 88 | node14 = Node.new('14', @kn) 89 | node3 = Node.new('3', @kn) 90 | node7 = Node.new('7', @kn) 91 | 92 | node15_contact = node15.to_contact 93 | node14_contact = node14.to_contact 94 | node3_contact = node3.to_contact 95 | 96 | node0.routing_table.insert(node15_contact) 97 | node0.routing_table.insert(node14_contact) 98 | node0.routing_table.insert(node3_contact) 99 | 100 | results = node0.receive_find_node('1', node7.to_contact) 101 | 102 | refute_empty(results) 103 | assert_equal(2, results.size) 104 | assert_includes(results, node15_contact) 105 | assert_includes(results, node3_contact) 106 | end 107 | 108 | def test_receive_find_node_multiple_buckets_starting_from_back 109 | # checking to see if results will be taken from multiple buckets 110 | node0 = Node.new('0', @kn) 111 | node4 = Node.new('4', @kn) 112 | node5 = Node.new('5', @kn) 113 | node12 = Node.new('12', @kn) 114 | node7 = Node.new('7', @kn) 115 | 116 | node4_contact = node4.to_contact 117 | node5_contact = node5.to_contact 118 | node12_contact = node12.to_contact 119 | 120 | node0.routing_table.insert(node4_contact) 121 | node0.routing_table.insert(node5_contact) 122 | node0.routing_table.insert(node12_contact) 123 | 124 | results = node0.receive_find_node('13', node7.to_contact) 125 | 126 | refute_empty(results) 127 | assert_equal(2, results.size) 128 | assert_includes(results, node12_contact) 129 | assert_includes(results, node4_contact) 130 | end 131 | 132 | def test_receive_find_node_fewer_than_k_results 133 | node0 = Node.new('0', @kn) 134 | node15 = Node.new('15', @kn) 135 | node7 = Node.new('7', @kn) 136 | 137 | node0.routing_table.insert(node15.to_contact) 138 | 139 | results = node0.receive_find_node('1', node7.to_contact) 140 | 141 | refute_empty(results) 142 | assert_equal(1, results.size) 143 | assert_equal('15', results.first.id) 144 | end 145 | 146 | def test_receive_find_node_exclude_requestor 147 | node0 = Node.new('0', @kn) 148 | node15 = Node.new('15', @kn) 149 | node14 = Node.new('14', @kn) 150 | node3 = Node.new('3', @kn) 151 | node7 = Node.new('7', @kn) 152 | 153 | node15_contact = node15.to_contact 154 | node14_contact = node14.to_contact 155 | node3_contact = node3.to_contact 156 | node7_contact = node7.to_contact 157 | 158 | node0.routing_table.insert(node15_contact) 159 | node0.routing_table.insert(node14_contact) 160 | node0.routing_table.insert(node3_contact) 161 | node0.routing_table.insert(node7_contact) 162 | 163 | results = node0.receive_find_node('1', node7_contact) 164 | 165 | refute_includes(results, node7_contact) 166 | assert_equal(2, results.size) 167 | assert_includes(results, node15_contact) 168 | assert_includes(results, node3_contact) 169 | end 170 | 171 | def test_find_node 172 | node7 = Node.new('7', @kn) # the requestor node 173 | node0 = Node.new('0', @kn) # the node that gets the request 174 | node4 = Node.new('4', @kn) 175 | node5 = Node.new('5', @kn) 176 | node12 = Node.new('12', @kn) 177 | 178 | node4_contact = node4.to_contact 179 | node5_contact = node5.to_contact 180 | node12_contact = node12.to_contact 181 | 182 | node0.routing_table.insert(node4_contact) 183 | node0.routing_table.insert(node5_contact) 184 | node0.routing_table.insert(node12_contact) 185 | 186 | results = node7.find_node('1', node0.to_contact) 187 | 188 | refute_empty(results) 189 | assert_equal(2, results.size) 190 | assert_includes(results, node4_contact) 191 | assert_includes(results, node5_contact) 192 | end 193 | 194 | def test_receive_find_value_with_match 195 | # return a address 196 | node0 = Node.new('0', @kn) # node that received request 197 | node0.dht_segment['10'] = ['some_address'] 198 | 199 | node7 = Node.new('7', @kn) # node making the request 200 | 201 | result = node0.receive_find_value('10', node7.to_contact) 202 | 203 | assert_equal('some_address', result['data']) 204 | end 205 | 206 | def test_receive_find_value_with_no_match 207 | # just returns closest nodes 208 | 209 | node0 = Node.new('0', @kn) # node that received request 210 | node0.dht_segment['11'] = ['some_address'] 211 | 212 | node4 = Node.new('4', @kn) 213 | node5 = Node.new('5', @kn) 214 | node12 = Node.new('12', @kn) 215 | node13 = Node.new('13', @kn) 216 | node7 = Node.new('7', @kn) 217 | 218 | node4_contact = node4.to_contact 219 | node5_contact = node5.to_contact 220 | node12_contact = node12.to_contact 221 | node13_contact = node13.to_contact 222 | 223 | node0.routing_table.insert(node4_contact) 224 | node0.routing_table.insert(node5_contact) 225 | node0.routing_table.insert(node12_contact) 226 | node0.routing_table.insert(node13_contact) 227 | 228 | results = node0.receive_find_value('10', node7.to_contact) 229 | 230 | refute_empty(results['contacts']) 231 | assert_equal(2, results['contacts'].size) 232 | assert_includes(results['contacts'], node12_contact) 233 | assert_includes(results['contacts'], node13_contact) 234 | end 235 | 236 | def test_find_value_with_match 237 | node0 = Node.new('0', @kn) # node that received request 238 | node0.dht_segment['10'] = ['some_address'] 239 | 240 | node7 = Node.new('7', @kn) # node making the request 241 | 242 | result = node7.find_value('10', node0.to_contact) 243 | 244 | assert_equal('some_address', result['data']) 245 | end 246 | 247 | def test_find_value_with_no_match 248 | node0 = Node.new('0', @kn) # node that received request 249 | node0.dht_segment['11'] = ['some_address'] 250 | 251 | node4 = Node.new('4', @kn) 252 | node5 = Node.new('5', @kn) 253 | node12 = Node.new('12', @kn) 254 | node7 = Node.new('7', @kn) 255 | 256 | node4_contact = node4.to_contact 257 | node5_contact = node5.to_contact 258 | node12_contact = node12.to_contact 259 | 260 | node0.routing_table.insert(node4_contact) 261 | node0.routing_table.insert(node5_contact) 262 | node0.routing_table.insert(node12_contact) 263 | 264 | results = node7.find_value('10', node0.to_contact) 265 | 266 | refute_empty(results['contacts']) 267 | assert_equal(2, results['contacts'].size) 268 | assert_includes(results['contacts'], node12_contact) 269 | assert_includes(results['contacts'], node5_contact) 270 | end 271 | 272 | def test_iterative_find_node 273 | node0 = Node.new('0', @kn) 274 | node4 = Node.new('4', @kn) 275 | node5 = Node.new('5', @kn) 276 | node12 = Node.new('12', @kn) 277 | node14 = Node.new('14', @kn) 278 | 279 | node4_contact = node4.to_contact 280 | node5_contact = node5.to_contact 281 | node12_contact = node12.to_contact 282 | node14_contact = node14.to_contact 283 | 284 | node0.routing_table.insert(node4_contact) 285 | node0.routing_table.insert(node5_contact) 286 | node0.routing_table.insert(node12_contact) 287 | 288 | node12.routing_table.insert(node14_contact) 289 | 290 | result = node0.iterative_find_node('15') 291 | assert_instance_of(Array, result) 292 | assert_equal(2, result.size) 293 | assert_includes(result.map(&:id), node14_contact.id) 294 | assert_includes(result.map(&:id), node12_contact.id) 295 | # test that ping adds new contact to our routing table 296 | bucket_index_of_node14 = Binary.shared_prefix_bit_length('0', '14') 297 | assert_includes(node0.routing_table.buckets[bucket_index_of_node14].map(&:id), node14_contact.id) 298 | end 299 | 300 | def test_iterative_store 301 | node0 = Node.new('0', @kn) 302 | node4 = Node.new('4', @kn) 303 | node5 = Node.new('5', @kn) 304 | node12 = Node.new('12', @kn) 305 | node14 = Node.new('14', @kn) 306 | 307 | node4_contact = node4.to_contact 308 | node5_contact = node5.to_contact 309 | node12_contact = node12.to_contact 310 | node14_contact = node14.to_contact 311 | 312 | node0.routing_table.insert(node4_contact) 313 | node0.routing_table.insert(node5_contact) 314 | node0.routing_table.insert(node12_contact) 315 | 316 | node12.routing_table.insert(node14_contact) 317 | node0.iterative_store('13', 'some_address') 318 | 319 | assert_equal(['some_address'], node12.dht_segment['13']) 320 | refute(node14.dht_segment['13']) 321 | end 322 | 323 | def test_iterative_find_value_with_match 324 | node0 = Node.new('0', @kn) 325 | node4 = Node.new('4', @kn) 326 | node5 = Node.new('5', @kn) 327 | node12 = Node.new('12', @kn) 328 | node14 = Node.new('14', @kn) 329 | 330 | node4_contact = node4.to_contact 331 | node5_contact = node5.to_contact 332 | node12_contact = node12.to_contact 333 | node14_contact = node14.to_contact 334 | 335 | node0.routing_table.insert(node4_contact) 336 | node0.routing_table.insert(node5_contact) 337 | node0.routing_table.insert(node12_contact) 338 | 339 | node12.routing_table.insert(node14_contact) 340 | node0.store('15', 'some_address', node14_contact) 341 | 342 | result = node0.iterative_find_value('15') 343 | assert_instance_of(String, result) 344 | assert_equal('some_address', result) 345 | # store in second closest node 346 | assert_equal(['some_address'], node12.dht_segment['15']) 347 | end 348 | 349 | def test_iterative_find_value_with_no_match 350 | node0 = Node.new('0', @kn) 351 | node4 = Node.new('4', @kn) 352 | node5 = Node.new('5', @kn) 353 | node12 = Node.new('12', @kn) 354 | node14 = Node.new('14', @kn) 355 | 356 | node4_contact = node4.to_contact 357 | node5_contact = node5.to_contact 358 | node12_contact = node12.to_contact 359 | node14_contact = node14.to_contact 360 | 361 | node0.routing_table.insert(node4_contact) 362 | node0.routing_table.insert(node5_contact) 363 | node0.routing_table.insert(node12_contact) 364 | 365 | node12.routing_table.insert(node14_contact) 366 | node0.store('13', 'some_address', node14_contact) 367 | 368 | result = node0.iterative_find_value('15') 369 | assert_instance_of(Array, result) 370 | assert_equal(2, result.size) 371 | assert_includes(result.map(&:id), node14_contact.id) 372 | assert_includes(result.map(&:id), node12_contact.id) 373 | end 374 | end 375 | -------------------------------------------------------------------------------- /lib/node.rb: -------------------------------------------------------------------------------- 1 | require 'open-uri' 2 | require 'digest/sha1' 3 | require 'socket' 4 | require 'ngrok/tunnel' 5 | require 'json' 6 | require_relative 'binary.rb' 7 | require_relative 'routing_table.rb' 8 | require_relative 'contact.rb' 9 | require_relative 'network_adapter.rb' 10 | require_relative 'storage.rb' 11 | 12 | class Node 13 | attr_reader :ip, :port, :id, :files, :routing_table, :dht_segment, :is_super, :superport, :manifests, :shards 14 | def initialize(num_string, network, port='80') 15 | @port = port 16 | @network = network 17 | join(@network) 18 | @id = num_string 19 | @routing_table = RoutingTable.new(self) 20 | generate_file_cache 21 | generate_manifest_cache 22 | generate_shard_cache 23 | @dht_segment = {} 24 | @is_super = false 25 | @superport = nil 26 | end 27 | 28 | def set_super 29 | @is_super = ENV['SUPER'] == 'true' 30 | end 31 | 32 | def activate(port) 33 | set_ip(port) 34 | set_leech 35 | @superport = ENV['SUPERPORT'] 36 | return if is_super 37 | @super_ip = ENV['SUPERIP'] || @ip 38 | result = JSON.parse(@network.info(@super_ip, @superport)) 39 | contact = Contact.new(id: result['id'], ip: result['ip'], port: result['port']) 40 | ping(contact) 41 | Thread.new do 42 | iterative_find_node(@id) 43 | broadcast 44 | end 45 | end 46 | 47 | def broadcast 48 | return if @leech 49 | [@manifests, @shards].each do |hash| 50 | hash.each do |k, v| 51 | iterative_store(k, file_url(v)) 52 | end 53 | end 54 | end 55 | 56 | def generate_file_cache 57 | @files = {} 58 | 59 | Dir.glob(File.expand_path(Defaults::ENVIRONMENT[:files] + '/*')).select { |f| File.file?(f) }.each do |file| 60 | file_hash = generate_file_id(File.read(file)) 61 | @files[file_hash] = '/files/' + File.basename(file) 62 | end 63 | end 64 | 65 | def get(url) 66 | file = @network.get(url) 67 | 68 | if file 69 | if File.extname(url) == '.xro' 70 | add_manifest(file.body, File.basename(url, File.extname(url))) 71 | compile_shards(file.body, File.basename(url)) 72 | else 73 | add_shard(File.basename(url), file.body) 74 | end 75 | end 76 | end 77 | 78 | def to_contact 79 | Contact.new(id: id, ip: ip, port: port) 80 | end 81 | 82 | def receive_ping(contact) 83 | @routing_table.insert(contact) 84 | end 85 | 86 | def ping(contact) 87 | response = @network.ping(contact, to_contact) 88 | @routing_table.insert(contact) if response 89 | response 90 | end 91 | 92 | def store(file_id, address, recipient_contact) 93 | response = @network.store(file_id, address, recipient_contact, to_contact) 94 | @routing_table.insert(recipient_contact) if response && response.code == 200 95 | end 96 | 97 | def receive_store(file_id, address, sender_contact) 98 | if @dht_segment[file_id] 99 | @dht_segment[file_id].push(address) unless @dht_segment[file_id].include?(address) 100 | else 101 | @dht_segment[file_id] = [address] 102 | end 103 | 104 | @routing_table.insert(sender_contact) 105 | end 106 | 107 | def iterative_store(file_id, address) 108 | return if @leech 109 | results = iterative_find_node(file_id) 110 | 111 | results.each do |contact| 112 | store(file_id, address, contact) 113 | end 114 | end 115 | 116 | def receive_find_node(query_id, sender_contact) 117 | closest_contacts = @routing_table.find_closest_contacts(query_id, sender_contact) 118 | @routing_table.insert(sender_contact) 119 | closest_contacts 120 | end 121 | 122 | def find_node(query_id, recipient_contact) 123 | results = @network.find_node(query_id, recipient_contact, to_contact) 124 | results.each do |r| 125 | @routing_table.insert(r) 126 | end 127 | results 128 | end 129 | 130 | def iterative_find_node(query_id) 131 | shortlist = [] 132 | results = @routing_table.find_closest_contacts(query_id, nil, Defaults::ENVIRONMENT[:alpha]) 133 | 134 | until k_elements?(shortlist.select(&:active)) 135 | fill_shortlist(shortlist, results) 136 | closest_contact = Binary.select_closest_xor(query_id, shortlist) 137 | 138 | # once we get past happy path, we only iterate over items not yet probed 139 | shortlist.each do |contact| 140 | temp_results = find_node(query_id, contact) 141 | ingest_contacts(temp_results, results, shortlist) 142 | contact.activate 143 | end 144 | 145 | break if results.empty? || closest_contact.nil? || no_closer_contacts(query_id, results, closest_contact) 146 | end 147 | shortlist 148 | end 149 | 150 | def receive_find_value(file_id, sender_contact) 151 | result = {} 152 | 153 | if dht_segment[file_id] && !dht_segment[file_id].empty? 154 | result['data'] = select_address(file_id) 155 | else 156 | result['contacts'] = receive_find_node(file_id, sender_contact) 157 | end 158 | @routing_table.insert(sender_contact) 159 | result 160 | end 161 | 162 | def select_address(file_id) 163 | values = dht_segment[file_id].clone.shuffle 164 | 165 | values.each do |address| 166 | response = @network.check_resource_status(address) 167 | if response == 200 168 | return address 169 | else 170 | evict_address(file_id, address) 171 | end 172 | end 173 | nil 174 | end 175 | 176 | def find_value(file_id, recipient_contact) 177 | results = @network.find_value(file_id, recipient_contact, to_contact) 178 | 179 | if results['contacts'] 180 | results['contacts'].each do |r| 181 | @routing_table.insert(r) 182 | end 183 | end 184 | results 185 | end 186 | 187 | def iterative_find_value(query_id) 188 | shortlist = [] 189 | results = @routing_table.find_closest_contacts(query_id, nil, Defaults::ENVIRONMENT[:alpha]) 190 | 191 | until k_elements?(shortlist.select(&:active)) 192 | fill_shortlist(shortlist, results) 193 | Binary.sort_by_xor!(id, shortlist) 194 | closest_contact = shortlist[0] 195 | 196 | # once we get past happy path, we only iterate over items not yet probed 197 | shortlist.each do |contact| 198 | temp_results = find_value(query_id, contact) 199 | if temp_results['data'] 200 | store_at_second_closest(shortlist, contact, query_id, temp_results['data']) 201 | return temp_results['data'] 202 | elsif temp_results['contacts'] 203 | ingest_contacts(temp_results['contacts'], results, shortlist) 204 | end 205 | contact.activate 206 | end 207 | 208 | break if results.empty? || no_closer_contacts(query_id, results, closest_contact) 209 | end 210 | shortlist 211 | end 212 | 213 | def sync 214 | Storage.write_to_disk(self) 215 | end 216 | 217 | def buckets_refresh 218 | routing_table.buckets.each do |bucket| 219 | all_contacts = bucket.contacts 220 | size = all_contacts.size 221 | if size > 0 222 | contact_id = all_contacts[rand(size)].id 223 | iterative_find_node(contact_id) 224 | end 225 | end 226 | end 227 | 228 | def save_file(name, content) 229 | file_id = generate_file_id(content) 230 | file_name = Defaults::ENVIRONMENT[:files] + '/' + name 231 | write_to_subfolder(Defaults::ENVIRONMENT[:files], name, content) 232 | add_to_cache(@files, file_id, '/files/' + name) 233 | Thread.new { shard_file(file_name, file_id) } 234 | end 235 | 236 | private 237 | 238 | def add_manifest(obj, file_id) 239 | file_name = file_id + '.xro' 240 | file_path = '/manifests/' + file_name 241 | 242 | write_to_subfolder(Defaults::ENVIRONMENT[:manifests], file_name, obj) 243 | add_to_cache(@manifests, file_id, file_path) 244 | iterative_store(file_id, file_url(file_path)) 245 | end 246 | 247 | def add_shard(name, data) 248 | file_path = '/shards/' + name 249 | 250 | write_to_subfolder(Defaults::ENVIRONMENT[:shards], name, data) 251 | add_to_cache(@shards, name, file_path) 252 | iterative_store(name, file_url(file_path)) 253 | end 254 | 255 | def add_to_cache(cache, key, value) 256 | cache[key] = value 257 | end 258 | 259 | def create_manifest(file_name, file_size) 260 | { file_name: file_name, length: file_size, pieces: [] } 261 | end 262 | 263 | def k_elements?(array) 264 | array.size == Defaults::ENVIRONMENT[:k] 265 | end 266 | 267 | def ingest_contacts(source, dest, shortlist) 268 | source.each do |c| 269 | dest.push(c) if new_contact?(c, dest, shortlist) 270 | end 271 | end 272 | 273 | def new_contact?(contact, array1, array2) 274 | !array1.find { |obj| obj.id == contact.id } && !array2.find { |obj| obj.id == contact.id } 275 | end 276 | 277 | def evict_address(file_id, address) 278 | dht_segment[file_id].delete(address) 279 | end 280 | 281 | def file_url(filepath) 282 | "http://#{@ip}:#{@port}#{filepath}" 283 | end 284 | 285 | def fill_shortlist(shortlist, source) 286 | shortlist.push(source.pop.clone) until source.empty? || k_elements?(shortlist) 287 | end 288 | 289 | def generate_file_id(file_content) 290 | Binary.sha(file_content).hex.to_s 291 | end 292 | 293 | def generate_manifest_cache 294 | @manifests = {} 295 | 296 | Dir.glob(File.expand_path(Defaults::ENVIRONMENT[:manifests] + '/*')).select { |f| File.file?(f) }.each do |file| 297 | file_hash = File.basename(file, ".xro") 298 | @manifests[file_hash] = '/manifests/' + File.basename(file) 299 | end 300 | end 301 | 302 | def generate_shard_cache 303 | @shards = {} 304 | 305 | Dir.glob(File.expand_path(Defaults::ENVIRONMENT[:shards] + '/*')).select { |f| File.file?(f) }.each do |file| 306 | file_hash = File.basename(file) 307 | @shards[file_hash] = '/shards/' + File.basename(file) 308 | end 309 | end 310 | 311 | def join(network) 312 | network.nodes.push(self) 313 | end 314 | 315 | def set_ip(port) 316 | @ip = lookup_ip(port) 317 | end 318 | 319 | def set_leech 320 | @leech = ENV["LEECH"] == 'true' 321 | end 322 | 323 | def lookup_ip(port) 324 | if ENV['FQDN'] 325 | ENV['FQDN'] 326 | elsif ENV['NAT'] == 'true' 327 | ngrok = initialize_ngrok(port) 328 | File.basename(ngrok) 329 | else 330 | private_ip = Socket.ip_address_list.detect(&:ipv4_private?) 331 | private_ip ? private_ip.ip_address : 'localhost' 332 | end 333 | end 334 | 335 | def initialize_ngrok(port) 336 | authfile = ENV['HOME'] + "/.ngrok2/ngrok.yml" 337 | if File.exist?(authfile) 338 | authtoken = authfile["authtoken"] 339 | ngrok = Ngrok::Tunnel.start(port: port, authtoken: authtoken) 340 | else 341 | ngrok = Ngrok::Tunnel.start(port: port) 342 | end 343 | end 344 | 345 | def no_closer_contacts(query_id, results, closest_contact) 346 | Binary.xor_distance_map(query_id, results).min >= Binary.xor_distance(closest_contact.id, query_id) 347 | end 348 | 349 | def reassemble_shards(shards, manifest) 350 | shard_paths = shards.map do |shard| 351 | Defaults::ENVIRONMENT[:shards] + '/' + shard 352 | end 353 | 354 | File.open(Defaults::ENVIRONMENT[:files] + '/' + manifest['file_name'], 'a') do |f| 355 | shard_paths.each do |path| 356 | f.write(File.read(path)) 357 | end 358 | end 359 | end 360 | 361 | def compile_shards(manifest_body, manifest_name) 362 | manifest = JSON.parse(manifest_body) 363 | shards_ids = manifest['pieces'] 364 | shard_count = shards_ids.length 365 | 366 | shards_ids.each do |shard_id| 367 | fetch_shard(shard_id) unless shard_exist?(shard_id) 368 | shard_count -= 1 if valid_shard?(shard_id) 369 | end 370 | 371 | if shard_count.zero? 372 | reassemble_shards(shards_ids, manifest) 373 | add_to_cache(@files, File.basename(manifest_name, '.xro'), '/files/' + manifest['file_name']) 374 | end 375 | sync 376 | end 377 | 378 | def shard_exist?(shard) 379 | File.exist?(Defaults::ENVIRONMENT[:shards] + '/' + shard) 380 | end 381 | 382 | def shard_file(file, id) 383 | size = File.stat(file).size 384 | manifest = create_manifest(File.basename(file), size) 385 | shard_size = size <= 1048576 ? size / 2 : 1048576 386 | 387 | File.open(file, "r") do |fh_in| 388 | until fh_in.eof? 389 | piece = fh_in.read(shard_size) 390 | piece_hash = generate_file_id(piece) 391 | 392 | manifest[:pieces].push(piece_hash) 393 | add_shard(piece_hash, piece) unless @shards[piece_hash] 394 | end 395 | end 396 | 397 | add_manifest(manifest.to_json, id) 398 | sync 399 | end 400 | 401 | def store_at_second_closest(shortlist, contact, query_id, data) 402 | second_closest = shortlist.find { |c| c.id != contact.id } 403 | store(query_id, data, second_closest) if second_closest 404 | end 405 | 406 | def valid_shard?(shard) 407 | shard_content = File.read(Defaults::ENVIRONMENT[:shards] + '/' + shard) 408 | generate_file_id(shard_content) == shard 409 | end 410 | 411 | def fetch_shard(shard) 412 | result = iterative_find_value(shard) 413 | get(result) if result 414 | end 415 | 416 | def write_to_subfolder(destination, name, content) 417 | file_name = destination + '/' + name 418 | File.open(file_name, 'wb') do |f| 419 | f.write(content) 420 | end 421 | end 422 | end 423 | -------------------------------------------------------------------------------- /test/routing_table_160bit_8k_test.rb: -------------------------------------------------------------------------------- 1 | require_relative 'test_helper.rb' 2 | require_relative '../lib/node.rb' 3 | require_relative "../lib/routing_table.rb" 4 | require_relative "../lib/kbucket.rb" 5 | require_relative "../lib/contact.rb" 6 | require_relative "../lib/fake_network_adapter.rb" 7 | 8 | class RoutingTableTest160bit8k < Minitest::Test 9 | def setup 10 | Defaults::ENVIRONMENT[:bit_length] = 160 11 | Defaults::ENVIRONMENT[:k] = 8 12 | @kn = FakeNetworkAdapter.new 13 | @node = Node.new('0', @kn) 14 | @routing_table = @node.routing_table 15 | end 16 | 17 | def test_create_routing_table 18 | assert_equal(1, @routing_table.buckets.size) 19 | end 20 | 21 | def test_insert_node_with_duplicate_id 22 | new_node = Node.new('0', @kn) 23 | 24 | @routing_table.insert(new_node) 25 | assert_equal(0, @routing_table.buckets[0].size) 26 | end 27 | 28 | def test_insert_if_bucket_not_full 29 | node15 = Node.new('15', @kn) 30 | node16 = Node.new('16', @kn) 31 | 32 | @routing_table.insert(node15) 33 | @routing_table.insert(node16) 34 | 35 | assert_equal(1, @routing_table.buckets.size) 36 | assert_equal(2, @routing_table.buckets[0].contacts.size) 37 | end 38 | 39 | def test_insert_find_closest_bucket_with_one_bucket_with_closest_shared_bit_length 40 | result = @routing_table.find_closest_bucket('1') 41 | assert_equal(result, @routing_table.buckets[0]) 42 | end 43 | 44 | def test_insert_find_closest_bucket_with_two_buckets_no_shared_bit_length 45 | @routing_table.create_bucket 46 | 47 | no_shared_id = (2 ** (Defaults::ENVIRONMENT[:bit_length] - 1)).to_s 48 | 49 | result = @routing_table.find_closest_bucket(no_shared_id) 50 | 51 | assert_equal(result, @routing_table.buckets[0]) 52 | end 53 | 54 | def test_insert_find_closest_bucket_with_two_buckets_no_shared_bit_length_bug 55 | @routing_table.create_bucket 56 | 57 | no_shared_id = (2**(Defaults::ENVIRONMENT[:bit_length]) - 1).to_s 58 | 59 | result = @routing_table.find_closest_bucket(no_shared_id) 60 | 61 | assert_equal(result, @routing_table.buckets[0]) 62 | end 63 | 64 | def test_insert_find_closest_bucket_with_two_buckets_one_shared_bit 65 | @routing_table.create_bucket 66 | one_shared_id = (2**(Defaults::ENVIRONMENT[:bit_length] - 2)).to_s 67 | 68 | result = @routing_table.find_closest_bucket(one_shared_id) 69 | 70 | assert_equal(result, @routing_table.buckets[1]) 71 | end 72 | 73 | def test_insert_find_closest_bucket_with_two_buckets_with_most_shared_bits 74 | @routing_table.create_bucket 75 | result = @routing_table.find_closest_bucket('1') 76 | 77 | assert_equal(result, @routing_table.buckets[1]) 78 | end 79 | 80 | def test_insert_find_closest_bucket_with_full_buckets_with_arbitrary_shared_bits 81 | total_buckets = Defaults::ENVIRONMENT[:bit_length] - 1 # create bucket equal to k 82 | 83 | total_buckets.times do 84 | @routing_table.create_bucket 85 | end 86 | 87 | result2 = @routing_table.find_closest_bucket('2') 88 | position = Binary.shared_prefix_bit_length('0', '2') 89 | result7 = @routing_table.find_closest_bucket('7') 90 | position2 = Binary.shared_prefix_bit_length('0', '7') 91 | result15 = @routing_table.find_closest_bucket('15') 92 | position3 = Binary.shared_prefix_bit_length('0', '15') 93 | 94 | assert_equal(result2, @routing_table.buckets[position]) 95 | 96 | assert_equal(result7, @routing_table.buckets[position2]) 97 | 98 | assert_equal(result15, @routing_table.buckets[position3]) 99 | end 100 | 101 | def test_insert_if_bucket_full_and_splittable_diff_xor_distance 102 | node56 = Node.new('56', @kn) 103 | node57 = Node.new('57', @kn) 104 | node58 = Node.new('58', @kn) 105 | node59 = Node.new('59', @kn) 106 | node60 = Node.new('60', @kn) 107 | node61 = Node.new('61', @kn) 108 | node62 = Node.new('62', @kn) 109 | node63 = Node.new('63', @kn) 110 | 111 | @routing_table.insert(node56.to_contact) 112 | @routing_table.insert(node57.to_contact) 113 | @routing_table.insert(node58.to_contact) 114 | @routing_table.insert(node59.to_contact) 115 | @routing_table.insert(node60.to_contact) 116 | @routing_table.insert(node61.to_contact) 117 | @routing_table.insert(node62.to_contact) 118 | @routing_table.insert(node63.to_contact) 119 | 120 | node7 = Node.new('7', @kn) 121 | @routing_table.insert(node7.to_contact) 122 | 123 | index_of_second_last_bucket = Binary.shared_prefix_bit_length('0', '56') 124 | 125 | assert_equal(index_of_second_last_bucket + 2, @routing_table.buckets.size) 126 | assert_equal(8, @routing_table.buckets[index_of_second_last_bucket].contacts.size) 127 | assert_equal(1, @routing_table.buckets.last.contacts.size) 128 | end 129 | 130 | def test_insert_if_bucket_full_and_splittable_smaller_distance_insert_first 131 | node56 = Node.new('56', @kn) 132 | node57 = Node.new('57', @kn) 133 | node58 = Node.new('58', @kn) 134 | node59 = Node.new('59', @kn) 135 | node60 = Node.new('60', @kn) 136 | node61 = Node.new('61', @kn) 137 | node62 = Node.new('62', @kn) 138 | node63 = Node.new('63', @kn) 139 | 140 | @routing_table.insert(node56.to_contact) 141 | @routing_table.insert(node57.to_contact) 142 | @routing_table.insert(node58.to_contact) 143 | @routing_table.insert(node59.to_contact) 144 | @routing_table.insert(node60.to_contact) 145 | @routing_table.insert(node61.to_contact) 146 | @routing_table.insert(node62.to_contact) 147 | @routing_table.insert(node63.to_contact) 148 | 149 | node80 = Node.new('80', @kn) # this id is further 150 | 151 | index1 = Binary.shared_prefix_bit_length('0', '56') 152 | index2 = Binary.shared_prefix_bit_length('0', '80') 153 | 154 | @routing_table.insert(node80.to_contact) 155 | 156 | assert_equal(8, @routing_table.buckets[index1].contacts.size) 157 | assert_equal(1, @routing_table.buckets[index2].contacts.size) 158 | assert_equal(index1 + 1, @routing_table.buckets.size) 159 | end 160 | 161 | def test_insert_if_bucket_full_and_splittable_same_xor_distance 162 | node56 = Node.new('56', @kn) 163 | node57 = Node.new('57', @kn) 164 | node58 = Node.new('58', @kn) 165 | node59 = Node.new('59', @kn) 166 | node60 = Node.new('60', @kn) 167 | node61 = Node.new('61', @kn) 168 | node62 = Node.new('62', @kn) 169 | node63 = Node.new('63', @kn) 170 | 171 | @routing_table.insert(node56.to_contact) 172 | @routing_table.insert(node57.to_contact) 173 | @routing_table.insert(node58.to_contact) 174 | @routing_table.insert(node59.to_contact) 175 | @routing_table.insert(node60.to_contact) 176 | @routing_table.insert(node61.to_contact) 177 | @routing_table.insert(node62.to_contact) 178 | @routing_table.insert(node63.to_contact) 179 | 180 | bucket_idx = Binary.shared_prefix_bit_length('0', '56') 181 | 182 | node55 = Node.new('55', @kn) # same xor distance 183 | @routing_table.insert(node55.to_contact) 184 | 185 | assert_equal(8, @routing_table.buckets[bucket_idx].contacts.size) 186 | assert_equal(bucket_idx + 1, @routing_table.buckets.size) 187 | end 188 | 189 | def test_insert_if_bucket_full_and_splittable_same_xor_distance_bucket_redistributable 190 | node31 = Node.new('31', @kn) # smaller xor 191 | node57 = Node.new('57', @kn) 192 | node58 = Node.new('58', @kn) 193 | node59 = Node.new('59', @kn) 194 | node60 = Node.new('60', @kn) 195 | node61 = Node.new('61', @kn) 196 | node62 = Node.new('62', @kn) 197 | node63 = Node.new('63', @kn) 198 | 199 | @routing_table.insert(node31.to_contact) 200 | @routing_table.insert(node57.to_contact) 201 | @routing_table.insert(node58.to_contact) 202 | @routing_table.insert(node59.to_contact) 203 | @routing_table.insert(node60.to_contact) 204 | @routing_table.insert(node61.to_contact) 205 | @routing_table.insert(node62.to_contact) 206 | @routing_table.insert(node63.to_contact) 207 | 208 | bucket_idx = Binary.shared_prefix_bit_length('0', '56') # 154 209 | 210 | node40 = Node.new('40', @kn) # inserting node with same xor distance 211 | @routing_table.insert(node40.to_contact) 212 | 213 | assert_equal(8, @routing_table.buckets[bucket_idx].contacts.size) 214 | assert_equal(1, @routing_table.buckets.last.contacts.size) 215 | assert_equal(bucket_idx + 2, @routing_table.buckets.size) # 156 216 | end 217 | 218 | def test_redistribute_one_bucket_to_two 219 | no_shared_id = (2**(Defaults::ENVIRONMENT[:bit_length] - 1)).to_s 220 | node_no_shared = Node.new(no_shared_id, @kn) 221 | node3 = Node.new('3', @kn) 222 | 223 | @routing_table.insert(node_no_shared.to_contact) 224 | @routing_table.insert(node3.to_contact) 225 | 226 | @routing_table.create_bucket 227 | 228 | @routing_table.redistribute_contacts 229 | 230 | assert_equal(1, @routing_table.buckets[0].contacts.size) 231 | assert_equal(1, @routing_table.buckets[1].contacts.size) 232 | end 233 | 234 | def test_insert_if_bucket_full_and_splittable_but_contains_at_least_1_closer_element 235 | node31 = Node.new('31', @kn) # smaller xor 236 | node57 = Node.new('57', @kn) 237 | node58 = Node.new('58', @kn) 238 | node59 = Node.new('59', @kn) 239 | node60 = Node.new('60', @kn) 240 | node61 = Node.new('61', @kn) 241 | node62 = Node.new('62', @kn) 242 | node63 = Node.new('63', @kn) 243 | 244 | @routing_table.insert(node31.to_contact) 245 | @routing_table.insert(node57.to_contact) 246 | @routing_table.insert(node58.to_contact) 247 | @routing_table.insert(node59.to_contact) 248 | @routing_table.insert(node60.to_contact) 249 | @routing_table.insert(node61.to_contact) 250 | @routing_table.insert(node62.to_contact) 251 | @routing_table.insert(node63.to_contact) 252 | 253 | bucket_idx = Binary.shared_prefix_bit_length('0', '56') 254 | 255 | node30 = Node.new('30', @kn) 256 | @routing_table.insert(node30.to_contact) 257 | 258 | assert_equal(7, @routing_table.buckets[bucket_idx].contacts.size) 259 | assert_equal(2, @routing_table.buckets.last.contacts.size) 260 | assert_equal(bucket_idx + 2, @routing_table.buckets.size) 261 | end 262 | 263 | def test_insert_if_bucket_full_and_not_splittable 264 | node56 = Node.new('56', @kn) 265 | node57 = Node.new('57', @kn) 266 | node58 = Node.new('58', @kn) 267 | node59 = Node.new('59', @kn) 268 | node60 = Node.new('60', @kn) 269 | node61 = Node.new('61', @kn) 270 | node62 = Node.new('62', @kn) 271 | node63 = Node.new('63', @kn) 272 | 273 | @routing_table.insert(node56.to_contact) 274 | @routing_table.insert(node57.to_contact) 275 | @routing_table.insert(node58.to_contact) 276 | @routing_table.insert(node59.to_contact) 277 | @routing_table.insert(node60.to_contact) 278 | @routing_table.insert(node61.to_contact) 279 | @routing_table.insert(node62.to_contact) 280 | @routing_table.insert(node63.to_contact) 281 | 282 | node31 = Node.new('31', @kn) 283 | @routing_table.insert(node31.to_contact) 284 | 285 | last_bucket_idx = Binary.shared_prefix_bit_length('0', '31') 286 | 287 | node40 = Node.new('40', @kn) 288 | @routing_table.insert(node40.to_contact) 289 | 290 | assert_equal(8, @routing_table.buckets[last_bucket_idx - 1].contacts.size) 291 | assert_equal(1, @routing_table.buckets.last.contacts.size) 292 | assert_equal(last_bucket_idx + 1, @routing_table.buckets.size) 293 | end 294 | 295 | def test_insert_if_bucket_full_and_not_splittable_and_head_node_live 296 | node56 = Node.new('56', @kn) 297 | node57 = Node.new('57', @kn) 298 | node58 = Node.new('58', @kn) 299 | node59 = Node.new('59', @kn) 300 | node60 = Node.new('60', @kn) 301 | node61 = Node.new('61', @kn) 302 | node62 = Node.new('62', @kn) 303 | node63 = Node.new('63', @kn) 304 | 305 | @routing_table.insert(node56.to_contact) 306 | @routing_table.insert(node57.to_contact) 307 | @routing_table.insert(node58.to_contact) 308 | @routing_table.insert(node59.to_contact) 309 | @routing_table.insert(node60.to_contact) 310 | @routing_table.insert(node61.to_contact) 311 | @routing_table.insert(node62.to_contact) 312 | @routing_table.insert(node63.to_contact) 313 | 314 | node31 = Node.new('31', @kn) 315 | @routing_table.insert(node31.to_contact) 316 | 317 | bucket_idx = Binary.shared_prefix_bit_length('0', '56') 318 | 319 | node40 = Node.new('40', @kn) 320 | @routing_table.insert(node40.to_contact) 321 | 322 | assert_equal('56', @routing_table.buckets[bucket_idx].tail.id) 323 | assert_equal('57', @routing_table.buckets[bucket_idx].head.id) 324 | assert_equal(bucket_idx + 2, @routing_table.buckets.size) 325 | end 326 | 327 | def test_insert_if_bucket_full_and_not_splittable_and_head_node_not_live 328 | node56 = Node.new('56', @kn) 329 | node57 = Node.new('57', @kn) 330 | node58 = Node.new('58', @kn) 331 | node59 = Node.new('59', @kn) 332 | node60 = Node.new('60', @kn) 333 | node61 = Node.new('61', @kn) 334 | node62 = Node.new('62', @kn) 335 | node63 = Node.new('63', @kn) 336 | 337 | @routing_table.insert(node56.to_contact) 338 | @routing_table.insert(node57.to_contact) 339 | @routing_table.insert(node58.to_contact) 340 | @routing_table.insert(node59.to_contact) 341 | @routing_table.insert(node60.to_contact) 342 | @routing_table.insert(node61.to_contact) 343 | @routing_table.insert(node62.to_contact) 344 | @routing_table.insert(node63.to_contact) 345 | 346 | node31 = Node.new('31', @kn) 347 | @routing_table.insert(node31.to_contact) 348 | 349 | bucket_idx = Binary.shared_prefix_bit_length('0', '56') 350 | 351 | @kn.nodes.delete_at(1) 352 | 353 | node40 = Node.new('40', @kn) 354 | @routing_table.insert(node40.to_contact) 355 | 356 | assert_equal('40', @routing_table.buckets[bucket_idx].tail.id) 357 | assert_equal('57', @routing_table.buckets[bucket_idx].head.id) 358 | assert_equal(bucket_idx + 2, @routing_table.buckets.size) 359 | end 360 | end 361 | -------------------------------------------------------------------------------- /test/node_6bit_8k_3a_test.rb: -------------------------------------------------------------------------------- 1 | require_relative 'test_helper.rb' 2 | require_relative "../lib/node.rb" 3 | require_relative "../lib/routing_table.rb" 4 | require_relative "../lib/fake_network_adapter.rb" 5 | require_relative "../lib/kbucket.rb" 6 | 7 | class NodeTest6bit8k < Minitest::Test 8 | def setup 9 | Defaults::ENVIRONMENT[:bit_length] = 6 10 | Defaults::ENVIRONMENT[:k] = 8 11 | Defaults::ENVIRONMENT[:alpha] = 3 12 | @kn = FakeNetworkAdapter.new 13 | end 14 | 15 | def test_create_node 16 | node = Node.new('0', @kn) 17 | 18 | assert_instance_of(Node, node) 19 | assert_instance_of(RoutingTable, node.routing_table) 20 | end 21 | 22 | def test_join_network 23 | node1 = Node.new('1', @kn) 24 | node2 = Node.new('2', @kn) 25 | 26 | assert_includes(@kn.nodes, node1) 27 | assert_includes(@kn.nodes, node2) 28 | end 29 | 30 | def test_ping_other_node_true_and_false 31 | node0 = Node.new('0', @kn) 32 | node1 = Node.new('1', @kn) 33 | node2 = Node.new('2', @kn) 34 | 35 | assert(node0.ping(node1.to_contact)) 36 | assert(node0.ping(node2.to_contact)) 37 | refute(node0.ping(Contact.new(id: '3', ip: ''))) 38 | end 39 | 40 | def test_ping_dead_node; end 41 | 42 | def test_receive_ping 43 | node0 = Node.new('0', @kn) 44 | node1 = Node.new('1', @kn) 45 | 46 | refute_includes(node0.routing_table.buckets[0].contacts, node1.to_contact) 47 | refute_includes(node1.routing_table.buckets[0].contacts, node0.to_contact) 48 | 49 | node0.ping(node1.to_contact) 50 | 51 | assert_equal(1, node1.routing_table.buckets[0].contacts.size) 52 | assert_equal(1, node0.routing_table.buckets[0].contacts.size) 53 | end 54 | 55 | def test_store 56 | node0 = Node.new('0', @kn) 57 | node1 = Node.new('1', @kn) 58 | 59 | node0.store('key', 'value', node1.to_contact) 60 | assert_equal(['value'], node1.dht_segment['key']) 61 | end 62 | 63 | def test_receive_find_node 64 | node0 = Node.new('0', @kn) 65 | 66 | node32 = Node.new('32', @kn) 67 | node57 = Node.new('57', @kn) 68 | node58 = Node.new('58', @kn) 69 | node59 = Node.new('59', @kn) 70 | node60 = Node.new('60', @kn) 71 | node61 = Node.new('61', @kn) 72 | node62 = Node.new('62', @kn) 73 | node63 = Node.new('63', @kn) 74 | 75 | node0.routing_table.insert(node32.to_contact) 76 | node0.routing_table.insert(node57.to_contact) 77 | node0.routing_table.insert(node58.to_contact) 78 | node0.routing_table.insert(node59.to_contact) 79 | node0.routing_table.insert(node60.to_contact) 80 | node0.routing_table.insert(node61.to_contact) 81 | node0.routing_table.insert(node62.to_contact) 82 | node0.routing_table.insert(node63.to_contact) 83 | 84 | node12 = Node.new('12', @kn) 85 | 86 | results = node0.receive_find_node('1', node12.to_contact) 87 | results.map!(&:id) 88 | 89 | refute_empty(results) 90 | assert_equal(8, results.size) 91 | assert_includes(results, '32') 92 | assert_includes(results, '63') 93 | end 94 | 95 | def test_receive_find_node_multiple_buckets 96 | node0 = Node.new('0', @kn) 97 | 98 | node32 = Node.new('32', @kn) 99 | node57 = Node.new('57', @kn) 100 | node58 = Node.new('58', @kn) 101 | node59 = Node.new('59', @kn) 102 | node60 = Node.new('60', @kn) 103 | node61 = Node.new('61', @kn) 104 | node62 = Node.new('62', @kn) 105 | node63 = Node.new('63', @kn) 106 | node7 = Node.new('7', @kn) 107 | 108 | node0.routing_table.insert(node32.to_contact) 109 | node0.routing_table.insert(node57.to_contact) 110 | node0.routing_table.insert(node58.to_contact) 111 | node0.routing_table.insert(node59.to_contact) 112 | node0.routing_table.insert(node60.to_contact) 113 | node0.routing_table.insert(node61.to_contact) 114 | node0.routing_table.insert(node62.to_contact) 115 | node0.routing_table.insert(node63.to_contact) 116 | node0.routing_table.insert(node7.to_contact) 117 | 118 | node12 = Node.new('12', @kn) 119 | 120 | results = node0.receive_find_node('1', node12.to_contact) 121 | results.map!(&:id) 122 | 123 | refute_empty(results) 124 | assert_equal(8, results.size) 125 | assert_includes(results, '7') 126 | assert_includes(results, '32') 127 | assert_includes(results, '62') 128 | end 129 | 130 | def test_receive_find_node_multiple_buckets_starting_from_back 131 | node0 = Node.new('0', @kn) 132 | 133 | node57 = Node.new('57', @kn) 134 | node58 = Node.new('58', @kn) 135 | node59 = Node.new('59', @kn) 136 | node60 = Node.new('60', @kn) 137 | node61 = Node.new('61', @kn) 138 | node62 = Node.new('62', @kn) 139 | node63 = Node.new('63', @kn) 140 | node7 = Node.new('7', @kn) 141 | node8 = Node.new('8', @kn) 142 | 143 | node0.routing_table.insert(node57.to_contact) 144 | node0.routing_table.insert(node58.to_contact) 145 | node0.routing_table.insert(node59.to_contact) 146 | node0.routing_table.insert(node60.to_contact) 147 | node0.routing_table.insert(node61.to_contact) 148 | node0.routing_table.insert(node62.to_contact) 149 | node0.routing_table.insert(node63.to_contact) 150 | node0.routing_table.insert(node7.to_contact) 151 | node0.routing_table.insert(node8.to_contact) 152 | 153 | node12 = Node.new('12', @kn) 154 | 155 | results = node0.receive_find_node('40', node12.to_contact) 156 | results.map!(&:id) 157 | 158 | refute_empty(results) 159 | assert_equal(8, results.size) 160 | assert_includes(results, '57') 161 | assert_includes(results, '63') 162 | assert_includes(results, '7') 163 | refute_includes(results, '8') 164 | end 165 | 166 | def test_receive_find_node_fewer_than_k_results 167 | node0 = Node.new('0', @kn) 168 | node15 = Node.new('15', @kn) 169 | node7 = Node.new('7', @kn) 170 | 171 | node0.routing_table.insert(node15.to_contact) 172 | 173 | results = node0.receive_find_node('1', node7.to_contact) 174 | 175 | refute_empty(results) 176 | assert_equal(1, results.size) 177 | assert_equal('15', results.first.id) 178 | end 179 | 180 | def test_receive_find_node_exclude_requestor 181 | node0 = Node.new('0', @kn) 182 | node15 = Node.new('15', @kn) 183 | node14 = Node.new('14', @kn) 184 | node3 = Node.new('3', @kn) 185 | node7 = Node.new('7', @kn) 186 | 187 | node15_contact = node15.to_contact 188 | node14_contact = node14.to_contact 189 | node3_contact = node3.to_contact 190 | node7_contact = node7.to_contact 191 | 192 | node0.routing_table.insert(node15_contact) 193 | node0.routing_table.insert(node14_contact) 194 | node0.routing_table.insert(node3_contact) 195 | node0.routing_table.insert(node7_contact) 196 | 197 | results = node0.receive_find_node('1', node7_contact) 198 | 199 | refute_includes(results, node7_contact) 200 | assert_equal(3, results.size) 201 | assert_includes(results, node15_contact) 202 | assert_includes(results, node14_contact) 203 | assert_includes(results, node3_contact) 204 | end 205 | 206 | def test_find_node 207 | node7 = Node.new('7', @kn) # the requestor node 208 | node0 = Node.new('0', @kn) # the node that gets the request 209 | node4 = Node.new('4', @kn) 210 | node5 = Node.new('5', @kn) 211 | node12 = Node.new('12', @kn) 212 | 213 | node4_contact = node4.to_contact 214 | node5_contact = node5.to_contact 215 | node12_contact = node12.to_contact 216 | 217 | node0.routing_table.insert(node4_contact) 218 | node0.routing_table.insert(node5_contact) 219 | node0.routing_table.insert(node12_contact) 220 | 221 | results = node7.find_node('1', node0.to_contact) 222 | 223 | refute_empty(results) 224 | assert_equal(3, results.size) 225 | assert_includes(results, node4_contact) 226 | assert_includes(results, node5_contact) 227 | assert_includes(results, node12_contact) 228 | end 229 | 230 | def test_receive_find_value_with_match 231 | # return a address 232 | node0 = Node.new('0', @kn) # node that received request 233 | node0.dht_segment['10'] = ['some_address'] 234 | 235 | node7 = Node.new('7', @kn) # node making the request 236 | 237 | result = node0.receive_find_value('10', node7.to_contact) 238 | 239 | assert_equal('some_address', result['data']) 240 | end 241 | 242 | def test_receive_find_value_with_no_match 243 | # just returns closest nodes 244 | 245 | node0 = Node.new('0', @kn) # node that received request 246 | node0.dht_segment['11'] = 'some_address' 247 | 248 | node4 = Node.new('4', @kn) 249 | node5 = Node.new('5', @kn) 250 | node12 = Node.new('12', @kn) 251 | node13 = Node.new('13', @kn) 252 | node7 = Node.new('7', @kn) 253 | 254 | node4_contact = node4.to_contact 255 | node5_contact = node5.to_contact 256 | node12_contact = node12.to_contact 257 | node13_contact = node13.to_contact 258 | 259 | node0.routing_table.insert(node4_contact) 260 | node0.routing_table.insert(node5_contact) 261 | node0.routing_table.insert(node12_contact) 262 | node0.routing_table.insert(node13_contact) 263 | 264 | results = node0.receive_find_value('10', node7.to_contact) 265 | 266 | refute_empty(results['contacts']) 267 | assert_equal(4, results['contacts'].size) 268 | assert_includes(results['contacts'], node12_contact) 269 | assert_includes(results['contacts'], node13_contact) 270 | end 271 | 272 | def test_find_value_with_match 273 | node0 = Node.new('0', @kn) # node that received request 274 | node0.dht_segment['10'] = ['some_address'] 275 | 276 | node7 = Node.new('7', @kn) # node making the request 277 | 278 | result = node7.find_value('10', node0.to_contact) 279 | 280 | assert_equal('some_address', result['data']) 281 | end 282 | 283 | def test_find_value_with_no_match 284 | node0 = Node.new('0', @kn) # node that received request 285 | node0.dht_segment['11'] = ['some_address'] 286 | 287 | node4 = Node.new('4', @kn) 288 | node5 = Node.new('5', @kn) 289 | node12 = Node.new('12', @kn) 290 | node7 = Node.new('7', @kn) 291 | 292 | node4_contact = node4.to_contact 293 | node5_contact = node5.to_contact 294 | node12_contact = node12.to_contact 295 | 296 | node0.routing_table.insert(node4_contact) 297 | node0.routing_table.insert(node5_contact) 298 | node0.routing_table.insert(node12_contact) 299 | 300 | results = node7.find_value('10', node0.to_contact) 301 | 302 | refute_empty(results['contacts']) 303 | assert_equal(3, results['contacts'].size) 304 | assert_includes(results['contacts'], node12_contact) 305 | assert_includes(results['contacts'], node4_contact) 306 | assert_includes(results['contacts'], node5_contact) 307 | end 308 | 309 | def test_iterative_find_node # test fails if alpha = 1 310 | node0 = Node.new('0', @kn) 311 | node4 = Node.new('4', @kn) 312 | node5 = Node.new('5', @kn) 313 | node12 = Node.new('12', @kn) 314 | node14 = Node.new('14', @kn) 315 | 316 | node4_contact = node4.to_contact 317 | node5_contact = node5.to_contact 318 | node12_contact = node12.to_contact 319 | node14_contact = node14.to_contact 320 | 321 | node0.routing_table.insert(node4_contact) 322 | node0.routing_table.insert(node5_contact) 323 | node0.routing_table.insert(node12_contact) 324 | 325 | node12.routing_table.insert(node14_contact) 326 | 327 | result = node0.iterative_find_node('15') 328 | assert_instance_of(Array, result) 329 | assert_equal(4, result.size) 330 | assert_includes(result.map(&:id), node14_contact.id) 331 | assert_includes(result.map(&:id), node12_contact.id) 332 | # test that ping adds new contact to our routing table 333 | assert_includes(node0.routing_table.buckets[0].map(&:id), node14_contact.id) 334 | end 335 | 336 | def test_iterative_store 337 | node0 = Node.new('0', @kn) 338 | node4 = Node.new('4', @kn) 339 | node5 = Node.new('5', @kn) 340 | node12 = Node.new('12', @kn) 341 | node14 = Node.new('14', @kn) 342 | 343 | node4_contact = node4.to_contact 344 | node5_contact = node5.to_contact 345 | node12_contact = node12.to_contact 346 | node14_contact = node14.to_contact 347 | 348 | node0.routing_table.insert(node4_contact) 349 | node0.routing_table.insert(node5_contact) 350 | node0.routing_table.insert(node12_contact) 351 | 352 | node12.routing_table.insert(node14_contact) 353 | node0.iterative_store('13', 'some_address') 354 | 355 | assert_equal(['some_address'], node12.dht_segment['13']) 356 | assert_equal(['some_address'], node4.dht_segment['13']) 357 | assert_equal(['some_address'], node5.dht_segment['13']) 358 | refute(node14.dht_segment['13']) 359 | end 360 | 361 | def test_iterative_find_value_with_match 362 | node0 = Node.new('0', @kn) 363 | node4 = Node.new('4', @kn) 364 | node5 = Node.new('5', @kn) 365 | node12 = Node.new('12', @kn) 366 | node14 = Node.new('14', @kn) 367 | 368 | node4_contact = node4.to_contact 369 | node5_contact = node5.to_contact 370 | node12_contact = node12.to_contact 371 | node14_contact = node14.to_contact 372 | 373 | node0.routing_table.insert(node4_contact) 374 | node0.routing_table.insert(node5_contact) 375 | node0.routing_table.insert(node12_contact) 376 | 377 | node12.routing_table.insert(node14_contact) 378 | node0.store('15', 'some_address', node14_contact) 379 | 380 | result = node0.iterative_find_value('15') 381 | assert_instance_of(String, result) 382 | assert_equal('some_address', result) 383 | # store in second closest node 384 | assert_equal(['some_address'], node4.dht_segment['15']) 385 | end 386 | 387 | def test_iterative_find_value_with_no_match 388 | node0 = Node.new('0', @kn) 389 | node4 = Node.new('4', @kn) 390 | node5 = Node.new('5', @kn) 391 | node12 = Node.new('12', @kn) 392 | node14 = Node.new('14', @kn) 393 | 394 | node4_contact = node4.to_contact 395 | node5_contact = node5.to_contact 396 | node12_contact = node12.to_contact 397 | node14_contact = node14.to_contact 398 | 399 | node0.routing_table.insert(node4_contact) 400 | node0.routing_table.insert(node5_contact) 401 | node0.routing_table.insert(node12_contact) 402 | 403 | node12.routing_table.insert(node14_contact) 404 | node0.store('13', 'some_address', node14_contact) 405 | 406 | result = node0.iterative_find_value('15') 407 | assert_instance_of(Array, result) 408 | assert_equal(4, result.size) 409 | ids = result.map(&:id) 410 | assert_includes(ids, node4_contact.id) 411 | assert_includes(ids, node5_contact.id) 412 | assert_includes(ids, node14_contact.id) 413 | assert_includes(ids, node12_contact.id) 414 | end 415 | end 416 | -------------------------------------------------------------------------------- /test/node_160bit_8k_3a_test.rb: -------------------------------------------------------------------------------- 1 | require_relative 'test_helper.rb' 2 | require_relative "../lib/node.rb" 3 | require_relative "../lib/routing_table.rb" 4 | require_relative "../lib/fake_network_adapter.rb" 5 | require_relative "../lib/kbucket.rb" 6 | 7 | class NodeTest160bit8k < Minitest::Test 8 | def setup 9 | Defaults::ENVIRONMENT[:bit_length] = 160 10 | Defaults::ENVIRONMENT[:k] = 8 11 | Defaults::ENVIRONMENT[:alpha] = 3 12 | @kn = FakeNetworkAdapter.new 13 | end 14 | 15 | def test_create_node 16 | node = Node.new('0', @kn) 17 | 18 | assert_instance_of(Node, node) 19 | assert_instance_of(RoutingTable, node.routing_table) 20 | end 21 | 22 | def test_join_network 23 | node1 = Node.new('1', @kn) 24 | node2 = Node.new('2', @kn) 25 | 26 | assert_includes(@kn.nodes, node1) 27 | assert_includes(@kn.nodes, node2) 28 | end 29 | 30 | def test_ping_other_node_true_and_false 31 | node0 = Node.new('0', @kn) 32 | node1 = Node.new('1', @kn) 33 | node2 = Node.new('2', @kn) 34 | 35 | assert(node0.ping(node1.to_contact)) 36 | assert(node0.ping(node2.to_contact)) 37 | refute(node0.ping(Contact.new(id: '3', ip: ''))) 38 | end 39 | 40 | def test_receive_ping 41 | node0 = Node.new('0', @kn) 42 | node1 = Node.new('1', @kn) 43 | 44 | refute_includes(node0.routing_table.buckets[0].contacts, node1.to_contact) 45 | refute_includes(node1.routing_table.buckets[0].contacts, node0.to_contact) 46 | 47 | node0.ping(node1.to_contact) 48 | 49 | assert_equal(1, node1.routing_table.buckets[0].contacts.size) 50 | assert_equal(1, node0.routing_table.buckets[0].contacts.size) 51 | end 52 | 53 | def test_store 54 | node0 = Node.new('0', @kn) 55 | node1 = Node.new('1', @kn) 56 | 57 | node0.store('key', 'value', node1.to_contact) 58 | assert_equal(['value'], node1.dht_segment['key']) 59 | end 60 | 61 | def test_receive_find_node 62 | node0 = Node.new('0', @kn) 63 | 64 | node32 = Node.new('32', @kn) 65 | node57 = Node.new('57', @kn) 66 | node58 = Node.new('58', @kn) 67 | node59 = Node.new('59', @kn) 68 | node60 = Node.new('60', @kn) 69 | node61 = Node.new('61', @kn) 70 | node62 = Node.new('62', @kn) 71 | node63 = Node.new('63', @kn) 72 | 73 | node0.routing_table.insert(node32.to_contact) 74 | node0.routing_table.insert(node57.to_contact) 75 | node0.routing_table.insert(node58.to_contact) 76 | node0.routing_table.insert(node59.to_contact) 77 | node0.routing_table.insert(node60.to_contact) 78 | node0.routing_table.insert(node61.to_contact) 79 | node0.routing_table.insert(node62.to_contact) 80 | node0.routing_table.insert(node63.to_contact) 81 | 82 | node12 = Node.new('12', @kn) 83 | 84 | results = node0.receive_find_node('1', node12.to_contact) 85 | results.map!(&:id) 86 | 87 | refute_empty(results) 88 | assert_equal(8, results.size) 89 | assert_includes(results, '32') 90 | assert_includes(results, '63') 91 | end 92 | 93 | def test_receive_find_node_multiple_buckets 94 | node0 = Node.new('0', @kn) 95 | 96 | node32 = Node.new('32', @kn) 97 | node57 = Node.new('57', @kn) 98 | node58 = Node.new('58', @kn) 99 | node59 = Node.new('59', @kn) 100 | node60 = Node.new('60', @kn) 101 | node61 = Node.new('61', @kn) 102 | node62 = Node.new('62', @kn) 103 | node63 = Node.new('63', @kn) 104 | node7 = Node.new('7', @kn) 105 | 106 | node0.routing_table.insert(node32.to_contact) 107 | node0.routing_table.insert(node57.to_contact) 108 | node0.routing_table.insert(node58.to_contact) 109 | node0.routing_table.insert(node59.to_contact) 110 | node0.routing_table.insert(node60.to_contact) 111 | node0.routing_table.insert(node61.to_contact) 112 | node0.routing_table.insert(node62.to_contact) 113 | node0.routing_table.insert(node63.to_contact) 114 | node0.routing_table.insert(node7.to_contact) 115 | 116 | node12 = Node.new('12', @kn) 117 | 118 | results = node0.receive_find_node('1', node12.to_contact) 119 | results.map!(&:id) 120 | 121 | refute_empty(results) 122 | assert_equal(8, results.size) 123 | assert_includes(results, '7') 124 | assert_includes(results, '32') 125 | assert_includes(results, '62') 126 | end 127 | 128 | def test_receive_find_node_multiple_buckets_starting_from_back 129 | node0 = Node.new('0', @kn) 130 | 131 | node57 = Node.new('57', @kn) 132 | node58 = Node.new('58', @kn) 133 | node59 = Node.new('59', @kn) 134 | node60 = Node.new('60', @kn) 135 | node61 = Node.new('61', @kn) 136 | node62 = Node.new('62', @kn) 137 | node63 = Node.new('63', @kn) 138 | node7 = Node.new('7', @kn) 139 | node8 = Node.new('8', @kn) 140 | 141 | node0.routing_table.insert(node57.to_contact) 142 | node0.routing_table.insert(node58.to_contact) 143 | node0.routing_table.insert(node59.to_contact) 144 | node0.routing_table.insert(node60.to_contact) 145 | node0.routing_table.insert(node61.to_contact) 146 | node0.routing_table.insert(node62.to_contact) 147 | node0.routing_table.insert(node63.to_contact) 148 | node0.routing_table.insert(node7.to_contact) 149 | node0.routing_table.insert(node8.to_contact) 150 | 151 | node12 = Node.new('12', @kn) 152 | 153 | results = node0.receive_find_node('40', node12.to_contact) 154 | results.map!(&:id) 155 | 156 | refute_empty(results) 157 | assert_equal(8, results.size) 158 | assert_includes(results, '57') 159 | assert_includes(results, '63') 160 | assert_includes(results, '7') 161 | refute_includes(results, '8') 162 | end 163 | 164 | def test_receive_find_node_fewer_than_k_results 165 | node0 = Node.new('0', @kn) 166 | node15 = Node.new('15', @kn) 167 | node16 = Node.new('16', @kn) 168 | node7 = Node.new('7', @kn) 169 | 170 | node0.routing_table.insert(node15.to_contact) 171 | node0.routing_table.insert(node16.to_contact) 172 | 173 | results = node0.receive_find_node('1', node7.to_contact) 174 | 175 | refute_empty(results) 176 | assert_equal(2, results.size) 177 | assert_equal('15', results.first.id) 178 | assert_equal('16', results.last.id) 179 | end 180 | 181 | def test_receive_find_node_exclude_requestor 182 | node0 = Node.new('0', @kn) 183 | node15 = Node.new('15', @kn) 184 | node14 = Node.new('14', @kn) 185 | node3 = Node.new('3', @kn) 186 | node7 = Node.new('7', @kn) 187 | 188 | node15_contact = node15.to_contact 189 | node14_contact = node14.to_contact 190 | node3_contact = node3.to_contact 191 | node7_contact = node7.to_contact 192 | 193 | node0.routing_table.insert(node15_contact) 194 | node0.routing_table.insert(node14_contact) 195 | node0.routing_table.insert(node3_contact) 196 | node0.routing_table.insert(node7_contact) 197 | 198 | results = node0.receive_find_node('1', node7_contact) 199 | 200 | refute_includes(results, node7_contact) 201 | assert_equal(3, results.size) 202 | assert_includes(results, node15_contact) 203 | assert_includes(results, node14_contact) 204 | assert_includes(results, node3_contact) 205 | end 206 | 207 | def test_find_node 208 | node7 = Node.new('7', @kn) # the requestor node 209 | node0 = Node.new('0', @kn) # the node that gets the request 210 | node4 = Node.new('4', @kn) 211 | node5 = Node.new('5', @kn) 212 | node12 = Node.new('12', @kn) 213 | 214 | node4_contact = node4.to_contact 215 | node5_contact = node5.to_contact 216 | node12_contact = node12.to_contact 217 | 218 | node0.routing_table.insert(node4_contact) 219 | node0.routing_table.insert(node5_contact) 220 | node0.routing_table.insert(node12_contact) 221 | 222 | results = node7.find_node('1', node0.to_contact) 223 | 224 | refute_empty(results) 225 | assert_equal(3, results.size) 226 | assert_includes(results, node4_contact) 227 | assert_includes(results, node5_contact) 228 | assert_includes(results, node12_contact) 229 | end 230 | 231 | def test_receive_find_value_with_match 232 | node0 = Node.new('0', @kn) # node that received request 233 | node0.dht_segment['10'] = ['some_address'] 234 | 235 | node7 = Node.new('7', @kn) # node making the request 236 | 237 | result = node0.receive_find_value('10', node7.to_contact) 238 | 239 | assert_equal('some_address', result['data']) 240 | end 241 | 242 | def test_receive_find_value_with_no_match 243 | # just returns closest nodes 244 | 245 | node0 = Node.new('0', @kn) # node that received request 246 | node0.dht_segment['11'] = ['some_address'] 247 | 248 | node4 = Node.new('4', @kn) 249 | node5 = Node.new('5', @kn) 250 | node12 = Node.new('12', @kn) 251 | node13 = Node.new('13', @kn) 252 | node7 = Node.new('7', @kn) 253 | 254 | node4_contact = node4.to_contact 255 | node5_contact = node5.to_contact 256 | node12_contact = node12.to_contact 257 | node13_contact = node13.to_contact 258 | 259 | node0.routing_table.insert(node4_contact) 260 | node0.routing_table.insert(node5_contact) 261 | node0.routing_table.insert(node12_contact) 262 | node0.routing_table.insert(node13_contact) 263 | 264 | results = node0.receive_find_value('10', node7.to_contact) 265 | 266 | refute_empty(results['contacts']) 267 | assert_equal(4, results['contacts'].size) 268 | assert_includes(results['contacts'], node12_contact) 269 | assert_includes(results['contacts'], node13_contact) 270 | end 271 | 272 | def test_find_value_with_match 273 | node0 = Node.new('0', @kn) # node that received request 274 | node0.dht_segment['10'] = ['some_address'] 275 | 276 | node7 = Node.new('7', @kn) # node making the request 277 | 278 | result = node7.find_value('10', node0.to_contact) 279 | 280 | assert_equal('some_address', result['data']) 281 | end 282 | 283 | def test_find_value_with_no_match 284 | node0 = Node.new('0', @kn) # node that received request 285 | node0.dht_segment['11'] = ['some_address'] 286 | 287 | node4 = Node.new('4', @kn) 288 | node5 = Node.new('5', @kn) 289 | node12 = Node.new('12', @kn) 290 | node7 = Node.new('7', @kn) 291 | 292 | node4_contact = node4.to_contact 293 | node5_contact = node5.to_contact 294 | node12_contact = node12.to_contact 295 | 296 | node0.routing_table.insert(node4_contact) 297 | node0.routing_table.insert(node5_contact) 298 | node0.routing_table.insert(node12_contact) 299 | 300 | results = node7.find_value('10', node0.to_contact) 301 | 302 | refute_empty(results['contacts']) 303 | assert_equal(3, results['contacts'].size) 304 | assert_includes(results['contacts'], node12_contact) 305 | assert_includes(results['contacts'], node5_contact) 306 | assert_includes(results['contacts'], node4_contact) 307 | end 308 | 309 | def test_iterative_find_node 310 | # no parallelism 311 | node0 = Node.new('0', @kn) 312 | node4 = Node.new('4', @kn) 313 | node5 = Node.new('5', @kn) 314 | node9 = Node.new('9', @kn) 315 | node10 = Node.new('10', @kn) 316 | node11 = Node.new('11', @kn) 317 | node12 = Node.new('12', @kn) 318 | node14 = Node.new('14', @kn) 319 | node13 = Node.new('13', @kn) 320 | 321 | node0.routing_table.insert(node4.to_contact) 322 | node0.routing_table.insert(node5.to_contact) 323 | node0.routing_table.insert(node9.to_contact) 324 | node0.routing_table.insert(node10.to_contact) 325 | 326 | node4.routing_table.insert(node11.to_contact) 327 | node4.routing_table.insert(node12.to_contact) 328 | node4.routing_table.insert(node13.to_contact) 329 | 330 | node14_contact = node14.to_contact 331 | 332 | node11.routing_table.insert(node14_contact) 333 | 334 | result = node0.iterative_find_node('15').map(&:id) 335 | 336 | assert_instance_of(Array, result) 337 | assert_equal(7, result.size) 338 | assert_includes(result, '14') 339 | assert_includes(result, '12') 340 | refute_includes(result, '10') 341 | # test that ping adds new contact to our routing table 342 | assert_includes(node0.routing_table.buckets[0].map(&:id), node14_contact.id) 343 | end 344 | 345 | def test_iterative_store 346 | node0 = Node.new('0', @kn) 347 | node4 = Node.new('4', @kn) 348 | node5 = Node.new('5', @kn) 349 | node12 = Node.new('12', @kn) 350 | node14 = Node.new('14', @kn) 351 | 352 | node4_contact = node4.to_contact 353 | node5_contact = node5.to_contact 354 | node12_contact = node12.to_contact 355 | node14_contact = node14.to_contact 356 | 357 | node0.routing_table.insert(node4_contact) 358 | node0.routing_table.insert(node5_contact) 359 | node0.routing_table.insert(node12_contact) 360 | 361 | node12.routing_table.insert(node14_contact) 362 | node0.iterative_store('13', 'some_address') 363 | 364 | assert_equal(['some_address'], node12.dht_segment['13']) 365 | assert_equal(['some_address'], node4.dht_segment['13']) 366 | assert_equal(['some_address'], node5.dht_segment['13']) 367 | refute(node14.dht_segment['13']) 368 | end 369 | 370 | def test_iterative_find_value_with_match 371 | node0 = Node.new('0', @kn) 372 | node4 = Node.new('4', @kn) 373 | node5 = Node.new('5', @kn) 374 | node12 = Node.new('12', @kn) 375 | node14 = Node.new('14', @kn) 376 | 377 | node4_contact = node4.to_contact 378 | node5_contact = node5.to_contact 379 | node12_contact = node12.to_contact 380 | node14_contact = node14.to_contact 381 | 382 | node0.routing_table.insert(node4_contact) 383 | node0.routing_table.insert(node5_contact) 384 | node0.routing_table.insert(node12_contact) 385 | 386 | node12.routing_table.insert(node14_contact) 387 | node0.store('15', 'some_address', node14_contact) 388 | 389 | result = node0.iterative_find_value('15') 390 | assert_instance_of(String, result) 391 | assert_equal('some_address', result) 392 | # store in second closest node 393 | assert_equal(['some_address'], node4.dht_segment['15']) 394 | end 395 | 396 | def test_iterative_find_value_with_no_match 397 | node0 = Node.new('0', @kn) 398 | node4 = Node.new('4', @kn) 399 | node5 = Node.new('5', @kn) 400 | node12 = Node.new('12', @kn) 401 | node14 = Node.new('14', @kn) 402 | 403 | node4_contact = node4.to_contact 404 | node5_contact = node5.to_contact 405 | node12_contact = node12.to_contact 406 | node14_contact = node14.to_contact 407 | 408 | node0.routing_table.insert(node4_contact) 409 | node0.routing_table.insert(node5_contact) 410 | node0.routing_table.insert(node12_contact) 411 | 412 | node12.routing_table.insert(node14_contact) 413 | node0.store('13', 'some_address', node14_contact) 414 | 415 | result = node0.iterative_find_value('15') 416 | assert_instance_of(Array, result) 417 | assert_equal(4, result.size) 418 | ids = result.map(&:id) 419 | assert_includes(ids, node4_contact.id) 420 | assert_includes(ids, node5_contact.id) 421 | assert_includes(ids, node14_contact.id) 422 | assert_includes(ids, node12_contact.id) 423 | end 424 | end 425 | --------------------------------------------------------------------------------