MEChat 1.2.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 3e81b866dee38480b59aa955074aae862729bb58570f5f712cddf9f197c5359f
4
+ data.tar.gz: 408448930057f827d762744384b41bec7eea5046d25ec3311b9f4608b80a67fd
5
+ SHA512:
6
+ metadata.gz: c3f4f4f96a05311799f72b2ba0a81b2482bcd5495c2f5c1aeb3d9c8bc53d236bcb4377c2bf6757f84c387f9c4097cba4e0fce543a7d57fed5b71bc6602757454
7
+ data.tar.gz: bef9f54ec1492c3eb3a6543bfb7da6e4403b3709a79d56410c47584e987594b60ea993030d31dc68823d5d6d65dc1c1a9ecf7053fbbd2dca6f7a7eb420a32527
data/Chat.rb ADDED
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+ require 'grpc'
3
+ require 'MEChat_pb'
4
+ require 'MEChat_services_pb'
5
+ require_relative './logger.rb'
6
+
7
+ this_dir = File.expand_path(File.dirname(__FILE__))
8
+ lib_dir = File.join(this_dir, 'lib')
9
+ $LOAD_PATH.unshift(lib_dir) unless $LOAD_PATH.include?(lib_dir)
10
+
11
+ class Chat < DistributedChat::ChatService::Service
12
+
13
+ def initialize ip,port,node_service
14
+ @node_service = node_service
15
+ @ip = ip
16
+ @port = port
17
+ @logger = MQTTLogger.new topic: "chat", ip: "#{ip.ip_address}:#{port}"
18
+ end
19
+
20
+ private def send_reply(ip:)
21
+ chat = DistributedChat::ChatService::Stub.new("#{ip.ip_address}:#{ip.ip_port}", :this_channel_is_insecure)
22
+ chat.release_critical_section(DistributedChat::Request.new(
23
+ username: "#{ip.ip_address}:#{ip.ip_port}",
24
+ message: "Reply",
25
+ timestamp: $alg.clock
26
+ ))
27
+ @logger.info "Sent reply to #{ip.ip_address}:#{ip.ip_port}"
28
+ end
29
+
30
+ def send_message(msg, _call)
31
+ $messages << msg
32
+ puts "#{msg.username}: #{msg.message}"
33
+ DistributedChat::Response.new status_code: 0
34
+ end
35
+
36
+ def request_critical_section(req,_call)
37
+ request = {:ip => Addrinfo.getaddrinfo(req.username.split(":")[0..-2].join(":"),req.username.split(":")[-1].to_i)[0], :message => req}
38
+ if $crit_section === 0
39
+ send_reply ip: request[:ip]
40
+ elsif $crit_section < 2 && request[:message].timestamp < $alg.clock
41
+ send_reply ip: request[:ip]
42
+ else
43
+ $alg.requests << request
44
+ end
45
+ DistributedChat::Response.new status_code: 0
46
+ end
47
+
48
+ def release_critical_section(req,_call)
49
+ $alg.replies << Addrinfo.getaddrinfo(req.username.split(":")[0..-2].join(":"),req.username.split(":")[-1].to_i)[0]
50
+ $alg.clock = [req.timestamp,$alg.clock].max + 1
51
+ DistributedChat::Response.new status_code: 0
52
+ end
53
+ end
data/NodeDiscovery.rb ADDED
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+ require 'grpc'
3
+ require 'MEChat_pb'
4
+ require 'MEChat_services_pb'
5
+ require_relative './logger.rb'
6
+ require 'socket'
7
+
8
+ this_dir = File.expand_path(File.dirname(__FILE__))
9
+ lib_dir = File.join(this_dir, 'lib')
10
+ $LOAD_PATH.unshift(lib_dir) unless $LOAD_PATH.include?(lib_dir)
11
+
12
+ class NodeInfoEnum
13
+ def initialize(nodes)
14
+ @nodes = nodes
15
+ end
16
+
17
+ def each
18
+ if block_given?
19
+ @nodes.each do |node|
20
+ yield DistributedChat::NodeInfo.new(address: node.address, port: node.port)
21
+ end
22
+ else
23
+ enum_for(:each)
24
+ end
25
+ end
26
+ end
27
+ class NodeDiscoveryImpl < DistributedChat::NodeDiscovery::Service
28
+
29
+ def initialize ip, port
30
+ @ip = ip
31
+ @port = port
32
+ @logger = MQTTLogger.new topic: "nodediscovery", ip: "#{ip.ip_address}:#{port}"
33
+ end
34
+ def register_node(node_info, _call)
35
+ if $nodes.find { |x| x.address === node_info.address && x.port === node_info.port }
36
+ @logger.warn node_info.address + ":" + node_info.port.to_s + " already registered"
37
+ raise GRPC::BadStatus.new(GRPC::Core::StatusCodes::ALREADY_EXISTS, "Node with ip: " + node_info.address + ":" + node_info.port.to_s + " already registered")
38
+ else
39
+ $nodes.push node_info
40
+ puts node_info.address + ":" + node_info.port.to_s + " joined network"
41
+ @logger.info "Registered node with ip:" + node_info.address + ":" + node_info.port.to_s
42
+ end
43
+ nodes_to_return = $nodes.reject { |x| x.address === node_info.address && x.port === node_info.port }
44
+ to_remove = []
45
+ nodes_to_return.each do |node|
46
+ begin
47
+ nodediscovery = DistributedChat::NodeDiscovery::Stub.new("#{node.address}:#{node.port}", :this_channel_is_insecure)
48
+ nodediscovery.register_node(DistributedChat::NodeInfo.new(address: node_info.address, port: node_info.port))
49
+ rescue GRPC::Unavailable => e
50
+ to_remove << node
51
+ end
52
+ end
53
+ $nodes -= to_remove
54
+ nodes_to_return -= to_remove
55
+ nodes_to_return << DistributedChat::NodeInfo.new(address: @ip.ip_address, port: @port)
56
+ NodeInfoEnum.new(nodes_to_return).each
57
+ end
58
+
59
+ def get_nodes(req,_call)
60
+ @logger.info "#{_call.peer[5..-1]} with username #{req.username} requested nodes list"
61
+ NodeInfoEnum.new($nodes).each
62
+ end
63
+
64
+ def get_nodes_local
65
+ $nodes
66
+ end
67
+
68
+ end
data/bin/chatdsva ADDED
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require_relative '../main.rb'
4
+
5
+ Main.new.main
6
+
data/client.rb ADDED
@@ -0,0 +1,94 @@
1
+ # frozen_string_literal: true
2
+ require 'grpc'
3
+ require 'MEChat_pb'
4
+ require 'MEChat_services_pb'
5
+ require_relative './NodeDiscovery.rb'
6
+ require_relative './logger.rb'
7
+
8
+ this_dir = File.expand_path(File.dirname(__FILE__))
9
+ lib_dir = File.join(this_dir, 'lib')
10
+ $LOAD_PATH.unshift(lib_dir) unless $LOAD_PATH.include?(lib_dir)
11
+
12
+ class Client
13
+
14
+ def initialize(ip: Addrinfo)
15
+ @ip = ip
16
+ @logger = MQTTLogger.new topic: "chat", ip: "#{ip.ip_address}:#{$port.to_s}"
17
+ end
18
+
19
+ def connect(ip:)
20
+ nodediscovery = DistributedChat::NodeDiscovery::Stub.new("#{ip.ip_address}:#{ip.ip_port}", :this_channel_is_insecure)
21
+ nodediscovery.register_node(DistributedChat::NodeInfo.new(address: @ip.ip_address, port: $port)).each do |node|
22
+ $nodes.push node
23
+ end
24
+
25
+ end
26
+
27
+ private def request_critical_section
28
+ $alg.mutex.synchronize do
29
+ $alg.clock += 1
30
+ to_remove = []
31
+ $nodes.each do |node|
32
+ begin
33
+ chat = DistributedChat::ChatService::Stub.new("#{node.address}:#{node.port}", :this_channel_is_insecure)
34
+ @logger.info "Sent request to #{node.address}:#{node.port}"
35
+ chat.request_critical_section(DistributedChat::Request.new(
36
+ username: "#{@ip.ip_address}:#{$port.to_s}",
37
+ message: "Request",
38
+ timestamp: $alg.clock
39
+ ))
40
+ rescue GRPC::Unavailable
41
+ to_remove << node
42
+ end
43
+ end
44
+ $nodes -= to_remove
45
+ end
46
+ end
47
+
48
+ private def send_reply(ip:)
49
+ chat = DistributedChat::ChatService::Stub.new("#{ip.ip_address}:#{ip.ip_port}", :this_channel_is_insecure)
50
+ chat.release_critical_section(DistributedChat::Request.new(
51
+ username: "#{ip.ip_address}:#{ip.ip_port}",
52
+ message: "Reply",
53
+ timestamp: $alg.clock
54
+ ))
55
+ @logger.info "Sent reply to #{ip.ip_address}:#{ip.ip_port}"
56
+ end
57
+
58
+ private def release_critical_section
59
+ $alg.mutex.synchronize do
60
+ $alg.requests.each do |request|
61
+ send_reply(ip: request[:ip])
62
+ end
63
+ $alg.requests.clear
64
+ $alg.replies.clear
65
+ end
66
+ end
67
+
68
+ def run
69
+ new_msg = gets.chomp
70
+ while new_msg != "exit"
71
+ $crit_section = 1
72
+ request_critical_section
73
+ sleepdur = 0
74
+ while $alg.replies.length < $nodes.length
75
+ sleepdur += 0.1
76
+ @logger.info "#{@ip.ip_address}:#{$port.to_s} waiting for replies"
77
+ sleep 0.1
78
+ if sleepdur % 5 == 0
79
+ puts "Waiting for replies. #{$alg.replies.length}/#{$nodes.length} replies received"
80
+ end
81
+ end
82
+ $crit_section = 2
83
+ $nodes.each do |node|
84
+ chat = DistributedChat::ChatService::Stub.new("#{node.address}:#{node.port}", :this_channel_is_insecure)
85
+ chat.send_message(DistributedChat::Request.new(username: "#{@ip.ip_address}:#{$port.to_s}", message: new_msg, timestamp: $alg.clock))
86
+ @logger.info "Sent message \"#{new_msg}\" to #{node.address}:#{node.port}"
87
+ end
88
+ $messages.push DistributedChat::Request.new(username: "#{@ip.ip_address}:#{$port.to_s}", message: new_msg, timestamp: $alg.clock)
89
+ release_critical_section
90
+ $crit_section = 0
91
+ new_msg = gets.chomp
92
+ end
93
+ end
94
+ end
data/lib/MEChat_pb.rb ADDED
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+ # Generated by the protocol buffer compiler. DO NOT EDIT!
3
+ # source: MEChat.proto
4
+
5
+ require 'google/protobuf'
6
+
7
+
8
+ descriptor_data = "\n\x0cMEChat.proto\x12\x10\x64istributed_chat\"?\n\x07Request\x12\x10\n\x08username\x18\x01 \x01(\t\x12\x0f\n\x07message\x18\x02 \x01(\t\x12\x11\n\ttimestamp\x18\x03 \x01(\x05\"\x1f\n\x08Response\x12\x13\n\x0bstatus_code\x18\x01 \x01(\x05\"O\n\x08NodeInfo\x12\x0f\n\x07\x61\x64\x64ress\x18\x01 \x01(\t\x12\x0c\n\x04port\x18\x02 \x01(\x05\x12\x16\n\ttimestamp\x18\x03 \x01(\x05H\x00\x88\x01\x01\x42\x0c\n\n_timestamp2\xf5\x01\n\x0b\x43hatService\x12\x44\n\x0bSendMessage\x12\x19.distributed_chat.Request\x1a\x1a.distributed_chat.Response\x12O\n\x16RequestCriticalSection\x12\x19.distributed_chat.Request\x1a\x1a.distributed_chat.Response\x12O\n\x16ReleaseCriticalSection\x12\x19.distributed_chat.Request\x1a\x1a.distributed_chat.Response2\x9e\x01\n\rNodeDiscovery\x12H\n\x0cRegisterNode\x12\x1a.distributed_chat.NodeInfo\x1a\x1a.distributed_chat.NodeInfo0\x01\x12\x43\n\x08GetNodes\x12\x19.distributed_chat.Request\x1a\x1a.distributed_chat.NodeInfo0\x01\x42\t\xaa\x02\x06MEChatb\x06proto3"
9
+
10
+ pool = Google::Protobuf::DescriptorPool.generated_pool
11
+
12
+ begin
13
+ pool.add_serialized_file(descriptor_data)
14
+ rescue TypeError
15
+ # Compatibility code: will be removed in the next major version.
16
+ require 'google/protobuf/descriptor_pb'
17
+ parsed = Google::Protobuf::FileDescriptorProto.decode(descriptor_data)
18
+ parsed.clear_dependency
19
+ serialized = parsed.class.encode(parsed)
20
+ file = pool.add_serialized_file(serialized)
21
+ warn "Warning: Protobuf detected an import path issue while loading generated file #{__FILE__}"
22
+ imports = [
23
+ ]
24
+ imports.each do |type_name, expected_filename|
25
+ import_file = pool.lookup(type_name).file_descriptor
26
+ if import_file.name != expected_filename
27
+ warn "- #{file.name} imports #{expected_filename}, but that import was loaded as #{import_file.name}"
28
+ end
29
+ end
30
+ warn "Each proto file must use a consistent fully-qualified name."
31
+ warn "This will become an error in the next major version."
32
+ end
33
+
34
+ module DistributedChat
35
+ Request = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("distributed_chat.Request").msgclass
36
+ Response = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("distributed_chat.Response").msgclass
37
+ NodeInfo = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("distributed_chat.NodeInfo").msgclass
38
+ end
@@ -0,0 +1,43 @@
1
+ # Generated by the protocol buffer compiler. DO NOT EDIT!
2
+ # Source: MEChat.proto for package 'distributed_chat'
3
+
4
+ require 'grpc'
5
+ require 'MEChat_pb'
6
+
7
+ module DistributedChat
8
+ module ChatService
9
+ class Service
10
+
11
+ include ::GRPC::GenericService
12
+
13
+ self.marshal_class_method = :encode
14
+ self.unmarshal_class_method = :decode
15
+ self.service_name = 'distributed_chat.ChatService'
16
+
17
+ # Method to send a message to the chat
18
+ rpc :SendMessage, ::DistributedChat::Request, ::DistributedChat::Response
19
+ # Method to request access to the critical section
20
+ rpc :RequestCriticalSection, ::DistributedChat::Request, ::DistributedChat::Response
21
+ # Method to release access to the critical section
22
+ rpc :ReleaseCriticalSection, ::DistributedChat::Request, ::DistributedChat::Response
23
+ end
24
+
25
+ Stub = Service.rpc_stub_class
26
+ end
27
+ # Define RPC methods for the distributed chat service
28
+ module NodeDiscovery
29
+ class Service
30
+
31
+ include ::GRPC::GenericService
32
+
33
+ self.marshal_class_method = :encode
34
+ self.unmarshal_class_method = :decode
35
+ self.service_name = 'distributed_chat.NodeDiscovery'
36
+
37
+ rpc :RegisterNode, ::DistributedChat::NodeInfo, stream(::DistributedChat::NodeInfo)
38
+ rpc :GetNodes, ::DistributedChat::Request, stream(::DistributedChat::NodeInfo)
39
+ end
40
+
41
+ Stub = Service.rpc_stub_class
42
+ end
43
+ end
data/logger.rb ADDED
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'mqtt'
4
+
5
+ class MQTTLogger
6
+ def initialize (topic: String, ip: String)
7
+ @client = MQTT::Client.new
8
+ @client.host = 'fireup.studio'
9
+ @client.connect
10
+ @topic = topic
11
+ @ip = ip
12
+ end
13
+
14
+ private def log level, msg
15
+ @client.publish(@topic, "#{@ip}|#{level}|#{msg}", retain=false)
16
+ end
17
+
18
+ def info msg
19
+ log "INFO",msg
20
+ end
21
+
22
+ def warn msg
23
+ log "WARN",msg
24
+ end
25
+
26
+ def error msg
27
+ log "ERROR",msg
28
+ end
29
+
30
+ def finalize
31
+ @client.disconnect
32
+ end
33
+ end
data/main.rb ADDED
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+ require 'grpc'
3
+ require 'MEChat_pb'
4
+ require 'MEChat_services_pb'
5
+ require_relative './NodeDiscovery.rb'
6
+ require_relative './server.rb'
7
+ require_relative './client.rb'
8
+ require_relative './ricart_agrawala.rb'
9
+
10
+ this_dir = File.expand_path(File.dirname(__FILE__))
11
+ lib_dir = File.join(this_dir, 'lib')
12
+ $LOAD_PATH.unshift(lib_dir) unless $LOAD_PATH.include?(lib_dir)
13
+ $port = RandomPort::Pool::SINGLETON.acquire
14
+ $nodes = []
15
+ $messages = []
16
+ $crit_section = 0
17
+ $alg = RicartAgrawala.new
18
+
19
+ class Main
20
+
21
+ def pick_ip
22
+ addrs = Socket.ip_address_list.reject( &:ipv4_loopback? )
23
+ .reject( &:ipv6_loopback? )
24
+ puts "Please select by which IP to present to other nodes"
25
+ addrs.each_with_index do |x,i|
26
+ puts "[#{i}] #{x.ip_address}"
27
+ end
28
+ puts "[#{addrs.length}] Manual IP"
29
+ picked = gets.to_i 10
30
+ return addrs[picked] unless picked.to_i >= addrs.length
31
+ Addrinfo.getaddrinfo(gets.chomp,80)[0]
32
+ end
33
+
34
+ def get_other_ip
35
+ puts "[J]oin network or [C]reate new network?"
36
+ choice = gets.chomp
37
+ return Addrinfo.getaddrinfo("224.0.0.1",80)[0] if choice.downcase[0] == "c"
38
+ puts "Please enter IP of a node in the network you wish to join"
39
+ ip = gets.chomp
40
+ puts "Please enter port of a node in the network you wish to join"
41
+ port = gets.to_i 10
42
+ Addrinfo.getaddrinfo(ip,port)[0]
43
+ end
44
+
45
+ def main
46
+ ip = pick_ip
47
+ connect_ip = get_other_ip
48
+ server = Thread.new { Server.new(ip: ip).run }
49
+ if connect_ip.ipv4_multicast?
50
+ client = Thread.new { Client.new(ip: ip).run }
51
+ else
52
+ client = Thread.new { Client.new(ip: ip).connect(ip: connect_ip); Client.new(ip: ip).run }
53
+ end
54
+ server.join
55
+ client.join
56
+ end
57
+
58
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+ require 'thread'
3
+
4
+ class RicartAgrawala
5
+ attr_accessor :clock, :replies, :requests
6
+ attr_reader :mutex
7
+
8
+ def initialize
9
+ @clock = 0
10
+ @replies = []
11
+ @requests = []
12
+ @mutex = Mutex.new
13
+ end
14
+ end
data/server.rb ADDED
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+ require 'grpc'
3
+ require 'random-port'
4
+ require 'MEChat_pb'
5
+ require 'MEChat_services_pb'
6
+ require_relative './NodeDiscovery.rb'
7
+ require_relative './Chat.rb'
8
+
9
+ this_dir = File.expand_path(File.dirname(__FILE__))
10
+ lib_dir = File.join(this_dir, 'lib')
11
+ $LOAD_PATH.unshift(lib_dir) unless $LOAD_PATH.include?(lib_dir)
12
+
13
+ class Server
14
+ def initialize(ip: Addrinfo)
15
+ @ip = ip
16
+ end
17
+
18
+ def run
19
+
20
+ node_discovery = NodeDiscoveryImpl.new @ip, $port
21
+ chat = Chat.new @ip,$port,node_discovery
22
+
23
+ server = GRPC::RpcServer.new
24
+ server.add_http2_port("0.0.0.0:#{$port.to_s}", :this_port_is_insecure)
25
+ server.handle(node_discovery)
26
+ server.handle(chat)
27
+ puts "Server side running on " + $port.to_s
28
+
29
+ server.run_till_terminated_or_interrupted(['int', 'SIGTERM'])
30
+
31
+ end
32
+ end
33
+
34
+ # Server.new.run
metadata ADDED
@@ -0,0 +1,151 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: MEChat
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.2.0
5
+ platform: ruby
6
+ authors:
7
+ - Lukas Kaufmann
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2024-01-08 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: grpc
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: multi_json
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: 1.13.1
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: 1.13.1
41
+ - !ruby/object:Gem::Dependency
42
+ name: mqtt
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: 0.6.0
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: 0.6.0
55
+ - !ruby/object:Gem::Dependency
56
+ name: random-port
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: 0.6.0
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: 0.6.0
69
+ - !ruby/object:Gem::Dependency
70
+ name: rbs
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: 3.3.2
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: 3.3.2
83
+ - !ruby/object:Gem::Dependency
84
+ name: bundler
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '2.4'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '2.4'
97
+ - !ruby/object:Gem::Dependency
98
+ name: grpc-tools
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '1.50'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '1.50'
111
+ description: SHM Chat app using gRPC and Ricart-Agrawala algorithm
112
+ email: kaufmlu1@fel.cvut.cz
113
+ executables:
114
+ - chatdsva
115
+ extensions: []
116
+ extra_rdoc_files: []
117
+ files:
118
+ - Chat.rb
119
+ - NodeDiscovery.rb
120
+ - bin/chatdsva
121
+ - client.rb
122
+ - lib/MEChat_pb.rb
123
+ - lib/MEChat_services_pb.rb
124
+ - logger.rb
125
+ - main.rb
126
+ - ricart_agrawala.rb
127
+ - server.rb
128
+ homepage:
129
+ licenses:
130
+ - MIT
131
+ metadata: {}
132
+ post_install_message:
133
+ rdoc_options: []
134
+ require_paths:
135
+ - lib
136
+ required_ruby_version: !ruby/object:Gem::Requirement
137
+ requirements:
138
+ - - ">="
139
+ - !ruby/object:Gem::Version
140
+ version: '0'
141
+ required_rubygems_version: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - ">="
144
+ - !ruby/object:Gem::Version
145
+ version: '0'
146
+ requirements: []
147
+ rubygems_version: 3.4.10
148
+ signing_key:
149
+ specification_version: 4
150
+ summary: gRPC Ruby shared memory chat app
151
+ test_files: []