bitcourier 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +15 -0
- data/.gitignore +18 -0
- data/.travis.yml +13 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +34 -0
- data/Rakefile +10 -0
- data/bin/bitcourier +19 -0
- data/bitcourier.gemspec +27 -0
- data/lib/bitcourier.rb +11 -0
- data/lib/bitcourier/daemon.rb +33 -0
- data/lib/bitcourier/network.rb +5 -0
- data/lib/bitcourier/network/client.rb +65 -0
- data/lib/bitcourier/network/server.rb +32 -0
- data/lib/bitcourier/node.rb +172 -0
- data/lib/bitcourier/node_manager.rb +35 -0
- data/lib/bitcourier/peer.rb +46 -0
- data/lib/bitcourier/peer_list.rb +72 -0
- data/lib/bitcourier/protocol.rb +1 -0
- data/lib/bitcourier/protocol/message.rb +4 -0
- data/lib/bitcourier/protocol/message/base.rb +62 -0
- data/lib/bitcourier/protocol/message/get_peer_list.rb +9 -0
- data/lib/bitcourier/protocol/message/hello.rb +29 -0
- data/lib/bitcourier/protocol/message/peer_info.rb +34 -0
- data/lib/bitcourier/version.rb +3 -0
- data/spec/bitcourier/protocol/message/base_spec.rb +45 -0
- data/spec/bitcourier/protocol/message/hello_spec.rb +33 -0
- data/spec/bitcourier/protocol/message/peer_info_spec.rb +29 -0
- data/spec/spec_helper.rb +17 -0
- metadata +152 -0
checksums.yaml
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
---
|
2
|
+
!binary "U0hBMQ==":
|
3
|
+
metadata.gz: !binary |-
|
4
|
+
NThjZTExNWE0MzhkMzhiM2EyYTE3OTIwMzJiYmZhOGJlMGIyM2JlNA==
|
5
|
+
data.tar.gz: !binary |-
|
6
|
+
MDlmYTFiM2YzNDg4Mjk2MWFiNGFiZDYxNDgxYTgxZWNmNjM1ZDAyOQ==
|
7
|
+
SHA512:
|
8
|
+
metadata.gz: !binary |-
|
9
|
+
Mzg0NzdkNWJjZDQ2MTZmY2EzZDZmMDg3Njk4OGI1MDllNWY1MGQ3ZjIxZDAy
|
10
|
+
OWE5MjZjMDJhYjg0ZjljZjM4YjBlMTdlNDEzNDFiMGY1YmZkYjQ2MjQ2ZjQ4
|
11
|
+
NDZlYjczNjFiZThlYjUxNjkxMTg1ODc4MzY0ZGYzNzQ4ZmU2NTQ=
|
12
|
+
data.tar.gz: !binary |-
|
13
|
+
NjljMjk3ZmJjYjEzYzkxYTIyY2JkMDdmNGQzMDIzNDdmODYwNDJmMGY4NWVm
|
14
|
+
NGFkOGQ2ZWUxMjRlZTZkYTg4N2VkYTA3OWU0MDZkMDkwMWExZjQxZjY2MDZl
|
15
|
+
NGIzNGUwNWE4NDg0YmVkNjM5MzU1Mzk2NGMyY2U5NGIxMWRkNTQ=
|
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Karol Sarnacki
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
# Bitcourier
|
2
|
+
|
3
|
+
[](https://travis-ci.org/elpassion/bitcourier)
|
4
|
+
[](https://gemnasium.com/elpassion/bitcourier)
|
5
|
+
|
6
|
+
Decentralized and trustless communication protocol used to anonymously send encrypted messages via peer-to-peer network.
|
7
|
+
|
8
|
+
## Installation
|
9
|
+
|
10
|
+
Add this line to your application's Gemfile:
|
11
|
+
|
12
|
+
gem 'bitcourier'
|
13
|
+
|
14
|
+
And then execute:
|
15
|
+
|
16
|
+
$ bundle
|
17
|
+
|
18
|
+
Or install it yourself as:
|
19
|
+
|
20
|
+
$ gem install bitcourier
|
21
|
+
|
22
|
+
## Usage
|
23
|
+
|
24
|
+
Start Bitcourier node on port 6081 (this is the default port):
|
25
|
+
|
26
|
+
$ bitcourier node -p 6081
|
27
|
+
|
28
|
+
## Contributing
|
29
|
+
|
30
|
+
1. Fork it
|
31
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
32
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
33
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
34
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
data/bin/bitcourier
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
$:.unshift File.dirname(__FILE__) + '/../lib'
|
4
|
+
|
5
|
+
require 'thor'
|
6
|
+
require 'bitcourier'
|
7
|
+
|
8
|
+
class BitcourierCli < Thor
|
9
|
+
|
10
|
+
desc 'node', 'Start Bitcourier node'
|
11
|
+
method_option :port, default: 6081, aliases: '-p', desc: 'port on which the Bitcourier node will listen'
|
12
|
+
def node
|
13
|
+
daemon = Bitcourier::Daemon.new
|
14
|
+
daemon.run(options.port)
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
|
19
|
+
BitcourierCli.start
|
data/bitcourier.gemspec
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'bitcourier/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = 'bitcourier'
|
8
|
+
spec.version = Bitcourier::VERSION
|
9
|
+
spec.authors = ['EL Passion']
|
10
|
+
spec.email = ['account@elpassion.com']
|
11
|
+
spec.description = %q{Decentralized and trustless communication protocol used to anonymously send encrypted messages via peer-to-peer network}
|
12
|
+
spec.summary = %q{Decentralized and trustless communication protocol used to anonymously send encrypted messages via peer-to-peer network}
|
13
|
+
spec.homepage = 'https://github.com/elpassion/bitcourier'
|
14
|
+
spec.license = 'MIT'
|
15
|
+
|
16
|
+
spec.files = `git ls-files`.split($/)
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ['lib']
|
20
|
+
|
21
|
+
spec.add_dependency 'thor', '~> 0.18.1'
|
22
|
+
|
23
|
+
spec.add_development_dependency 'bundler', '~> 1.3'
|
24
|
+
spec.add_development_dependency 'minitest', '>= 5.0.8'
|
25
|
+
spec.add_development_dependency 'rake'
|
26
|
+
spec.add_development_dependency 'simplecov', '>= 0.7.1'
|
27
|
+
end
|
data/lib/bitcourier.rb
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
require 'bitcourier/protocol'
|
2
|
+
require 'bitcourier/network'
|
3
|
+
require 'bitcourier/node'
|
4
|
+
require 'bitcourier/node_manager'
|
5
|
+
require 'bitcourier/daemon'
|
6
|
+
require 'bitcourier/peer'
|
7
|
+
require 'bitcourier/peer_list'
|
8
|
+
require 'bitcourier/version'
|
9
|
+
|
10
|
+
module Bitcourier
|
11
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'securerandom'
|
2
|
+
|
3
|
+
module Bitcourier
|
4
|
+
|
5
|
+
class Daemon
|
6
|
+
attr_accessor :server, :client, :node_manager, :peer_list, :nonce
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
self.server = Network::Server.new self
|
10
|
+
self.node_manager = NodeManager.new self
|
11
|
+
self.client = Network::Client.new self
|
12
|
+
self.peer_list = PeerList.new
|
13
|
+
self.nonce = SecureRandom.random_number(2**64)
|
14
|
+
end
|
15
|
+
|
16
|
+
def start
|
17
|
+
server_thread = server.run
|
18
|
+
client_thread = client.run
|
19
|
+
|
20
|
+
server_thread.join
|
21
|
+
client_thread.join
|
22
|
+
end
|
23
|
+
|
24
|
+
def run(port)
|
25
|
+
server.port = port
|
26
|
+
|
27
|
+
start
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
|
33
|
+
Thread.abort_on_exception = true
|
@@ -0,0 +1,65 @@
|
|
1
|
+
require 'timeout'
|
2
|
+
|
3
|
+
module Bitcourier
|
4
|
+
module Network
|
5
|
+
class Client
|
6
|
+
PEER_CONNECTION_RETRY_DELAY = 5 # In seconds
|
7
|
+
NEXT_PEER_DELAY = 5
|
8
|
+
CONNECT_TIMEOUT = 5
|
9
|
+
|
10
|
+
def initialize context
|
11
|
+
@context = context
|
12
|
+
end
|
13
|
+
|
14
|
+
def run
|
15
|
+
@thread = Thread.new do
|
16
|
+
loop do
|
17
|
+
if @context.node_manager.needs_nodes?
|
18
|
+
if peer_connection = next_peer_connection
|
19
|
+
@context.node_manager.add_socket peer_connection, true
|
20
|
+
else
|
21
|
+
sleep(NEXT_PEER_DELAY)
|
22
|
+
end
|
23
|
+
else
|
24
|
+
sleep(NEXT_PEER_DELAY)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def next_peer_connection
|
33
|
+
socket = nil
|
34
|
+
|
35
|
+
if peer = @context.peer_list.next
|
36
|
+
print "Connecting to #{peer.ip}:#{peer.port}... "
|
37
|
+
|
38
|
+
timeout(CONNECT_TIMEOUT) do
|
39
|
+
socket = TCPSocket.new(peer.ip, peer.port)
|
40
|
+
end
|
41
|
+
|
42
|
+
print "Connected.\n"
|
43
|
+
|
44
|
+
peer.touch
|
45
|
+
@context.peer_list.store(peer)
|
46
|
+
end
|
47
|
+
rescue Errno::ECONNREFUSED
|
48
|
+
print "Connection refused.\n"
|
49
|
+
|
50
|
+
peer.retry_in PEER_CONNECTION_RETRY_DELAY
|
51
|
+
@context.peer_list.store(peer)
|
52
|
+
rescue Errno::ETIMEDOUT, Timeout::Error
|
53
|
+
print "Timed out.\n"
|
54
|
+
|
55
|
+
peer.retry_in PEER_CONNECTION_RETRY_DELAY
|
56
|
+
@context.peer_list.store(peer)
|
57
|
+
rescue Exception => e
|
58
|
+
print "#{e.class}: #{e}\n"
|
59
|
+
puts e.backtrace
|
60
|
+
ensure
|
61
|
+
return socket
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'socket'
|
2
|
+
module Bitcourier
|
3
|
+
module Network
|
4
|
+
class Server
|
5
|
+
DEFAULT_PORT = 6081
|
6
|
+
|
7
|
+
attr_accessor :port
|
8
|
+
|
9
|
+
def initialize context, port = DEFAULT_PORT
|
10
|
+
@port = port
|
11
|
+
@context = context
|
12
|
+
end
|
13
|
+
|
14
|
+
def run
|
15
|
+
@server = TCPServer.new @port
|
16
|
+
|
17
|
+
puts "Started server on port #{@port}"
|
18
|
+
|
19
|
+
@thread = Thread.new do
|
20
|
+
loop do
|
21
|
+
socket = @server.accept
|
22
|
+
@context.node_manager.add_socket socket, false
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def ip_string
|
28
|
+
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,172 @@
|
|
1
|
+
module Bitcourier
|
2
|
+
class Node
|
3
|
+
|
4
|
+
class State
|
5
|
+
class Unimplemented < StandardError; end
|
6
|
+
|
7
|
+
attr_accessor :node
|
8
|
+
|
9
|
+
def initialize node
|
10
|
+
self.node = node
|
11
|
+
end
|
12
|
+
|
13
|
+
def method_missing method, args
|
14
|
+
name = method.id2name
|
15
|
+
|
16
|
+
if name.start_with? 'on_'
|
17
|
+
puts "Can't handle #{name[3..-1]} in current state"
|
18
|
+
else
|
19
|
+
super(method, args)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def set_state state
|
24
|
+
node.set_state state
|
25
|
+
end
|
26
|
+
|
27
|
+
def on_enter
|
28
|
+
end
|
29
|
+
|
30
|
+
def on_leave
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
class ActiveHandshake < State
|
35
|
+
def on_enter
|
36
|
+
node.send_hello
|
37
|
+
end
|
38
|
+
|
39
|
+
def on_hello msg
|
40
|
+
if msg.protocol_version == Protocol::Message::Hello::PROTOCOL_VERSION
|
41
|
+
node.remote_port = msg.port
|
42
|
+
set_state ReadyState
|
43
|
+
else
|
44
|
+
node.disconnect
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
class PassiveHandshake < State
|
50
|
+
def on_hello msg
|
51
|
+
version_ok = msg.protocol_version == Protocol::Message::Hello::PROTOCOL_VERSION
|
52
|
+
nonce_ok = msg.nonce != node.context.nonce
|
53
|
+
|
54
|
+
if version_ok and nonce_ok
|
55
|
+
node.remote_port = msg.port
|
56
|
+
node.send_hello
|
57
|
+
set_state ReadyState
|
58
|
+
else
|
59
|
+
node.disconnect
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
class ReadyState < State
|
65
|
+
def on_enter
|
66
|
+
node.remember_peer
|
67
|
+
node.send_message(Protocol::Message::GetPeerList.new)
|
68
|
+
end
|
69
|
+
|
70
|
+
def on_get_peer_list msg
|
71
|
+
node.send_peer_list
|
72
|
+
end
|
73
|
+
|
74
|
+
def on_peer_info msg
|
75
|
+
node.on_peer_info(msg)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
attr_accessor :socket, :context, :remote_port
|
80
|
+
|
81
|
+
def initialize(context, socket)
|
82
|
+
self.socket = socket
|
83
|
+
self.context = context
|
84
|
+
end
|
85
|
+
|
86
|
+
def set_state state
|
87
|
+
self.state.on_leave if self.state
|
88
|
+
self.state = state.new(self)
|
89
|
+
self.state.on_enter
|
90
|
+
end
|
91
|
+
|
92
|
+
def disconnect
|
93
|
+
socket.close
|
94
|
+
end
|
95
|
+
|
96
|
+
def remember_peer
|
97
|
+
remote_ip = socket.peeraddr[3]
|
98
|
+
peer = Peer.new remote_ip, remote_port
|
99
|
+
|
100
|
+
context.peer_list.store peer
|
101
|
+
end
|
102
|
+
|
103
|
+
def send_hello
|
104
|
+
msg = Protocol::Message::Hello.new
|
105
|
+
msg.port = context.server.port
|
106
|
+
msg.nonce = context.nonce
|
107
|
+
send_message msg
|
108
|
+
end
|
109
|
+
|
110
|
+
def send_peer_list
|
111
|
+
context.peer_list.peers.each do |peer|
|
112
|
+
msg = Protocol::Message::PeerInfo.new
|
113
|
+
msg.ip = peer.ip
|
114
|
+
msg.port = peer.port
|
115
|
+
msg.last_seen_at = peer.last_seen_at
|
116
|
+
send_message msg
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
def send_message msg
|
121
|
+
data = msg.pack
|
122
|
+
socket.write data
|
123
|
+
end
|
124
|
+
|
125
|
+
def on_peer_info msg
|
126
|
+
context.peer_list.store Peer.new(msg.ip, msg.port, msg.last_seen_at)
|
127
|
+
end
|
128
|
+
|
129
|
+
def on_message msg
|
130
|
+
return if state.nil?
|
131
|
+
|
132
|
+
case msg
|
133
|
+
when Protocol::Message::Hello
|
134
|
+
state.on_hello(msg)
|
135
|
+
when Protocol::Message::GetPeerList
|
136
|
+
state.on_get_peer_list(msg)
|
137
|
+
when Protocol::Message::PeerInfo
|
138
|
+
state.on_peer_info(msg)
|
139
|
+
else
|
140
|
+
puts "Don't know how to handle message class #{msg.class}"
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
def run
|
145
|
+
@thread = Thread.new do
|
146
|
+
buffer = ''
|
147
|
+
|
148
|
+
begin
|
149
|
+
loop do
|
150
|
+
data = socket.recv(1024)
|
151
|
+
|
152
|
+
break if data.length == 0
|
153
|
+
|
154
|
+
buffer += data
|
155
|
+
|
156
|
+
while (message_size = Protocol::Message::Base.message_size(buffer)) > 0
|
157
|
+
message_data = buffer.slice!(0, message_size)
|
158
|
+
message = Protocol::Message::Base.unpack(message_data)
|
159
|
+
on_message message
|
160
|
+
end
|
161
|
+
end
|
162
|
+
rescue IOError
|
163
|
+
puts "Connection closed"
|
164
|
+
end
|
165
|
+
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
attr_accessor :state
|
170
|
+
|
171
|
+
end
|
172
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module Bitcourier
|
2
|
+
class NodeManager
|
3
|
+
MINIMUM_NODES = 3
|
4
|
+
|
5
|
+
attr_accessor :context
|
6
|
+
|
7
|
+
def initialize context
|
8
|
+
self.nodes = []
|
9
|
+
self.context = context
|
10
|
+
end
|
11
|
+
|
12
|
+
def needs_nodes?
|
13
|
+
nodes.count < MINIMUM_NODES
|
14
|
+
end
|
15
|
+
|
16
|
+
def add_socket socket, active = false
|
17
|
+
node = Node.new context, socket
|
18
|
+
|
19
|
+
if active
|
20
|
+
node.set_state Node::ActiveHandshake
|
21
|
+
else
|
22
|
+
node.set_state Node::PassiveHandshake
|
23
|
+
end
|
24
|
+
|
25
|
+
nodes << node
|
26
|
+
|
27
|
+
node.run
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
attr_accessor :nodes
|
33
|
+
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module Bitcourier
|
2
|
+
class Peer
|
3
|
+
attr_accessor :ip, :port, :last_seen_at, :next_connection_at
|
4
|
+
|
5
|
+
def initialize ip, port, last_seen_at = nil, next_connection_at = Time.now.utc
|
6
|
+
self.ip = ip
|
7
|
+
self.port = port
|
8
|
+
self.last_seen_at = last_seen_at
|
9
|
+
self.next_connection_at = next_connection_at
|
10
|
+
end
|
11
|
+
|
12
|
+
def touch(timestamp = Time.now.utc)
|
13
|
+
self.last_seen_at = [last_seen_at || Time.at(0).utc, timestamp || Time.now.utc].max
|
14
|
+
end
|
15
|
+
|
16
|
+
def update(peer)
|
17
|
+
touch(peer.last_seen_at)
|
18
|
+
self.next_connection_at = peer.next_connection_at
|
19
|
+
end
|
20
|
+
|
21
|
+
def can_connect?(time = Time.now.utc)
|
22
|
+
next_connection_at < time
|
23
|
+
end
|
24
|
+
|
25
|
+
def retry_in delay
|
26
|
+
self.next_connection_at = Time.now + delay
|
27
|
+
end
|
28
|
+
|
29
|
+
def to_a
|
30
|
+
[
|
31
|
+
ip,
|
32
|
+
port,
|
33
|
+
last_seen_at && last_seen_at.to_i,
|
34
|
+
next_connection_at && next_connection_at.to_i
|
35
|
+
]
|
36
|
+
end
|
37
|
+
|
38
|
+
def same?(other)
|
39
|
+
other && (ip == other.ip) && (port == other.port)
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.from_a a
|
43
|
+
new(a[0], a[1].to_i, a[2] && Time.at(a[2].to_i).utc, a[3] && Time.at(a[3].to_i).utc)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
module Bitcourier
|
2
|
+
class PeerList
|
3
|
+
attr_reader :peers
|
4
|
+
|
5
|
+
def initialize
|
6
|
+
@peers = []
|
7
|
+
@storage = Storage.new(self)
|
8
|
+
|
9
|
+
load
|
10
|
+
end
|
11
|
+
|
12
|
+
def store(peer)
|
13
|
+
if existing = find(peer)
|
14
|
+
existing.update(peer)
|
15
|
+
else
|
16
|
+
peers << peer
|
17
|
+
end
|
18
|
+
|
19
|
+
save
|
20
|
+
end
|
21
|
+
|
22
|
+
def next
|
23
|
+
peers.select(&:can_connect?).sample
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
attr_reader :storage
|
29
|
+
|
30
|
+
def find(peer)
|
31
|
+
peers.detect { |existing| existing.same?(peer) }
|
32
|
+
end
|
33
|
+
|
34
|
+
def save
|
35
|
+
storage.write
|
36
|
+
end
|
37
|
+
|
38
|
+
def load
|
39
|
+
storage.read
|
40
|
+
|
41
|
+
seed if peers.size == 0
|
42
|
+
end
|
43
|
+
|
44
|
+
def seed
|
45
|
+
store Peer.new('146.185.167.10', 6081)
|
46
|
+
end
|
47
|
+
|
48
|
+
class Storage
|
49
|
+
PATH = './tmp/peer_list.txt'
|
50
|
+
|
51
|
+
def initialize list
|
52
|
+
@list = list
|
53
|
+
end
|
54
|
+
|
55
|
+
def read
|
56
|
+
File.read(PATH).lines.map do |line|
|
57
|
+
@list.store Peer.from_a(line.split('|'))
|
58
|
+
end
|
59
|
+
rescue Errno::ENOENT
|
60
|
+
return []
|
61
|
+
end
|
62
|
+
|
63
|
+
def write
|
64
|
+
File.open(PATH, 'w') do |file|
|
65
|
+
@list.peers.each do |peer|
|
66
|
+
file.puts peer.to_a.join('|')
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'bitcourier/protocol/message'
|
@@ -0,0 +1,62 @@
|
|
1
|
+
module Bitcourier
|
2
|
+
module Protocol
|
3
|
+
module Message
|
4
|
+
class Base
|
5
|
+
MAGIC_BYTES = 0x1234
|
6
|
+
HEADER_SIZE = 6
|
7
|
+
|
8
|
+
def header
|
9
|
+
[MAGIC_BYTES, self.class::ID, payload.size].pack('SSS')
|
10
|
+
end
|
11
|
+
|
12
|
+
def payload
|
13
|
+
''
|
14
|
+
end
|
15
|
+
|
16
|
+
def pack
|
17
|
+
header + payload
|
18
|
+
end
|
19
|
+
|
20
|
+
def extract bytes
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.unpack bytes
|
24
|
+
magic, id, length = unpack_header(bytes)
|
25
|
+
|
26
|
+
return nil if magic != MAGIC_BYTES # wrong magic
|
27
|
+
return nil if length + HEADER_SIZE > bytes.size # partial package
|
28
|
+
return nil unless klass = find_message_class(id)
|
29
|
+
|
30
|
+
msg = klass.new
|
31
|
+
msg.extract bytes[HEADER_SIZE..-1]
|
32
|
+
|
33
|
+
msg
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.message_size bytes
|
37
|
+
return 0 if bytes.size < HEADER_SIZE
|
38
|
+
|
39
|
+
_, _, length = unpack_header(bytes)
|
40
|
+
|
41
|
+
return 0 if bytes.size < (length + HEADER_SIZE)
|
42
|
+
|
43
|
+
return length + HEADER_SIZE
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
def self.unpack_header bytes
|
49
|
+
bytes[0...HEADER_SIZE].unpack('SSS')
|
50
|
+
end
|
51
|
+
|
52
|
+
def self.find_message_class id
|
53
|
+
[Hello, GetPeerList, PeerInfo].each do |message_class|
|
54
|
+
return message_class if message_class::ID == id
|
55
|
+
end
|
56
|
+
|
57
|
+
nil
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Bitcourier
|
2
|
+
module Protocol
|
3
|
+
module Message
|
4
|
+
class Hello < Base
|
5
|
+
ID = 0x1
|
6
|
+
PROTOCOL_VERSION = 1
|
7
|
+
|
8
|
+
attr_accessor :protocol_version, :port, :nonce
|
9
|
+
|
10
|
+
def initialize
|
11
|
+
self.protocol_version = PROTOCOL_VERSION
|
12
|
+
end
|
13
|
+
|
14
|
+
def payload
|
15
|
+
[protocol_version.to_i, port.to_i, nonce.to_i].pack('SSQ')
|
16
|
+
end
|
17
|
+
|
18
|
+
def extract bytes
|
19
|
+
data = bytes.unpack('SSQ')
|
20
|
+
|
21
|
+
self.protocol_version = data[0]
|
22
|
+
self.port = data[1]
|
23
|
+
self.nonce = data[2]
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module Bitcourier
|
2
|
+
module Protocol
|
3
|
+
module Message
|
4
|
+
|
5
|
+
class PeerInfo < Base
|
6
|
+
ID = 0x3
|
7
|
+
|
8
|
+
attr_accessor :ip, :port, :last_seen_at
|
9
|
+
|
10
|
+
def payload
|
11
|
+
[ip_array, port.to_i, last_seen_at.to_i].flatten.pack('CCCCSL')
|
12
|
+
end
|
13
|
+
|
14
|
+
def extract bytes
|
15
|
+
data = bytes.unpack('CCCCSL')
|
16
|
+
|
17
|
+
self.ip = data[0..3].join('.')
|
18
|
+
self.port = data[4].to_i
|
19
|
+
self.last_seen_at = Time.at(data[5].to_i).utc
|
20
|
+
|
21
|
+
self
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def ip_array
|
27
|
+
ip.split('.').map(&:to_i)
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Bitcourier::Protocol::Message::Base do
|
4
|
+
|
5
|
+
describe '::unpack' do
|
6
|
+
|
7
|
+
describe 'given GetPeerList packet bytes' do
|
8
|
+
it 'returns GetPeerList message object' do
|
9
|
+
get_peer_list_bytes = "4\x12\x02\x00\x00\x00".force_encoding('ASCII-8BIT')
|
10
|
+
get_peer_list = Bitcourier::Protocol::Message::Base.unpack(get_peer_list_bytes)
|
11
|
+
|
12
|
+
get_peer_list.must_be_instance_of Bitcourier::Protocol::Message::GetPeerList
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
describe 'given Hello packet bytes' do
|
17
|
+
it 'returns Hello message object' do
|
18
|
+
hello_bytes = "4\x12\x01\x00\f\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00".force_encoding('ASCII-8BIT')
|
19
|
+
hello = Bitcourier::Protocol::Message::Base.unpack(hello_bytes)
|
20
|
+
|
21
|
+
hello.must_be_instance_of Bitcourier::Protocol::Message::Hello
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
describe 'given PeerInfo packet bytes' do
|
26
|
+
it 'returns PeerInfo message object' do
|
27
|
+
peer_info_bytes = "4\x12\x03\x00\n\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00".force_encoding('ASCII-8BIT')
|
28
|
+
peer_info = Bitcourier::Protocol::Message::Base.unpack(peer_info_bytes)
|
29
|
+
|
30
|
+
peer_info.must_be_instance_of Bitcourier::Protocol::Message::PeerInfo
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
describe 'given unknown packet bytes' do
|
35
|
+
it 'returns nil' do
|
36
|
+
unknown_bytes = "4\x12\xFF\x00\x00\x00".force_encoding('ASCII-8BIT')
|
37
|
+
unknown = Bitcourier::Protocol::Message::Base.unpack(unknown_bytes)
|
38
|
+
|
39
|
+
unknown.must_be_nil
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Bitcourier::Protocol::Message::Hello do
|
4
|
+
before do
|
5
|
+
@hello = Bitcourier::Protocol::Message::Hello.new
|
6
|
+
@hello_bytes = "{\x00\x11\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF".force_encoding('ASCII-8BIT')
|
7
|
+
end
|
8
|
+
|
9
|
+
it 'initiates with default version number' do
|
10
|
+
@hello.protocol_version.must_equal Bitcourier::Protocol::Message::Hello::PROTOCOL_VERSION
|
11
|
+
end
|
12
|
+
|
13
|
+
describe '#payload' do
|
14
|
+
it 'returns payload bytes' do
|
15
|
+
@hello.protocol_version = 123
|
16
|
+
@hello.port = 17
|
17
|
+
@hello.nonce = 2**64 - 1
|
18
|
+
|
19
|
+
@hello.payload.must_equal @hello_bytes
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
describe '#extract' do
|
24
|
+
it 'extracts payload bytes to object values' do
|
25
|
+
@hello.extract @hello_bytes
|
26
|
+
|
27
|
+
@hello.protocol_version.must_equal 123
|
28
|
+
@hello.port.must_equal 17
|
29
|
+
@hello.nonce.must_equal 2**64 - 1
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Bitcourier::Protocol::Message::PeerInfo do
|
4
|
+
before do
|
5
|
+
@peer_info = Bitcourier::Protocol::Message::PeerInfo.new
|
6
|
+
@peer_info_bytes = "\x01\x02\x03\x04\xFF\xFF\xAF5\xA24".force_encoding('ASCII-8BIT')
|
7
|
+
end
|
8
|
+
|
9
|
+
describe '#payload' do
|
10
|
+
it 'returns payload bytes' do
|
11
|
+
@peer_info.ip = '1.2.3.4'
|
12
|
+
@peer_info.port = 65535
|
13
|
+
@peer_info.last_seen_at = Time.utc(1997, 12, 25, 10, 30, 7)
|
14
|
+
|
15
|
+
@peer_info.payload.must_equal @peer_info_bytes
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
describe '#extract' do
|
20
|
+
it 'extracts payload bytes to object values' do
|
21
|
+
@peer_info.extract @peer_info_bytes
|
22
|
+
|
23
|
+
@peer_info.ip.must_equal '1.2.3.4'
|
24
|
+
@peer_info.port.must_equal 65535
|
25
|
+
@peer_info.last_seen_at.must_equal Time.utc(1997, 12, 25, 10, 30, 7)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
$LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
|
2
|
+
|
3
|
+
if RUBY_VERSION < '1.9'
|
4
|
+
class String
|
5
|
+
def force_encoding(encoding)
|
6
|
+
self
|
7
|
+
end
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
gem 'minitest' # ensure we are using the gem version
|
12
|
+
|
13
|
+
require 'bitcourier'
|
14
|
+
require 'minitest/autorun'
|
15
|
+
require 'simplecov'
|
16
|
+
|
17
|
+
SimpleCov.start
|
metadata
ADDED
@@ -0,0 +1,152 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: bitcourier
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- EL Passion
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2013-10-26 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: thor
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ~>
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 0.18.1
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ~>
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 0.18.1
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: bundler
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ~>
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1.3'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ~>
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1.3'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: minitest
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ! '>='
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 5.0.8
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ! '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 5.0.8
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rake
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ! '>='
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: simplecov
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ! '>='
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: 0.7.1
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ! '>='
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: 0.7.1
|
83
|
+
description: Decentralized and trustless communication protocol used to anonymously
|
84
|
+
send encrypted messages via peer-to-peer network
|
85
|
+
email:
|
86
|
+
- account@elpassion.com
|
87
|
+
executables:
|
88
|
+
- bitcourier
|
89
|
+
extensions: []
|
90
|
+
extra_rdoc_files: []
|
91
|
+
files:
|
92
|
+
- .gitignore
|
93
|
+
- .travis.yml
|
94
|
+
- Gemfile
|
95
|
+
- Gemfile.lock
|
96
|
+
- LICENSE.txt
|
97
|
+
- README.md
|
98
|
+
- Rakefile
|
99
|
+
- bin/bitcourier
|
100
|
+
- bitcourier.gemspec
|
101
|
+
- lib/bitcourier.rb
|
102
|
+
- lib/bitcourier/daemon.rb
|
103
|
+
- lib/bitcourier/network.rb
|
104
|
+
- lib/bitcourier/network/client.rb
|
105
|
+
- lib/bitcourier/network/server.rb
|
106
|
+
- lib/bitcourier/node.rb
|
107
|
+
- lib/bitcourier/node_manager.rb
|
108
|
+
- lib/bitcourier/peer.rb
|
109
|
+
- lib/bitcourier/peer_list.rb
|
110
|
+
- lib/bitcourier/protocol.rb
|
111
|
+
- lib/bitcourier/protocol/message.rb
|
112
|
+
- lib/bitcourier/protocol/message/base.rb
|
113
|
+
- lib/bitcourier/protocol/message/get_peer_list.rb
|
114
|
+
- lib/bitcourier/protocol/message/hello.rb
|
115
|
+
- lib/bitcourier/protocol/message/peer_info.rb
|
116
|
+
- lib/bitcourier/version.rb
|
117
|
+
- spec/bitcourier/protocol/message/base_spec.rb
|
118
|
+
- spec/bitcourier/protocol/message/hello_spec.rb
|
119
|
+
- spec/bitcourier/protocol/message/peer_info_spec.rb
|
120
|
+
- spec/spec_helper.rb
|
121
|
+
- tmp/.gitkeep
|
122
|
+
homepage: https://github.com/elpassion/bitcourier
|
123
|
+
licenses:
|
124
|
+
- MIT
|
125
|
+
metadata: {}
|
126
|
+
post_install_message:
|
127
|
+
rdoc_options: []
|
128
|
+
require_paths:
|
129
|
+
- lib
|
130
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
131
|
+
requirements:
|
132
|
+
- - ! '>='
|
133
|
+
- !ruby/object:Gem::Version
|
134
|
+
version: '0'
|
135
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
136
|
+
requirements:
|
137
|
+
- - ! '>='
|
138
|
+
- !ruby/object:Gem::Version
|
139
|
+
version: '0'
|
140
|
+
requirements: []
|
141
|
+
rubyforge_project:
|
142
|
+
rubygems_version: 2.1.10
|
143
|
+
signing_key:
|
144
|
+
specification_version: 4
|
145
|
+
summary: Decentralized and trustless communication protocol used to anonymously send
|
146
|
+
encrypted messages via peer-to-peer network
|
147
|
+
test_files:
|
148
|
+
- spec/bitcourier/protocol/message/base_spec.rb
|
149
|
+
- spec/bitcourier/protocol/message/hello_spec.rb
|
150
|
+
- spec/bitcourier/protocol/message/peer_info_spec.rb
|
151
|
+
- spec/spec_helper.rb
|
152
|
+
has_rdoc:
|