nova 0.0.2

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.
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