nova 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (55) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +19 -0
  3. data/README.md +29 -0
  4. data/bin/nova +8 -0
  5. data/lib/generator/template/new_install/galaxy/some_star.rb +3 -0
  6. data/lib/generator/template/new_install/supernova.yml +16 -0
  7. data/lib/nova.rb +49 -0
  8. data/lib/nova/cli.rb +62 -0
  9. data/lib/nova/commands/server.rb +71 -0
  10. data/lib/nova/common.rb +17 -0
  11. data/lib/nova/common/event_handler.rb +165 -0
  12. data/lib/nova/common/event_handler/event.rb +147 -0
  13. data/lib/nova/common/features.rb +93 -0
  14. data/lib/nova/common/features/feature.rb +65 -0
  15. data/lib/nova/common/metadata.rb +65 -0
  16. data/lib/nova/common/metadata/data.rb +171 -0
  17. data/lib/nova/common/star_management.rb +164 -0
  18. data/lib/nova/constructor.rb +84 -0
  19. data/lib/nova/exceptions.rb +16 -0
  20. data/lib/nova/project.rb +199 -0
  21. data/lib/nova/remote.rb +10 -0
  22. data/lib/nova/remote/fake.rb +51 -0
  23. data/lib/nova/remote/fake/commands.rb +44 -0
  24. data/lib/nova/remote/fake/file_system.rb +76 -0
  25. data/lib/nova/remote/fake/operating_system.rb +52 -0
  26. data/lib/nova/remote/fake/platform.rb +89 -0
  27. data/lib/nova/star.rb +25 -0
  28. data/lib/nova/starbound.rb +14 -0
  29. data/lib/nova/starbound/client.rb +59 -0
  30. data/lib/nova/starbound/encryptor.rb +134 -0
  31. data/lib/nova/starbound/encryptors.rb +13 -0
  32. data/lib/nova/starbound/encryptors/openssl.rb +122 -0
  33. data/lib/nova/starbound/encryptors/plaintext.rb +64 -0
  34. data/lib/nova/starbound/encryptors/rbnacl.rb +67 -0
  35. data/lib/nova/starbound/protocol.rb +81 -0
  36. data/lib/nova/starbound/protocol/encryption.rb +48 -0
  37. data/lib/nova/starbound/protocol/exceptions.rb +38 -0
  38. data/lib/nova/starbound/protocol/messages.rb +116 -0
  39. data/lib/nova/starbound/protocol/packet.rb +267 -0
  40. data/lib/nova/starbound/protocol/socket.rb +231 -0
  41. data/lib/nova/starbound/server.rb +182 -0
  42. data/lib/nova/version.rb +5 -0
  43. data/spec/constructor_spec.rb +20 -0
  44. data/spec/local_spec.rb +26 -0
  45. data/spec/nova_spec.rb +7 -0
  46. data/spec/spec_helper.rb +19 -0
  47. data/spec/star/some_type.rb +27 -0
  48. data/spec/star_spec.rb +107 -0
  49. data/spec/starbound/encryptor_spec.rb +33 -0
  50. data/spec/starbound/openssl_encryptor_spec.rb +80 -0
  51. data/spec/starbound/packet_spec.rb +61 -0
  52. data/spec/starbound/plaintext_encryptor_spec.rb +27 -0
  53. data/spec/starbound/protocol_spec.rb +163 -0
  54. data/spec/starbound/rbnacl_encryptor_spec.rb +70 -0
  55. metadata +166 -0
