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.
Files changed (51) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +1 -0
  3. data/README.md +7 -0
  4. data/lib/meshchat.rb +73 -0
  5. data/lib/meshchat/cli.rb +164 -0
  6. data/lib/meshchat/cli/command.rb +63 -0
  7. data/lib/meshchat/cli/config.rb +30 -0
  8. data/lib/meshchat/cli/exit.rb +9 -0
  9. data/lib/meshchat/cli/identity.rb +9 -0
  10. data/lib/meshchat/cli/import.rb +37 -0
  11. data/lib/meshchat/cli/init.rb +34 -0
  12. data/lib/meshchat/cli/input.rb +60 -0
  13. data/lib/meshchat/cli/irb.rb +18 -0
  14. data/lib/meshchat/cli/listen.rb +9 -0
  15. data/lib/meshchat/cli/ping.rb +61 -0
  16. data/lib/meshchat/cli/ping_all.rb +11 -0
  17. data/lib/meshchat/cli/server.rb +16 -0
  18. data/lib/meshchat/cli/share.rb +9 -0
  19. data/lib/meshchat/cli/stop_listening.rb +9 -0
  20. data/lib/meshchat/cli/whisper.rb +34 -0
  21. data/lib/meshchat/cli/who.rb +9 -0
  22. data/lib/meshchat/config/hash_file.rb +75 -0
  23. data/lib/meshchat/config/settings.rb +112 -0
  24. data/lib/meshchat/database.rb +30 -0
  25. data/lib/meshchat/display.rb +32 -0
  26. data/lib/meshchat/display/base.rb +53 -0
  27. data/lib/meshchat/display/manager.rb +51 -0
  28. data/lib/meshchat/encryption.rb +27 -0
  29. data/lib/meshchat/encryption/aes_rsa.rb +65 -0
  30. data/lib/meshchat/encryption/passthrough.rb +17 -0
  31. data/lib/meshchat/instance.rb +34 -0
  32. data/lib/meshchat/message.rb +38 -0
  33. data/lib/meshchat/message/base.rb +93 -0
  34. data/lib/meshchat/message/chat.rb +14 -0
  35. data/lib/meshchat/message/disconnection.rb +13 -0
  36. data/lib/meshchat/message/node_list.rb +41 -0
  37. data/lib/meshchat/message/node_list_diff.rb +15 -0
  38. data/lib/meshchat/message/node_list_hash.rb +29 -0
  39. data/lib/meshchat/message/ping.rb +32 -0
  40. data/lib/meshchat/message/ping_reply.rb +9 -0
  41. data/lib/meshchat/message/relay.rb +43 -0
  42. data/lib/meshchat/message/whisper.rb +36 -0
  43. data/lib/meshchat/models/entry.rb +104 -0
  44. data/lib/meshchat/net/client.rb +60 -0
  45. data/lib/meshchat/net/listener/request.rb +48 -0
  46. data/lib/meshchat/net/listener/request_processor.rb +45 -0
  47. data/lib/meshchat/net/listener/server.rb +61 -0
  48. data/lib/meshchat/net/request.rb +29 -0
  49. data/lib/meshchat/notifier/base.rb +50 -0
  50. data/lib/meshchat/version.rb +3 -0
  51. 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