noam_lemma 0.2.1

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
+ SHA1:
3
+ metadata.gz: 87f2a65c040cab0caea74d12a7e8d58d4ed36dd1
4
+ data.tar.gz: 0d9d0a71273809b6f28436a944fd69b2b9696c7e
5
+ SHA512:
6
+ metadata.gz: 1883fc59b41232681372d16811a62d4d73d135aca9fba538f74f924b693d7d3b06a3df0b8452a0db50b35a5a56b1f85ebbabdfbde15aafc75e577c677d7636d1
7
+ data.tar.gz: 4933af13dc4c016f0f2038cff34c1bff9d9d6af4b83e027c6238cd52b3b71aaae389dddc58fee98f8154234e780e2285529c2a5e252d840b8d79e515a829580c
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ -r spec_helper
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in noam_lemma.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 John Van Enk
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,20 @@
1
+ noam\_lemma
2
+ ===============
3
+
4
+ A Noam Lemma implementation for Ruby.
5
+
6
+ This library exposes the fundamental concepts in a Lemma to Ruby developers. It
7
+ handles registration, subscription, and message processing. All one needs to do
8
+ to create a new Lemma in a network is interact with the Noam::Lemma class. See
9
+ the `example/` directory in the project for further details on usage.
10
+
11
+ Build the ruby gem from noam lemma directory
12
+ `gem build noam_lemma.gemspec`
13
+
14
+ Install the gem
15
+ `gem install noam_lemma-version.gem`
16
+
17
+ Known Issues
18
+ ------------
19
+
20
+ * Listening fails un-gracefully when the server goes down.
data/Rakefile ADDED
@@ -0,0 +1,11 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ require "./examples/lemma_verification"
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ desc "perform verification tests"
9
+ task :verify do
10
+ Noam::LemmaVerification.run
11
+ end
@@ -0,0 +1,34 @@
1
+ require 'noam_lemma'
2
+
3
+ # This is an example of a Ruby Lemma that publishes message and *also* uses the
4
+ # "Guest" model of connection. This Lemma will advertise that it's available on
5
+ # the local network and only begin speaking messages once a server requests a
6
+ # connection from the Lemma.
7
+
8
+ publisher = Noam::Lemma.new('example-guest-publisher', [], ["e3"])
9
+
10
+ # Using the `advertise` method asks the Lemma to announce it's presence and
11
+ # wait for a message from a server that may want to connect to it.
12
+ #
13
+ # The "local-test" parameter is the room name. Servers with a room name that's
14
+ # the same as the Lemma's advertised room name will connect automatically.
15
+ publisher.advertise("")
16
+
17
+ seq = 0
18
+ e = "e3"
19
+ loop do
20
+ # Construct a value to send with the event.
21
+ v = {"seq" => seq, "time" => Time.now.to_s}
22
+
23
+ # If `speak` returns false, we're unable to speak the message likely because
24
+ # the socket has closed. The connection would have to be restarted.
25
+ unless publisher.speak(e, v)
26
+ puts "Done"
27
+ break
28
+ end
29
+ puts "Wrote: #{e} -> #{v.inspect}"
30
+
31
+ seq += 1
32
+ # Sleep for a while so that we don't bog down the network.
33
+ sleep(1)
34
+ end
@@ -0,0 +1,31 @@
1
+ require 'noam_lemma'
2
+
3
+ # This is an example of a Ruby Lemma that publishes message and *also* uses the
4
+ # "Guest" model of connection. This Lemma will advertise that it's available on
5
+ # the local network and only begin subscribing to messages once a server
6
+ # requests a connection from the Lemma.
7
+
8
+ subscriber = Noam::Lemma.new('example-guest-subscriber', ["e3"], [])
9
+
10
+ # Using the `advertise` method asks the Lemma to announce it's presence and
11
+ # wait for a message from a server that may want to connect to it.
12
+ #
13
+ # The "local-test" parameter is the room name. Servers with a room name that's
14
+ # the same as the Lemma's advertised room name will connect automatically.
15
+ subscriber.advertise("")
16
+
17
+ loop do
18
+ # The `listen` method will return an Event object once one is received by the
19
+ # Lemma. Until an event is heard, the `listen` method blocks.
20
+ m = subscriber.listen
21
+
22
+ # There's one special value that's returned from `listen`: the `:cancelled`
23
+ # symbol. If this shows up, it means some one else has called the `stop`
24
+ # method on the Lemma.
25
+ if :cancelled == m
26
+ puts "Done"
27
+ break
28
+ else
29
+ puts "Read: #{m.event} -> #{m.value.inspect}"
30
+ end
31
+ end
@@ -0,0 +1,43 @@
1
+ require "noam_lemma"
2
+
3
+ class Noam::LemmaVerification
4
+ def self.run
5
+ echo
6
+ plus_one
7
+ sum
8
+ name
9
+ end
10
+
11
+ def self.echo
12
+ lemma = Noam::Lemma.new("verification", ["Echo"], ["EchoVerify"])
13
+ lemma.advertise("lemma_verification")
14
+ event = lemma.listen
15
+ lemma.speak("EchoVerify", event.value)
16
+ lemma.stop
17
+ end
18
+
19
+ def self.plus_one
20
+ lemma = Noam::Lemma.new("verification", ["PlusOne"], ["PlusOneVerify"])
21
+ lemma.advertise("lemma_verification")
22
+ event = lemma.listen
23
+ lemma.speak("PlusOneVerify", event.value + 1)
24
+ lemma.stop
25
+ end
26
+
27
+ def self.sum
28
+ lemma = Noam::Lemma.new("verification", ["Sum"], ["SumVerify"])
29
+ lemma.advertise("lemma_verification")
30
+ event = lemma.listen
31
+ lemma.speak("SumVerify", event.value.inject {|sum, v| sum + v})
32
+ lemma.stop
33
+ end
34
+
35
+ def self.name
36
+ lemma = Noam::Lemma.new("verification", ["Name"], ["NameVerify"])
37
+ lemma.advertise("lemma_verification")
38
+ event = lemma.listen
39
+ fullname = "#{event.value["firstName"]} #{event.value["lastName"]}"
40
+ lemma.speak("NameVerify", {fullName: fullname})
41
+ lemma.stop
42
+ end
43
+ end
@@ -0,0 +1,47 @@
1
+ require 'noam_lemma'
2
+
3
+ # This is an example of a Ruby Lemma that publishes messages. It expects a Noam
4
+ # server to be running. It also expects that the noam-lemma.rb file is in the
5
+ # search path. If you run this example from the project root, the following
6
+ # command should work:
7
+ #
8
+ # ruby -Ilib example/publisher.rb
9
+ #
10
+ # This example _will not_ work on the same machine running the Noam server as
11
+ # both programs need to bind to UDP port 1030.
12
+
13
+ publisher = Noam::Lemma.new('example-publisher', [], ["e1", "e2"])
14
+
15
+ # Using the `discover` method asks the Lemma to proactively try and discover a
16
+ # server to connect to on the local network. Once the server is discovered, it
17
+ # will connect and send a Noam 'register' message. When `discover` returns, the
18
+ # Lemma is ready to send events.
19
+ publisher.discover
20
+
21
+ seq = 0
22
+ loop do
23
+ # This method block picks an event to send. It's randomized a bit so that we
24
+ # can see things change over time.
25
+ e = if 0.5 < rand()
26
+ "e1"
27
+ else
28
+ "e2"
29
+ end
30
+
31
+ # Next, package the event sequence and the current time into a value.
32
+ v = {"seq" => seq, "time" => Time.now.to_s}
33
+
34
+ # Attempt to speak the chosen event with the value. Note, the event is either
35
+ # "e1" or "e2" based on how rand() returned.
36
+ unless publisher.speak(e, v)
37
+ # If `speak` returns false, we're unable to speak the message likely because
38
+ # the socket has closed. The connection would have to be restarted.
39
+ puts "Done"
40
+ break
41
+ end
42
+ puts "Wrote: #{e} -> #{v.inspect}"
43
+
44
+ seq += 1
45
+ # Sleep for a while so that we don't bog down the network.
46
+ sleep(1)
47
+ end
@@ -0,0 +1,35 @@
1
+ require 'noam_lemma'
2
+
3
+ # This is an example of a Ruby Lemma that subscribes to messages. It expects a
4
+ # Noam server to be running. It also expects that the noam-lemma.rb file is in
5
+ # the search path. If you run this example from the project root, the following
6
+ # command should work:
7
+ #
8
+ # ruby -Ilib example/subscriber.rb
9
+ #
10
+ # This example _will not_ work on the same machine running the Noam server as
11
+ # both programs need to bind to UDP port 1030.
12
+
13
+ subscriber = Noam::Lemma.new('example-subscriber', ["e1", "e2"], [])
14
+
15
+ # Using the `discover` method asks the Lemma to proactively try and discover a
16
+ # server to connect to on the local network. Once the server is discovered, it
17
+ # will connect and send a Noam 'register' message. When `discover` returns, the
18
+ # Lemma is ready to receive events.
19
+ subscriber.discover
20
+
21
+ loop do
22
+ # The `listen` method will return an Event object once one is received by the
23
+ # Lemma. Until an event is heard, the `listen` method blocks.
24
+ m = subscriber.listen
25
+
26
+ # There's one special value that's returned from `listen`: the `:cancelled`
27
+ # symbol. If this shows up, it means some one else has called the `stop`
28
+ # method on the Lemma.
29
+ if :cancelled == m
30
+ puts "Done"
31
+ break
32
+ else
33
+ puts "Read: #{m.event} -> #{m.value.inspect}"
34
+ end
35
+ end
@@ -0,0 +1,36 @@
1
+ module Noam
2
+ class Beacon
3
+ attr_reader :name, :host, :port
4
+
5
+ def initialize(name, host, port)
6
+ @name = name
7
+ @host = host
8
+ @port = port
9
+ end
10
+
11
+ def self.discover(net = "0.0.0.0")
12
+ socket = UDPSocket.new
13
+ begin
14
+ socket.setsockopt(Socket::SOL_SOCKET, Socket::SO_BROADCAST, true)
15
+ socket.bind(net, Noam::BEACON_PORT)
16
+
17
+ raise "Didn't see beacon after #{WAIT_TIME} seconds." unless message_received?(socket)
18
+
19
+ data, addr = socket.recvfrom(MAX_RESPONSE_LENGTH)
20
+ parsed_data = JSON.parse(data)
21
+ Beacon.new(parsed_data[1], addr[2], parsed_data[2])
22
+ ensure
23
+ socket.close
24
+ end
25
+ end
26
+
27
+ private
28
+
29
+ MAX_RESPONSE_LENGTH = 1600
30
+ WAIT_TIME = 10.0
31
+
32
+ def self.message_received?(socket)
33
+ IO.select([socket], [], [], WAIT_TIME) != nil
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,70 @@
1
+ require "noam_lemma/message_filter"
2
+
3
+ module Noam
4
+ class Lemma
5
+ attr_reader :name, :listener, :player, :speaks
6
+
7
+ def initialize(name, hears = [], speaks = [])
8
+ @name = name
9
+ @speaks = speaks
10
+ @player = nil
11
+ @listener = nil
12
+
13
+ initialize_message_filter(hears)
14
+ end
15
+
16
+ def discover(beacon = nil)
17
+ beacon ||= Beacon.discover
18
+ start(beacon.host, beacon.port)
19
+ end
20
+
21
+ def advertise(room_name)
22
+ marco = Noam::Message::Marco.new(room_name, @name)
23
+ polo = marco.start
24
+ start(polo.host, polo.port)
25
+ end
26
+
27
+ def speak(event, value)
28
+ if @player
29
+ @player.put(Noam::Message::Playable.new(@name, event, value))
30
+ true
31
+ else
32
+ false
33
+ end
34
+ end
35
+
36
+ def listen
37
+ @listener.take
38
+ end
39
+
40
+ def stop
41
+ @player.stop if @player
42
+ @listener.stop if @listener
43
+ @player = nil
44
+ @listener = nil
45
+ end
46
+
47
+ def hears
48
+ @message_filter.hears
49
+ end
50
+
51
+ def set_message_filter(message_filter)
52
+ @message_filter = message_filter
53
+ end
54
+
55
+ private
56
+
57
+ def start(host, port)
58
+ @listener = Listener.new
59
+ @player = Player.new(host, port)
60
+ @player.put(Message::Register.new(@name, @listener.port, @message_filter.hears, @speaks))
61
+ end
62
+
63
+ def initialize_message_filter(hears)
64
+ @message_filter = MessageFilter.new
65
+ hears.each do |event_name|
66
+ @message_filter.hear(event_name) {}
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,73 @@
1
+ require 'thread'
2
+
3
+ module Noam
4
+ class Listener
5
+ attr_reader :port
6
+
7
+ def initialize
8
+ @queue = Queue.new
9
+ @server = TCPServer.new(0)
10
+ @port = @server.addr[1]
11
+
12
+ manage_queue_on_thread
13
+ end
14
+
15
+ def take
16
+ @queue.pop
17
+ end
18
+
19
+ def stop
20
+ @exit_requested = true
21
+ @thread.join
22
+ end
23
+
24
+ private
25
+
26
+ def manage_queue_on_thread
27
+ @thread = Thread.new do |t|
28
+ begin
29
+ loop_listen
30
+ ensure
31
+ @server.close
32
+ end
33
+ end
34
+ end
35
+
36
+ def loop_listen
37
+ loop do
38
+ if client = listen_for_connection
39
+ read_from_client(client)
40
+ client.close
41
+ end
42
+
43
+ if exiting?
44
+ @queue.push(:cancelled)
45
+ break
46
+ end
47
+ end
48
+ end
49
+
50
+ def listen_for_connection
51
+ timeout_sec = 0.1
52
+ available_ios = select([@server], nil, nil, timeout_sec)
53
+ @server.accept if available_ios
54
+ end
55
+
56
+ def read_from_client(client)
57
+ begin
58
+ loop do
59
+ message_length = client.read_nonblock(Message::MESSAGE_LENGTH_STRING_SIZE).to_i
60
+ message_content = client.read_nonblock(message_length)
61
+ @queue.push(Message::Heard.from_noam(message_content))
62
+ break if exiting?
63
+ end
64
+ rescue IO::WaitReadable
65
+ retry unless exiting?
66
+ end
67
+ end
68
+
69
+ def exiting?
70
+ return @exit_requested
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,18 @@
1
+ module Noam
2
+ module Message
3
+ class Heard
4
+ attr_reader :source, :event, :value
5
+
6
+ def initialize(source, event, value)
7
+ @source = source
8
+ @event = event
9
+ @value = value
10
+ end
11
+
12
+ def self.from_noam(noam)
13
+ _, source, event, value = JSON.parse(noam)
14
+ Heard.new(source, event, value)
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,57 @@
1
+ require 'socket'
2
+ require 'json'
3
+
4
+ module Noam
5
+ module Message
6
+ class Marco
7
+ attr_reader :port
8
+
9
+ def initialize(room_name, lemma_name)
10
+ @room_name = room_name
11
+ @lemma_name = lemma_name
12
+ end
13
+
14
+ def start
15
+ bcast_socket = UDPSocket.new
16
+ reply_socket = UDPSocket.new
17
+ reply_socket.bind("0.0.0.0", 0)
18
+
19
+ begin
20
+ bcast_socket.setsockopt(Socket::SOL_SOCKET, Socket::SO_BROADCAST, true)
21
+
22
+ loop do
23
+ bcast_socket.send(noam_encode, 0, "255.255.255.255", Noam::BEACON_PORT)
24
+ if message_received?(bcast_socket)
25
+ break
26
+ end
27
+ end
28
+
29
+ get_polo_response(bcast_socket)
30
+ ensure
31
+ reply_socket.close
32
+ end
33
+ end
34
+
35
+ def noam_encode
36
+ ["marco", @lemma_name, @room_name, Noam::DEVICE_TYPE, Noam::VERSION].to_json
37
+ end
38
+
39
+ private
40
+
41
+ MAX_RESPONSE_LENGTH = 1600
42
+ WAIT_TIME = 5.0
43
+
44
+ def message_received?(socket)
45
+ IO.select([socket], [], [], WAIT_TIME)
46
+ end
47
+
48
+ def get_polo_response(socket)
49
+ message, sockaddr = socket.recvfrom(MAX_RESPONSE_LENGTH)
50
+ _, _, noam_port = JSON.parse(message)
51
+ _, _, addr, _ = sockaddr
52
+
53
+ Polo.new(addr, noam_port)
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,16 @@
1
+ module Noam
2
+ module Message
3
+ class Playable
4
+ def initialize(host_id, event, value)
5
+ @host_id = host_id
6
+ @event = event
7
+ @value = value
8
+ end
9
+
10
+ def noam_encode
11
+ j = ['event', @host_id, @event, @value].to_json
12
+ Noam::Message.encode_length(j.length) + j
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,15 @@
1
+ module Noam
2
+ module Message
3
+ class Polo
4
+ attr_reader :host, :port
5
+
6
+ class InvalidHost < Exception; end
7
+ class InvalidPort < Exception; end
8
+
9
+ def initialize(host, port)
10
+ raise InvalidHost.new if (@host = host).nil?
11
+ raise InvalidPort.new if (@port = port).nil?
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,17 @@
1
+ module Noam
2
+ module Message
3
+ class Register
4
+ def initialize(device_id, port, hears, speaks)
5
+ @device_id = device_id
6
+ @port = port
7
+ @hears = hears
8
+ @speaks = speaks
9
+ end
10
+
11
+ def noam_encode
12
+ j = ["register", @device_id, @port.to_i, @hears, @speaks, Noam::DEVICE_TYPE, Noam::VERSION].to_json
13
+ Noam::Message.encode_length(j.length) + j
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,15 @@
1
+ module Noam
2
+ module Message
3
+ MESSAGE_LENGTH_STRING_SIZE = 6
4
+
5
+ def self.encode_length(l)
6
+ ("%06u" % l)
7
+ end
8
+ end
9
+ end
10
+
11
+ require 'noam_lemma/message/heard'
12
+ require 'noam_lemma/message/marco'
13
+ require 'noam_lemma/message/playable'
14
+ require 'noam_lemma/message/polo'
15
+ require 'noam_lemma/message/register'
@@ -0,0 +1,24 @@
1
+ module Noam
2
+ class MessageFilter
3
+ def initialize
4
+ @hears = {}
5
+ end
6
+
7
+ def hear(event_name, &block)
8
+ @hears[event_name] ||= []
9
+ @hears[event_name] << block
10
+ end
11
+
12
+ def receive(message)
13
+ blocks = @hears[message.event] || []
14
+ blocks.each do |block|
15
+ block.call(message)
16
+ end
17
+ message
18
+ end
19
+
20
+ def hears
21
+ @hears.keys.uniq
22
+ end
23
+ end
24
+ end