meshchat 0.5.0
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 +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
|