gameoverseer 0.1.2 → 0.1.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +4 -3
- data/Gemfile +3 -10
- data/Gemfile.lock +30 -34
- data/README.md +51 -48
- data/gameoverseer.gemspec +2 -2
- data/lib/gameoverseer/channels/channel_manager.rb +49 -36
- data/lib/gameoverseer/clients/client_manager.rb +66 -50
- data/lib/gameoverseer/console/console.rb +206 -207
- data/lib/gameoverseer/encryption_handler/encryption_handler.rb +40 -0
- data/lib/gameoverseer/input_handler/input_handler.rb +36 -30
- data/lib/gameoverseer/messages/message_manager.rb +42 -36
- data/lib/gameoverseer/packet_handler/packet_handler.rb +35 -0
- data/lib/gameoverseer/server/renet_server.rb +126 -84
- data/lib/gameoverseer/services/service.rb +111 -90
- data/lib/gameoverseer/services/services.rb +38 -31
- data/lib/gameoverseer/version.rb +4 -4
- data/lib/gameoverseer.rb +59 -57
- data/test/test_helper.rb +1 -0
- data/test-clients/protocol-lib.rb +13 -13
- data/test-clients/test-client-alpha.rb +36 -36
- data/test-clients/test-client-beta.rb +37 -37
- data/test-clients/test-client-gamma.rb +62 -59
- metadata +23 -30
- data/lib/gameoverseer/server/encryption.rb +0 -31
@@ -0,0 +1,40 @@
|
|
1
|
+
module GameOverseer
|
2
|
+
class EncryptionHandler
|
3
|
+
|
4
|
+
def initialize(key_size: 128, keypair_size: 2048)
|
5
|
+
EncryptionHandler.instance = self
|
6
|
+
generate_server_keypair(keypair_size)
|
7
|
+
end
|
8
|
+
|
9
|
+
def generate_server_keypair(keypair_size)
|
10
|
+
@server_keypair = OpenSSL::PKey::RSA.generate(keypair_size)
|
11
|
+
end
|
12
|
+
|
13
|
+
def encrypt_keypair(client_id, string)
|
14
|
+
end
|
15
|
+
|
16
|
+
def decrypt_keypair(string)
|
17
|
+
end
|
18
|
+
|
19
|
+
def encrypt(client_id, string)
|
20
|
+
end
|
21
|
+
|
22
|
+
def decrypt(client_id, string)
|
23
|
+
end
|
24
|
+
|
25
|
+
def set_client_keypair(client_id, public_key_pem)
|
26
|
+
end
|
27
|
+
|
28
|
+
def set_client_key(client_id)
|
29
|
+
ClientManager.instance.update(client_id, '_aes_key', 'In progress, not ready.')
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.instance
|
33
|
+
@instance
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.instance=(_instance)
|
37
|
+
@instance = _instance
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -1,30 +1,36 @@
|
|
1
|
-
module GameOverseer
|
2
|
-
class InputHandler
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
1
|
+
module GameOverseer
|
2
|
+
class InputHandler
|
3
|
+
|
4
|
+
# @param client_id [Integer]
|
5
|
+
# @param data [Hash]
|
6
|
+
def self.process_data(client_id, data)
|
7
|
+
@data = data
|
8
|
+
@client_id = client_id
|
9
|
+
forward_to_channel_manager if data_valid?
|
10
|
+
end
|
11
|
+
|
12
|
+
# Checks if hash contains 'channel' and 'mode'
|
13
|
+
# @return [Boolean]
|
14
|
+
def self.data_valid?
|
15
|
+
if @data["channel"]
|
16
|
+
if @data["mode"]
|
17
|
+
true
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
# Sends data and client_id to {ChannelManager}
|
23
|
+
def self.forward_to_channel_manager
|
24
|
+
count = 0
|
25
|
+
begin
|
26
|
+
channel_manager = GameOverseer::ChannelManager.instance
|
27
|
+
channel_manager.send_to_service(@client_id, @data)
|
28
|
+
rescue NoMethodError => e
|
29
|
+
GameOverseer::Console.log("InputHandler> #{e.to_s}")
|
30
|
+
raise if count >=2
|
31
|
+
count+=1
|
32
|
+
retry unless count >= 2
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -1,36 +1,42 @@
|
|
1
|
-
module GameOverseer
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
end
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
1
|
+
module GameOverseer
|
2
|
+
|
3
|
+
# Handles sending messages to clients on behalf of services
|
4
|
+
class MessageManager
|
5
|
+
MESSAGES = []
|
6
|
+
BROADCASTS = []
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
MessageManager.instance = self
|
10
|
+
end
|
11
|
+
|
12
|
+
# Send a message to a specific client
|
13
|
+
# @param client_id [Integer] ID of client
|
14
|
+
# @param string [String] message to send
|
15
|
+
# @param reliable [Boolean] whether or not packet delivery is reliable
|
16
|
+
# @param channel [Integer] What channel to send on
|
17
|
+
def message(client_id, string, reliable = false, channel = ChannelManager::CHAT)
|
18
|
+
GameOverseer::ENetServer.instance.transmit(client_id, string, reliable, channel)
|
19
|
+
GameOverseer::Console.log("MessageManager> #{string}-#{client_id}")
|
20
|
+
end
|
21
|
+
|
22
|
+
|
23
|
+
# Send a message to all connected clients
|
24
|
+
# @param string [String] message to send
|
25
|
+
# @param reliable [Boolean] whether or not packet delivery is reliable
|
26
|
+
# @param channel [Integer] What channel to send on
|
27
|
+
def broadcast(string, reliable = false, channel = ChannelManager::CHAT)
|
28
|
+
GameOverseer::ENetServer.instance.broadcast(string, reliable, channel)
|
29
|
+
GameOverseer::Console.log("MessageManager> #{string}-#{channel}")
|
30
|
+
end
|
31
|
+
|
32
|
+
# @return [MessageManager]
|
33
|
+
def self.instance
|
34
|
+
@instance
|
35
|
+
end
|
36
|
+
|
37
|
+
# @param _instance [MessageManager]
|
38
|
+
def self.instance=_instance
|
39
|
+
@instance = _instance
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module GameOverseer
|
2
|
+
class PacketHandler
|
3
|
+
|
4
|
+
def initialize
|
5
|
+
PacketHandler.instance = self
|
6
|
+
end
|
7
|
+
|
8
|
+
def pre_processor(packet, sending)
|
9
|
+
data = nil
|
10
|
+
if sending
|
11
|
+
data = MultiJson.dump(packet)
|
12
|
+
else
|
13
|
+
data = MultiJson.load(packet)
|
14
|
+
end
|
15
|
+
|
16
|
+
return data
|
17
|
+
end
|
18
|
+
|
19
|
+
def receive(client_id, packet)
|
20
|
+
_packet = pre_processor(packet, false)
|
21
|
+
end
|
22
|
+
|
23
|
+
def transmit(client_id, data)
|
24
|
+
_packet = pre_processor(data, true)
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.instance
|
28
|
+
@instance
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.instance=(_instance)
|
32
|
+
@instance = _instance
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -1,84 +1,126 @@
|
|
1
|
-
module GameOverseer
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
@
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
#
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
def
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
end
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
end
|
1
|
+
module GameOverseer
|
2
|
+
|
3
|
+
# GameOverseers' connection to the world
|
4
|
+
#
|
5
|
+
# This server uses the renet library, which is C bindings for the Enet networking library
|
6
|
+
class ENetServer
|
7
|
+
# @param host [String] host or ip for the server to run on
|
8
|
+
# @param port [Integer] port for the server to run on
|
9
|
+
# @param max_clients [Integer] max number of clients that can be connected at one time
|
10
|
+
# @param channels [Integer] number of channels (See Enet documentation)
|
11
|
+
# @param download_bandwidth [Integer] max bandwidth for downloading per-second (0 is unlimited)
|
12
|
+
# @param upload_bandwidth [Integer] max bandwidth for uploading per-second (0 is unlimited)
|
13
|
+
# @return [Thread]
|
14
|
+
def initialize(host, port, packet_handler, encryption_handler, max_clients = 4, channels = 4, download_bandwidth = 0, upload_bandwidth = 0)
|
15
|
+
GameOverseer::Console.log("Server> Started on: #{host}:#{port}.")
|
16
|
+
GameOverseer::Services.enable
|
17
|
+
GameOverseer::ENetServer.instance = self
|
18
|
+
|
19
|
+
@message_manager = GameOverseer::MessageManager.instance
|
20
|
+
@channel_manager = GameOverseer::ChannelManager.instance
|
21
|
+
@client_manager = GameOverseer::ClientManager.instance
|
22
|
+
@packet_handler = packet_handler.new
|
23
|
+
@encryption_handler = encryption_handler.instance if encryption_handler
|
24
|
+
|
25
|
+
@server = ENet::Server.new(port, max_clients, channels, download_bandwidth, upload_bandwidth) # Port, max clients, channels, download bandwidth, upload bandwith
|
26
|
+
@server.use_compression(true)
|
27
|
+
@terminate = false
|
28
|
+
|
29
|
+
@server.on_connection(method(:on_connect))
|
30
|
+
@server.on_packet_receive(method(:on_packet))
|
31
|
+
@server.on_disconnection(method(:on_disconnect))
|
32
|
+
|
33
|
+
run
|
34
|
+
end
|
35
|
+
|
36
|
+
# Runs the server in a Thread,, in a loop, calling update on the server.
|
37
|
+
#
|
38
|
+
# @return [Thread]
|
39
|
+
def run
|
40
|
+
Thread.new {
|
41
|
+
loop do
|
42
|
+
@server.update(1000)
|
43
|
+
break if @terminate
|
44
|
+
end
|
45
|
+
}
|
46
|
+
end
|
47
|
+
|
48
|
+
# Called when a packet is received
|
49
|
+
# @param client_id [Integer] ID of client
|
50
|
+
# @param data [String] data client sent
|
51
|
+
# @param channel [Integer] channel that this was sent to
|
52
|
+
def on_packet(client_id, data, channel)
|
53
|
+
handle_connection(client_id, data, channel)
|
54
|
+
end
|
55
|
+
|
56
|
+
# callled when a client connects
|
57
|
+
# @param client_id [Integer] ID of client
|
58
|
+
# @param ip_address [String] address of client
|
59
|
+
def on_connect(client_id, ip_address)
|
60
|
+
@client_manager.add(client_id, ip_address)
|
61
|
+
end
|
62
|
+
|
63
|
+
# callled when a client disconnects
|
64
|
+
# @param client_id [Integer] ID of client
|
65
|
+
def on_disconnect(client_id)
|
66
|
+
@client_manager.remove(client_id)
|
67
|
+
end
|
68
|
+
|
69
|
+
# send message to a specific client
|
70
|
+
# @param client_id [Integer] ID of client
|
71
|
+
# @param message [String] message to be sent to client
|
72
|
+
# @param reliable [Boolean] whether or not the packet is guaranteed to be received by the client
|
73
|
+
# @param channel [Integer] what channel to send on
|
74
|
+
def transmit(client_id, message, reliable = false, channel = ChannelManager::CHAT)
|
75
|
+
@server.send_packet(client_id, message, reliable, channel)
|
76
|
+
end
|
77
|
+
|
78
|
+
# send message to all connected clients
|
79
|
+
# @param message [String] message to be sent to clients
|
80
|
+
# @param reliable [Boolean] whether or not the packet is guaranteed to be received by the clients
|
81
|
+
# @param channel [Integer] what channel to send on
|
82
|
+
def broadcast(message, reliable = false, channel = ChannelManager::CHAT)
|
83
|
+
@server.broadcast_packet(message, reliable, channel)
|
84
|
+
end
|
85
|
+
|
86
|
+
# send data to the InputHandler for processing
|
87
|
+
# @param data [Hash]
|
88
|
+
# @param client_id [Integer] ID of client that sent the data
|
89
|
+
def process_data(client_id, data)
|
90
|
+
GameOverseer::InputHandler.process_data(client_id, data)
|
91
|
+
end
|
92
|
+
|
93
|
+
# Handles received packets from clients and sends them through the {PacketHandler} for pre-processing, then sends it on to {#process_data}
|
94
|
+
# @param client_id [Integer]
|
95
|
+
# @param data [String] data received from client
|
96
|
+
# @param channel [Integer] channel that this packet was sent along
|
97
|
+
def handle_connection(client_id, data, channel)
|
98
|
+
_data = @packet_handler.receive(client_id, data)
|
99
|
+
if _data
|
100
|
+
process_data(client_id, _data)
|
101
|
+
else
|
102
|
+
# TODO: Better error handling :D
|
103
|
+
transmit(client_id, '{"channel":"_error", "mode":"_error", "data":{"code":400, "message":"something went wrong, likely bad data!"}}', true, ChannelManager::FAULT)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def terminate
|
108
|
+
@terminate = true
|
109
|
+
end
|
110
|
+
|
111
|
+
def self.instance
|
112
|
+
@instance
|
113
|
+
end
|
114
|
+
|
115
|
+
def self.instance=(_instance)
|
116
|
+
@instance = _instance
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
class ENetServerRunner
|
121
|
+
attr_reader :supervisor
|
122
|
+
def start(host, port, packet_handler = PacketHandler, encryption_handler = nil)
|
123
|
+
@supervisor = GameOverseer::ENetServer.new(host, port, packet_handler, encryption_handler)
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
@@ -1,90 +1,111 @@
|
|
1
|
-
module GameOverseer
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
end
|
26
|
-
|
27
|
-
# Called
|
28
|
-
def
|
29
|
-
end
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
def
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
end
|
1
|
+
module GameOverseer
|
2
|
+
|
3
|
+
# Services are at the heart on GameOverseer
|
4
|
+
#
|
5
|
+
# Subclass this class to implement a service
|
6
|
+
class Service
|
7
|
+
attr_accessor :client_id
|
8
|
+
attr_reader :safe_methods
|
9
|
+
|
10
|
+
# Adds the class that subclassed this class to a list for activation later
|
11
|
+
# @param subclass [Service]
|
12
|
+
def self.inherited(subclass)
|
13
|
+
Services.register(subclass)
|
14
|
+
GameOverseer::Console.log "Service> added '#{subclass}' to Services::List."
|
15
|
+
end
|
16
|
+
|
17
|
+
# This method should not be overridden, you should instead implement {#setup} with no arguments
|
18
|
+
def initialize
|
19
|
+
if defined?(self.setup)
|
20
|
+
@client_id = 0
|
21
|
+
@safe_methods = []
|
22
|
+
setup
|
23
|
+
enable
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
# Called before {#enable} there should be no active code here, only setup variables.
|
28
|
+
def setup
|
29
|
+
end
|
30
|
+
|
31
|
+
# Called when services are first initialized, put active code here, in a thread.
|
32
|
+
def enable
|
33
|
+
end
|
34
|
+
|
35
|
+
# Called when a message is recieved for this channel.
|
36
|
+
# @param data [Hash] The data from the packet
|
37
|
+
def process(data)
|
38
|
+
end
|
39
|
+
|
40
|
+
def version
|
41
|
+
# Please use the sematic versioning system,
|
42
|
+
# http://semver.org
|
43
|
+
#
|
44
|
+
# e.g.
|
45
|
+
# "1.5.9"
|
46
|
+
# (Major.Minor.Patch)
|
47
|
+
"0.0.0-default"
|
48
|
+
end
|
49
|
+
|
50
|
+
# Sets methods that are safe for {#data_to_method} to call
|
51
|
+
# @param array [Array] Array of strings or symbols that match a method in the service class
|
52
|
+
def set_safe_methods(array)
|
53
|
+
raise "argument must be an array of strings or symbols" unless array.is_a?(Array)
|
54
|
+
@safe_methods = array
|
55
|
+
end
|
56
|
+
|
57
|
+
# @return [ChannelManager] Active instance of ChannelManager
|
58
|
+
def channel_manager
|
59
|
+
ChannelManager.instance
|
60
|
+
end
|
61
|
+
|
62
|
+
# @return [MessageManager] Instance of MessageManager
|
63
|
+
def message_manager
|
64
|
+
MessageManager.instance
|
65
|
+
end
|
66
|
+
|
67
|
+
# @return [ClientManager] Current instance of ClientManager
|
68
|
+
def client_manager
|
69
|
+
ClientManager.instance
|
70
|
+
end
|
71
|
+
|
72
|
+
# Uses the 'mode' from a packet to call the method of the same name
|
73
|
+
# @param data [Hash] data from packet
|
74
|
+
def data_to_method(data)
|
75
|
+
@safe_methods.each do |method|
|
76
|
+
if data['mode'] == method.to_s
|
77
|
+
self.send(data['mode'], data)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
# Calls Proc immediately then every milliseconds, async.
|
83
|
+
# @param milliseconds [Integer][Float] Time to wait before calling the block
|
84
|
+
# @param block [Proc]
|
85
|
+
def every(milliseconds, &block)
|
86
|
+
Thread.new do
|
87
|
+
loop do
|
88
|
+
block.call
|
89
|
+
sleep(milliseconds/1000.0)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
# Calls Proc after milliseconds have passed, async.
|
95
|
+
# @param milliseconds [Integer][Float] Time to wait before calling the block
|
96
|
+
# @param block [Proc]
|
97
|
+
def after(milliseconds, &block)
|
98
|
+
Thread.new do
|
99
|
+
sleep(milliseconds/1000.0)
|
100
|
+
block.call
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
# String to be logged
|
105
|
+
# @param string [String] text to log
|
106
|
+
# @param color [Gosu::Color] color of text in console
|
107
|
+
def log(string, color = Gosu::Color::RED)
|
108
|
+
GameOverseer::Console.log_with_color(string, color)
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|