meshchat 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
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