@@ -0,0 +1,182 @@
1
+ require 'socket'
2
+
3
+ module Nova
4
+ module Starbound
5
+
6
+ # A Starbound server.
7
+ #
8
+ # @todo Default proc.
9
+ class Server
10
+
11
+ # The options passed to the server on initialization.
12
+ #
13
+ # @return [Hash]
14
+ attr_reader :options
15
+
16
+ # The default options.
17
+ DEFAULT_OPTIONS = {
18
+ :type => :tcp,
19
+ :host => "127.0.0.1",
20
+ :port => 2010,
21
+ :path => "/tmp/sock"
22
+ }
23
+
24
+ # Whether or not to run. Once this is set to false, the server
25
+ # stops listening for clients.
26
+ #
27
+ # @return [Boolean]
28
+ attr_accessor :run
29
+
30
+ # The list of active threads that are running.
31
+ #
32
+ # @return [Array<Thread>]
33
+ attr_reader :thread_list
34
+
35
+ # Initialize the server with the given options. If the options
36
+ # has a +:protocol+ key, it is removed from the options and set
37
+ # as the protocol options.
38
+ #
39
+ # @param options [Hash]
40
+ # @option options [Symbol] :type the type of server. Can be any
41
+ # of +:tcp+ or +:unix+. Defaults to +:tcp+.
42
+ # @option options [String] :host the host to bind to. Defaults
43
+ # to +"127.0.0.1"+. Only used if +:type+ is +:tcp+.
44
+ # @option options [Numeric] :port the port to bind to. Defaults
45
+ # to +2010+. Only used if +:type+ is +:tcp+.
46
+ # @option options [String] :path the path to the unix socket.
47
+ # Only used if +:type+ is +:unix+.
48
+ def initialize(options = {})
49
+ @options = DEFAULT_OPTIONS.merge options
50
+ @protocol_options = (options.delete(:protocol) || {}).dup
51
+ @run = true
52
+ @thread_list = []
53
+ end
54
+
55
+ # How to handle the client. Accepts a block, which is used in
56
+ # {#listen} when a client connects. If no block is given, the
57
+ # a block is returned.
58
+ #
59
+ # @yieldparam protocol [Protocol] the protocol instance that is
60
+ # used to handle the client. By the time it is yielded, the
61
+ # client has already done a handshake with the server.
62
+ # @return [Proc]
63
+ def with_client(&block)
64
+ if block_given?
65
+ @with_client = block
66
+ else
67
+ @with_client ||= proc {}
68
+ end
69
+ end
70
+
71
+ # Reads a ruby file in the instance of this server, so that it
72
+ # can build a {#with_client} block for it.
73
+ #
74
+ # @param file [String] the file to read from.
75
+ def read_client_file(file)
76
+ return unless file && File.exists?(file)
77
+
78
+ instance_eval File.open(file, "r").read, file, 1
79
+ end
80
+
81
+ # Listen for clients. Calls {#handle_client} when a client is
82
+ # found.
83
+ #
84
+ # @return [void]
85
+ def listen
86
+ Nova.logger.info { "Server started." }
87
+ while run
88
+ next unless IO.select [server], nil, nil, 1
89
+ thread_list << Thread.start(server.accept) do |client|
90
+ Nova.logger.info { "Client accepted." }
91
+
92
+ handle_client client
93
+
94
+ thread_list.delete(Thread.current)
95
+ Nova.logger.info { "Client disconnected." }
96
+ end
97
+ end
98
+ end
99
+
100
+ # Shuts down the server.
101
+ #
102
+ # @return [void]
103
+ def shutdown
104
+ puts "shutting down"
105
+ @run = false
106
+ thread_list.each do |thread|
107
+ thread.raise ExitError
108
+ end
109
+ end
110
+
111
+ # Returns the server type that this server should run as,
112
+ # already instantized.
113
+ #
114
+ # @return [Object]
115
+ def server
116
+ @_server ||= case options.fetch(:type, :tcp)
117
+ when :tcp
118
+ TCPServer.new options.fetch(:host, "127.0.0.1"),
119
+ options.fetch(:port, 2010)
120
+ when :unix
121
+ UNIXServer.new options.fetch(:path)
122
+ when :pipe
123
+ FakeServer.new options.fetch(:pipe)
124
+ end
125
+ end
126
+
127
+ private
128
+
129
+ # Handles a client after they connect. Creates a {Protocol},
130
+ # initiates the handshake, and calls {#with_client} to handle
131
+ # the protocol afterwards.
132
+ #
133
+ # @param client [IO] the client to handle.
134
+ # @return [void]
135
+ def handle_client(client)
136
+ protocol = Protocol.new(
137
+ @protocol_options.merge(:type => :server))
138
+
139
+ protocol.socket = client
140
+ protocol.handshake
141
+
142
+ with_client.call(protocol)
143
+
144
+ protocol.close(false)
145
+
146
+ rescue ExitError => e
147
+ Nova.logger.error { "Closing while client is connected. Notifying..." }
148
+ if protocol
149
+ protocol.close(:shutdown)
150
+ else
151
+ client.close unless client.closed?
152
+ end
153
+ rescue RemoteCloseError
154
+ rescue ProtocolError => e
155
+ Nova.logger.error { "Client failed: #{e.message} #{e.backtrace.join("\n")}" }
156
+ client.close unless client.closed?
157
+ ensure
158
+ protocol.close(false)
159
+ end
160
+
161
+ # A fake server that wraps a pipe.
162
+ class FakeServer
163
+
164
+ # Initialize the fake server with the pipe.
165
+ def initialize(pipe)
166
+ @pipe = pipe
167
+ end
168
+
169
+ # Does nothing.
170
+ def listen(_); end
171
+
172
+ # Returns the pipe.
173
+ #
174
+ # @return [Object]
175
+ def accept
176
+ @pipe
177
+ end
178
+ end
179
+
180
+ end
181
+ end
182
+ end
@@ -0,0 +1,5 @@
1
+ module Nova
2
+
3
+ # The version of Nova.
4
+ VERSION = "0.0.2".freeze
5
+ end
@@ -0,0 +1,20 @@
1
+ describe Nova::Constructor do
2
+
3
+ it "should create stars from types" do
4
+ d = described_class.new(non_existant_star: :some_name) {}
5
+
6
+ expect {
7
+ d.modify_or_create
8
+ }.to raise_error(Nova::NoStarError)
9
+
10
+ d = described_class.new(star: :some_name) do
11
+ def some_method; 5; end
12
+ end
13
+
14
+ klass = d.modify_or_create
15
+ expect(klass.as).to be :some_name
16
+ expect(klass.instance_methods).to include(:some_method)
17
+ expect(klass.new.some_method).to be 5
18
+ end
19
+
20
+ end
@@ -0,0 +1,26 @@
1
+ =begin
2
+
3
+ require 'nova/remote/local'
4
+
5
+ describe Nova::Remote::Local do
6
+
7
+ before :each do
8
+ Nova::Star.remote = Nova::Remote::Local
9
+ end
10
+
11
+ context "platforms" do
12
+
13
+ it "should determine platforms" do
14
+ expect(Nova::Star.new.platform).to have_at_least(3).items
15
+ end
16
+ end
17
+
18
+ context "commands" do
19
+ it "should execute the command" do
20
+ star = Nova::Star.new
21
+ expect(star.line("echo", "hello").pass.stdout).to eq("hello\n")
22
+ end
23
+ end
24
+ end
25
+
26
+ =end
data/spec/nova_spec.rb ADDED
@@ -0,0 +1,7 @@
1
+ describe Nova do
2
+
3
+ it "has a logger" do
4
+ expect(Nova.logger).to be_a Logger
5
+ end
6
+
7
+ end
@@ -0,0 +1,19 @@
1
+ require 'coveralls'
2
+
3
+ Coveralls.wear!
4
+
5
+ module NovaHelper
6
+
7
+ def self.build_packet(type = 0, body = "hello world", data = { :packet_id => 1, :nonce => "" })
8
+ Nova::Starbound::Protocol::Packet.build(type, body, data)
9
+ end
10
+
11
+ def self.packet_from_socket(sock)
12
+ Nova::Starbound::Protocol::Packet.from_socket(sock)
13
+ end
14
+
15
+ def self.build_response(type, body, pack)
16
+ Nova::Starbound::Protocol::Packet.build_response(type, body, pack, :nonce => "")
17
+ end
18
+
19
+ end
@@ -0,0 +1,27 @@
1
+ class SomeType < Nova::Star
2
+ star_type :some_type
3
+
4
+ metadata do
5
+ require_options :hello
6
+ end
7
+
8
+ feature :some_feature do
9
+ on :some_event do; end
10
+
11
+ on :enable do
12
+ tag :enable
13
+
14
+ 3
15
+ end
16
+
17
+ on :disable do
18
+ tag :disable
19
+
20
+ 4
21
+ end
22
+ end
23
+
24
+ on :foo do; 1; end
25
+
26
+ on :bar, requires: :an_option do; 2; end
27
+ end
data/spec/star_spec.rb ADDED
@@ -0,0 +1,107 @@
1
+ require_relative 'star/some_type'
2
+
3
+ describe Nova::Star do
4
+
5
+ before :each do
6
+ Nova::Star.remote = Nova::Remote::Fake
7
+ end
8
+
9
+ context "management" do
10
+ it "handles types" do
11
+ expect(Nova::Star.types).to eq(star: Nova::Star, some_type: SomeType)
12
+ end
13
+
14
+ it "has pretty inspect" do
15
+ expect(Nova::Star.inspect).to eq("Nova::Star")
16
+ expect(SomeType.inspect).to eq("Nova::Star/SomeType")
17
+ end
18
+
19
+ it "has a default remote" do
20
+ expect(SomeType.remote).to be(Nova::Remote::Fake)
21
+ end
22
+ end
23
+
24
+ context "options manager" do
25
+ it "validates options" do
26
+ expect {
27
+ star = SomeType.new
28
+ star.options = {}
29
+ }.to raise_error(Nova::InvalidOptionsError)
30
+ end
31
+ end
32
+
33
+ context "event manager" do
34
+ it "manages events" do
35
+ event = Nova::Common::EventHandler::Event
36
+ [SomeType, SomeType.new].each do |type|
37
+ expect(type.events).to be_instance_of Set
38
+ expect(type.has_event?(:foo)).to be_instance_of event
39
+ expect(type.has_event?(:not_foo)).to be_nil
40
+ expect(type.has_event?(:bar)).to be_instance_of event
41
+
42
+ expect(type.has_event_with_options?(:foo)).to be_instance_of event
43
+ expect(type.has_event_with_options?(:bar)).to be_nil
44
+ expect(type.has_event_with_options?(:bar, an_option: true)).to be_instance_of event
45
+ end
46
+ end
47
+
48
+ it "runs events" do
49
+ star = SomeType.new
50
+ context = double(:context)
51
+ star.bind! context
52
+
53
+ expect(star.run!(:foo)).to be 1
54
+
55
+ expect {
56
+ star.run!(:bar)
57
+ }.to raise_error(Nova::NoEventError)
58
+ expect(star.run(:bar)).to be_instance_of Nova::NoEventError
59
+ expect(star.run!(:bar, an_option: true)).to be 2
60
+
61
+ expect {
62
+ star.run! :not_an_event
63
+ }.to raise_error(Nova::NoEventError)
64
+ expect(star.run(:not_an_event)).to be_instance_of Nova::NoEventError
65
+ end
66
+ end
67
+
68
+ context "features" do
69
+ it "supports defined features" do
70
+ expect(SomeType.supports?(:some_feature)).to be true
71
+ expect(SomeType.supports?(:another_feature)).to be false
72
+ end
73
+
74
+ it "gives fake features" do
75
+ star = SomeType.new
76
+ expect(star.feature(:some_feature)).to_not be_fake
77
+ expect(star.feature(:another_feature)).to be_fake
78
+
79
+ expect(star.supports?(:some_feature)).to be true
80
+ expect(star.supports?(:another_feature)).to be false
81
+ end
82
+
83
+ it "runs enable and disable events" do
84
+ star = SomeType.new
85
+ context = stub(:context)
86
+ context.should_receive(:tag).with(:enable)
87
+ expect(star.feature(:some_feature).bind(context).enable!).to be 3
88
+
89
+ context = stub(:context)
90
+ context.should_receive(:tag).with(:disable)
91
+ expect(star.feature(:some_feature).bind(context).disable!).to be 4
92
+ end
93
+ end
94
+
95
+ context "platforms" do
96
+ it "displays correct platforms" do
97
+ expect(SomeType.new.remote.platform.types).to eq([])
98
+ end
99
+ end
100
+
101
+ context "commands" do
102
+ it "gives a command line" do
103
+ star = SomeType.new
104
+ expect(star.remote.command.line("something", "arguments")).to be_instance_of Command::Runner
105
+ end
106
+ end
107
+ end
@@ -0,0 +1,33 @@
1
+ describe Nova::Starbound::Encryptor do
2
+
3
+ it "is not available" do
4
+ expect(described_class).to_not be_available
5
+
6
+ expect {
7
+ subject
8
+ }.to raise_error NotImplementedError
9
+ end
10
+
11
+ it "is not plaintext" do
12
+ expect(described_class).to_not be_plaintext
13
+ end
14
+
15
+ it "sorts the encryptors" do
16
+ expect(described_class.sorted_encryptors).to have(3).items
17
+ expect(described_class.sorted_encryptors.first.preference).to be > described_class.sorted_encryptors.last.preference
18
+ end
19
+
20
+ context "encrypting" do
21
+ before(:each) { described_class.stub(:available?).and_return(true) }
22
+
23
+ it "raises errors" do
24
+ [:encrypt, :decrypt, :private_key!, :public_key,
25
+ :other_public_key=].each do |m|
26
+
27
+ expect(subject).to respond_to m
28
+
29
+ expect { subject.send(m) }.to raise_error NotImplementedError
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,80 @@
1
+ describe Nova::Starbound::Encryptors::OpenSSL do
2
+
3
+ it "is available" do
4
+ expect(described_class).to be_available
5
+ end
6
+
7
+ context "handling keys" do
8
+ subject { described_class.new }
9
+ its(:private_key!) { should be_instance_of ::OpenSSL::PKey::RSA }
10
+
11
+ it "has a public key" do
12
+ subject.private_key! # initialize the private key
13
+ expect(subject.public_key).to be_a String
14
+ end
15
+ end
16
+
17
+ before :each do
18
+ @packet = NovaHelper.build_packet
19
+ end
20
+
21
+ it "encrypts a packet successfully" do
22
+
23
+ subject.private_key!
24
+ public_key = subject.public_key
25
+
26
+ secret = ::OpenSSL::Random.random_bytes(4096 / 16)
27
+ encrypted_secret = ::OpenSSL::PKey::RSA.new(public_key).public_encrypt(secret)
28
+
29
+ subject.other_public_key = encrypted_secret
30
+
31
+ expect(subject.options[:shared_secret]).to eq secret
32
+ encrypted = nil
33
+
34
+ expect {
35
+ encrypted = subject.encrypt(@packet)
36
+ }.to_not raise_error
37
+
38
+ expect(encrypted).to be_instance_of Nova::Starbound::Protocol::Packet
39
+ expect(encrypted.body.bytesize).to eq encrypted[:size]
40
+ end
41
+
42
+ it "decrypts a packet successfully" do
43
+
44
+ subject.private_key!
45
+
46
+ public_key = ::OpenSSL::PKey::RSA.new(4096)
47
+ subject.other_public_key = public_key.public_key.to_der
48
+ encrypted_secret = subject.public_key
49
+
50
+ secret = public_key.private_decrypt(encrypted_secret)
51
+ expect(secret).to eq subject.options[:shared_secret]
52
+
53
+ encrypted = subject.encrypt(@packet)
54
+ decrypted = nil
55
+
56
+ expect {
57
+ decrypted = subject.decrypt(encrypted)
58
+ }.to_not raise_error
59
+
60
+ expect(decrypted.body).to eq @packet.body
61
+ end
62
+
63
+ it "raises an error on non-matching hashes" do
64
+ subject.private_key!
65
+ public_key = subject.public_key
66
+
67
+ secret = ::OpenSSL::Random.random_bytes(4096 / 16)
68
+ encrypted_secret = ::OpenSSL::PKey::RSA.new(public_key).public_encrypt(secret)
69
+
70
+ subject.other_public_key = encrypted_secret
71
+
72
+ encrypted = subject.encrypt(@packet)
73
+ encrypted.body.replace("\x00" * encrypted.size)
74
+
75
+ expect {
76
+ subject.decrypt(encrypted)
77
+ }.to raise_error(Nova::Starbound::EncryptorError)
78
+ end
79
+
80
+ end