meshchat 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CHANGELOG.md +1 -0
- data/README.md +7 -0
- data/lib/meshchat.rb +73 -0
- data/lib/meshchat/cli.rb +164 -0
- data/lib/meshchat/cli/command.rb +63 -0
- data/lib/meshchat/cli/config.rb +30 -0
- data/lib/meshchat/cli/exit.rb +9 -0
- data/lib/meshchat/cli/identity.rb +9 -0
- data/lib/meshchat/cli/import.rb +37 -0
- data/lib/meshchat/cli/init.rb +34 -0
- data/lib/meshchat/cli/input.rb +60 -0
- data/lib/meshchat/cli/irb.rb +18 -0
- data/lib/meshchat/cli/listen.rb +9 -0
- data/lib/meshchat/cli/ping.rb +61 -0
- data/lib/meshchat/cli/ping_all.rb +11 -0
- data/lib/meshchat/cli/server.rb +16 -0
- data/lib/meshchat/cli/share.rb +9 -0
- data/lib/meshchat/cli/stop_listening.rb +9 -0
- data/lib/meshchat/cli/whisper.rb +34 -0
- data/lib/meshchat/cli/who.rb +9 -0
- data/lib/meshchat/config/hash_file.rb +75 -0
- data/lib/meshchat/config/settings.rb +112 -0
- data/lib/meshchat/database.rb +30 -0
- data/lib/meshchat/display.rb +32 -0
- data/lib/meshchat/display/base.rb +53 -0
- data/lib/meshchat/display/manager.rb +51 -0
- data/lib/meshchat/encryption.rb +27 -0
- data/lib/meshchat/encryption/aes_rsa.rb +65 -0
- data/lib/meshchat/encryption/passthrough.rb +17 -0
- data/lib/meshchat/instance.rb +34 -0
- data/lib/meshchat/message.rb +38 -0
- data/lib/meshchat/message/base.rb +93 -0
- data/lib/meshchat/message/chat.rb +14 -0
- data/lib/meshchat/message/disconnection.rb +13 -0
- data/lib/meshchat/message/node_list.rb +41 -0
- data/lib/meshchat/message/node_list_diff.rb +15 -0
- data/lib/meshchat/message/node_list_hash.rb +29 -0
- data/lib/meshchat/message/ping.rb +32 -0
- data/lib/meshchat/message/ping_reply.rb +9 -0
- data/lib/meshchat/message/relay.rb +43 -0
- data/lib/meshchat/message/whisper.rb +36 -0
- data/lib/meshchat/models/entry.rb +104 -0
- data/lib/meshchat/net/client.rb +60 -0
- data/lib/meshchat/net/listener/request.rb +48 -0
- data/lib/meshchat/net/listener/request_processor.rb +45 -0
- data/lib/meshchat/net/listener/server.rb +61 -0
- data/lib/meshchat/net/request.rb +29 -0
- data/lib/meshchat/notifier/base.rb +50 -0
- data/lib/meshchat/version.rb +3 -0
- metadata +288 -0
@@ -0,0 +1,32 @@
|
|
1
|
+
module MeshChat
|
2
|
+
module Display
|
3
|
+
|
4
|
+
module_function
|
5
|
+
|
6
|
+
# delegate :start, :add_line,
|
7
|
+
# :info, :warning, :alert, :success, :chat, :whisper,
|
8
|
+
# :present_message, to: :current
|
9
|
+
|
10
|
+
# TODO: Delegate doesn't work on modules?
|
11
|
+
def start(*args); current.start(*args); end
|
12
|
+
def add_line(*args); current.add_line(*args); end
|
13
|
+
def info(*args); current.info(*args); end
|
14
|
+
def warning(*args); current.warning(*args); end
|
15
|
+
def alert(*args); current.alert(*args); end
|
16
|
+
def success(*args); current.success(*args); end
|
17
|
+
def chat(*args); current.chat(*args); end
|
18
|
+
def whisper(*args); current.whisper(*args); end
|
19
|
+
def present_message(*args); current.present_message(*args); end
|
20
|
+
|
21
|
+
# TODO: break these out in to their own Logger class, and not
|
22
|
+
# on the display object
|
23
|
+
def fatal(*args); current.fatal(*args); end
|
24
|
+
def debug(*args); current.debug(*args); end
|
25
|
+
def error(*args); current.error(*args); end
|
26
|
+
|
27
|
+
def current
|
28
|
+
MeshChat::Instance.display
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module MeshChat
|
2
|
+
module Display
|
3
|
+
class Base
|
4
|
+
|
5
|
+
# instantiate the UI, startup the CLI
|
6
|
+
def start
|
7
|
+
fail 'overload this method'
|
8
|
+
end
|
9
|
+
|
10
|
+
# output a generic line of text
|
11
|
+
def add_line(_line)
|
12
|
+
fail 'overload this method'
|
13
|
+
end
|
14
|
+
|
15
|
+
# formatter for a whisper message
|
16
|
+
def whisper(_line)
|
17
|
+
fail 'overload this method'
|
18
|
+
end
|
19
|
+
|
20
|
+
# server info or other ignorable information
|
21
|
+
def info(_line)
|
22
|
+
fail 'overload this method'
|
23
|
+
end
|
24
|
+
|
25
|
+
# warning message that the user may or may not care about
|
26
|
+
def warning(_line)
|
27
|
+
fail 'overload this method'
|
28
|
+
end
|
29
|
+
|
30
|
+
# really try to get the user's attention
|
31
|
+
def alert(_line)
|
32
|
+
fail 'overload this method'
|
33
|
+
end
|
34
|
+
|
35
|
+
# a happy message to affirm the user something succeded
|
36
|
+
def success(_line)
|
37
|
+
fail 'overload this method'
|
38
|
+
end
|
39
|
+
|
40
|
+
# general chat message
|
41
|
+
def chat(_line)
|
42
|
+
fail 'overload this method'
|
43
|
+
end
|
44
|
+
|
45
|
+
|
46
|
+
|
47
|
+
# log a message
|
48
|
+
def log(_msg)
|
49
|
+
fail 'overload this method'
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'meshchat/display/base'
|
2
|
+
|
3
|
+
module MeshChat
|
4
|
+
module Display
|
5
|
+
class Manager
|
6
|
+
|
7
|
+
attr_accessor :_ui
|
8
|
+
|
9
|
+
delegate :start, to: :_ui
|
10
|
+
delegate :add_line, to: :_ui
|
11
|
+
delegate :info, to: :_ui
|
12
|
+
delegate :warning, to: :_ui
|
13
|
+
delegate :alert, to: :_ui
|
14
|
+
delegate :success, to: :_ui
|
15
|
+
delegate :chat, to: :_ui
|
16
|
+
delegate :whisper, to: :_ui
|
17
|
+
|
18
|
+
attr_accessor :_logger
|
19
|
+
delegate :fatal, to: :_logger
|
20
|
+
delegate :debug, to: :_logger
|
21
|
+
delegate :error, to: :_logger
|
22
|
+
|
23
|
+
def initialize(ui_klass)
|
24
|
+
self._logger = Logger.new('debug.log')
|
25
|
+
self._ui = ui_klass.new
|
26
|
+
end
|
27
|
+
|
28
|
+
def present_message(message)
|
29
|
+
result = message.handle
|
30
|
+
return unless result
|
31
|
+
|
32
|
+
case message.class.name
|
33
|
+
when Message::Chat.name
|
34
|
+
chat result
|
35
|
+
Notify.show(summary: message.sender_name, body: message.message)
|
36
|
+
when Message::Whisper.name
|
37
|
+
whisper result
|
38
|
+
Notify.show(summary: message.sender_name, body: message.message)
|
39
|
+
when Message::PingReply.name, Message::Ping.name
|
40
|
+
info result
|
41
|
+
when Message::NodeList.name,
|
42
|
+
Message::NodeListDiff.name,
|
43
|
+
Message::NodeListHash.name
|
44
|
+
# display nothing
|
45
|
+
else
|
46
|
+
add_line result
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'meshchat/encryption/aes_rsa'
|
2
|
+
require 'meshchat/encryption/passthrough'
|
3
|
+
|
4
|
+
module MeshChat
|
5
|
+
module Encryption
|
6
|
+
module_function
|
7
|
+
|
8
|
+
DEFAULT_ENCRYPTOR = AES_RSA
|
9
|
+
|
10
|
+
def encryptor=(klass)
|
11
|
+
@current_encryptor = klass
|
12
|
+
end
|
13
|
+
|
14
|
+
def current_encryptor
|
15
|
+
@current_encryptor || DEFAULT_ENCRYPTOR
|
16
|
+
end
|
17
|
+
|
18
|
+
def decrypt(*args)
|
19
|
+
current_encryptor.decrypt(*args)
|
20
|
+
end
|
21
|
+
|
22
|
+
def encrypt(*args)
|
23
|
+
current_encryptor.encrypt(*args)
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
module MeshChat
|
2
|
+
module Encryption
|
3
|
+
# Using AES266-CBC with RSA
|
4
|
+
#
|
5
|
+
# Docs on AES
|
6
|
+
# http://ruby-doc.org/stdlib-2.0.0/libdoc/openssl/rdoc/OpenSSL/Cipher.html
|
7
|
+
module AES_RSA
|
8
|
+
module_function
|
9
|
+
|
10
|
+
# Use Cipher Block Chaining
|
11
|
+
# https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Cipher_Block_Chaining_.28CBC.29
|
12
|
+
AES_MODE = :CBC
|
13
|
+
|
14
|
+
# 1. Generate random AES key to encrypt message
|
15
|
+
# 2. Use Public Key to encrypt AES Key
|
16
|
+
# 3. Prepend encrypted AES key to the encrypted message
|
17
|
+
#
|
18
|
+
# Output message format will look like the following:
|
19
|
+
#
|
20
|
+
# {RSA Encrypted AES Key}{RSA Encrypted IV}{AES Encrypted Message}
|
21
|
+
def encrypt(msg, public_key)
|
22
|
+
# 1
|
23
|
+
cipher = OpenSSL::Cipher::AES256.new(AES_MODE)
|
24
|
+
cipher.encrypt
|
25
|
+
aes_key = cipher.random_key
|
26
|
+
aes_iv = cipher.random_iv
|
27
|
+
aes_encrypted_message = cipher.update(msg)
|
28
|
+
# pad, because of how CBC works
|
29
|
+
padding = cipher.final
|
30
|
+
encrypted = aes_encrypted_message + padding
|
31
|
+
|
32
|
+
# 2
|
33
|
+
rsa_encryptor = OpenSSL::PKey::RSA.new public_key
|
34
|
+
rsa_encrypted_aes_key = rsa_encryptor.public_encrypt(aes_key)
|
35
|
+
rsa_encrypted_aes_iv = rsa_encryptor.public_encrypt(aes_iv)
|
36
|
+
|
37
|
+
# 3
|
38
|
+
rsa_encrypted_aes_key + rsa_encrypted_aes_iv + encrypted
|
39
|
+
end
|
40
|
+
|
41
|
+
# 1. Split the string in to the AES key and the encrypted message
|
42
|
+
# 2. Decrypt the AES key using the private key
|
43
|
+
# 3. Decrypt the message using the AES key
|
44
|
+
def decrypt(msg, private_key)
|
45
|
+
# 1
|
46
|
+
rsa_encrypted_aes_key = msg[0..255] # 256 bits
|
47
|
+
rsa_encrypted_aes_iv = msg[256..511] # next 256 bits
|
48
|
+
aes_encrypted_message = msg[512..msg.length]
|
49
|
+
|
50
|
+
# 2
|
51
|
+
rsa_decryptor = OpenSSL::PKey::RSA.new private_key
|
52
|
+
aes_key = rsa_decryptor.private_decrypt rsa_encrypted_aes_key
|
53
|
+
aes_iv = rsa_decryptor.private_decrypt rsa_encrypted_aes_iv
|
54
|
+
|
55
|
+
# 3
|
56
|
+
decipher = OpenSSL::Cipher::AES256.new(AES_MODE)
|
57
|
+
decipher.decrypt
|
58
|
+
decipher.key = aes_key
|
59
|
+
decipher.iv = aes_iv
|
60
|
+
|
61
|
+
decipher.update(aes_encrypted_message) + decipher.final
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module MeshChat
|
2
|
+
module Encryption
|
3
|
+
# This should normally be for testing
|
4
|
+
#
|
5
|
+
# It just retuns the message that is asked to be encrypted
|
6
|
+
module Passthrough
|
7
|
+
module_function
|
8
|
+
def encrypt(msg, *args)
|
9
|
+
msg
|
10
|
+
end
|
11
|
+
|
12
|
+
def decrypt(msg, *args)
|
13
|
+
msg
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module MeshChat
|
2
|
+
class Instance
|
3
|
+
attr_accessor :client_name, :client_version, :display
|
4
|
+
|
5
|
+
class << self
|
6
|
+
delegate :client_name, :client_version, :display,
|
7
|
+
to: :instance
|
8
|
+
|
9
|
+
def start(options)
|
10
|
+
# calling instance to get things going
|
11
|
+
@instance = new(options)
|
12
|
+
@instance.start_ui(options[:display], options[:on_display_start])
|
13
|
+
end
|
14
|
+
|
15
|
+
def instance
|
16
|
+
@instance
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def initialize(options = {})
|
21
|
+
self.client_name = options[:client_name] || MeshChat::NAME
|
22
|
+
self.client_version = options[:client_version] || MeshChat::VERSION
|
23
|
+
end
|
24
|
+
|
25
|
+
# @param [class] klass should be something that implements Display::Base
|
26
|
+
# @param [Proc] proc what to do when starting the UI
|
27
|
+
def start_ui(klass, on_display_start)
|
28
|
+
self.display = Display::Manager.new(klass)
|
29
|
+
display.start do
|
30
|
+
on_display_start.call if on_display_start
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'meshchat/message/base'
|
2
|
+
require 'meshchat/message/chat'
|
3
|
+
require 'meshchat/message/ping'
|
4
|
+
require 'meshchat/message/ping_reply'
|
5
|
+
require 'meshchat/message/disconnection'
|
6
|
+
require 'meshchat/message/whisper'
|
7
|
+
require 'meshchat/message/relay'
|
8
|
+
require 'meshchat/message/node_list'
|
9
|
+
require 'meshchat/message/node_list_diff'
|
10
|
+
require 'meshchat/message/node_list_hash'
|
11
|
+
|
12
|
+
module MeshChat
|
13
|
+
module Message
|
14
|
+
CHAT = 'chat'
|
15
|
+
PING = 'ping'
|
16
|
+
PING_REPLY = 'pingreply'
|
17
|
+
WHISPER = 'whisper'
|
18
|
+
RELAY = 'relay'
|
19
|
+
DISCONNECTION = 'disconnection'
|
20
|
+
|
21
|
+
NODE_LIST = 'nodelist'
|
22
|
+
NODE_LIST_HASH = 'nodelisthash'
|
23
|
+
NODE_LIST_DIFF = 'nodelistdiff'
|
24
|
+
|
25
|
+
TYPES = {
|
26
|
+
CHAT => Chat,
|
27
|
+
WHISPER => Whisper,
|
28
|
+
DISCONNECTION => Disconnection,
|
29
|
+
PING => Ping,
|
30
|
+
PING_REPLY => PingReply,
|
31
|
+
NODE_LIST => NodeList,
|
32
|
+
NODE_LIST_DIFF => NodeListDiff,
|
33
|
+
NODE_LIST_HASH => NodeListHash
|
34
|
+
}
|
35
|
+
|
36
|
+
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,93 @@
|
|
1
|
+
module MeshChat
|
2
|
+
module Message
|
3
|
+
#
|
4
|
+
# NOTE:
|
5
|
+
# #display: shows the message
|
6
|
+
# should be used locally, before *sending* a message
|
7
|
+
# #handle: processing logic for the message
|
8
|
+
# should be used when receiving a message, and there
|
9
|
+
# needs to be a response right away
|
10
|
+
# #respond: where the actual logic for the response goes
|
11
|
+
class Base
|
12
|
+
attr_accessor :payload, :time_recieved,
|
13
|
+
:message, :sender_name, :sender_location, :sender_uid
|
14
|
+
|
15
|
+
# @param [String] message
|
16
|
+
# @param [String] name_of_sender
|
17
|
+
# @param [String] location the location of the sender
|
18
|
+
# @param [Hash] payload optionally overrides the default payload
|
19
|
+
def initialize(
|
20
|
+
message: '',
|
21
|
+
sender_name: '',
|
22
|
+
sender_location: '',
|
23
|
+
sender_uid: '',
|
24
|
+
time_recieved: nil,
|
25
|
+
payload: nil)
|
26
|
+
|
27
|
+
@payload = payload.presence
|
28
|
+
|
29
|
+
# TODO: find a more elegant way to represent this
|
30
|
+
self.message = message.presence || @payload.try(:[], 'message')
|
31
|
+
self.sender_name = sender_name.presence || @payload.try(:[], 'sender').try(:[], 'alias') || Settings['alias']
|
32
|
+
self.sender_location = sender_location.presence || @payload.try(:[], 'sender').try(:[], 'location') || Settings.location
|
33
|
+
self.sender_uid = sender_uid.presence || @payload.try(:[], 'sender').try(:[], 'uid') || Settings['uid']
|
34
|
+
self.time_recieved = time_recieved.presence || Time.now
|
35
|
+
|
36
|
+
end
|
37
|
+
|
38
|
+
def payload
|
39
|
+
@payload ||= {
|
40
|
+
'type' => type,
|
41
|
+
'message' => message,
|
42
|
+
'client' => client,
|
43
|
+
'client_version' => client_version,
|
44
|
+
'time_sent' => time_recieved || Time.now.to_s,
|
45
|
+
'sender' => {
|
46
|
+
'alias' => sender_name,
|
47
|
+
'location' => sender_location,
|
48
|
+
'uid' => sender_uid
|
49
|
+
}
|
50
|
+
}
|
51
|
+
end
|
52
|
+
|
53
|
+
def type
|
54
|
+
@type ||= TYPES.invert[self.class]
|
55
|
+
end
|
56
|
+
|
57
|
+
def client
|
58
|
+
MeshChat.name
|
59
|
+
end
|
60
|
+
|
61
|
+
def client_version
|
62
|
+
MeshChat.version
|
63
|
+
end
|
64
|
+
|
65
|
+
# shows the message
|
66
|
+
# should be used locally, before *sending* a message
|
67
|
+
def display
|
68
|
+
message
|
69
|
+
end
|
70
|
+
|
71
|
+
# processing logic for the message
|
72
|
+
# should be used when receiving a message, and there
|
73
|
+
# needs to be a response right away.
|
74
|
+
# this may call display, if the response is always to be displayed
|
75
|
+
def handle
|
76
|
+
display
|
77
|
+
end
|
78
|
+
|
79
|
+
# Most message types aren't going to need to have an
|
80
|
+
# immediate response.
|
81
|
+
#
|
82
|
+
def respond
|
83
|
+
return
|
84
|
+
end
|
85
|
+
|
86
|
+
# this message should be called immediately
|
87
|
+
# before sending to the whomever
|
88
|
+
def render
|
89
|
+
payload.to_json
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module MeshChat
|
2
|
+
module Message
|
3
|
+
class Chat < Base
|
4
|
+
|
5
|
+
def display
|
6
|
+
time_recieved = self.time_recieved.strftime('%e/%m/%y %H:%I:%M')
|
7
|
+
name = payload['sender']['alias']
|
8
|
+
message = payload['message']
|
9
|
+
|
10
|
+
"#{time_recieved} #{name} > #{message}"
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module MeshChat
|
2
|
+
module Message
|
3
|
+
class Disconnection < Base
|
4
|
+
def display
|
5
|
+
location = payload['sender']['location']
|
6
|
+
name = payload['sender']['alias']
|
7
|
+
node = Node.find_by_location(location)
|
8
|
+
node.update(online: false) if node
|
9
|
+
"#{name}@#{location} has disconnected"
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|