MEChat 1.2.0

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