rinterface 0.0.3

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.
@@ -0,0 +1,37 @@
1
+ module Erlang
2
+ module External
3
+ module Types
4
+ SMALL_INT = 97
5
+ INT = 98
6
+
7
+ SMALL_BIGNUM = 110
8
+ LARGE_BIGNUM = 111
9
+
10
+ FLOAT = 99
11
+ NEW_FLOAT = 70
12
+
13
+ ATOM = 100
14
+ REF = 101 #old style reference
15
+ NEW_REF = 114
16
+ PORT = 102 #not supported accross node boundaries
17
+ PID = 103
18
+
19
+ SMALL_TUPLE = 104
20
+ LARGE_TUPLE = 105
21
+
22
+ NIL = 106
23
+ STRING = 107
24
+ LIST = 108
25
+ BIN = 109
26
+
27
+ FUN = 117
28
+ NEW_FUN = 112
29
+ end
30
+
31
+ VERSION = 131
32
+
33
+ MAX_INT = (1 << 27) -1
34
+ MIN_INT = -(1 << 27)
35
+ MAX_ATOM = 255
36
+ end
37
+ end
@@ -0,0 +1,242 @@
1
+ #
2
+ # Single shot client.
3
+ # - connects to epmd to get the port number of the erlang node
4
+ # - does the handshake
5
+ # - makes the RPC call
6
+ #
7
+ module Erlang
8
+ class Node
9
+ attr_reader :result
10
+
11
+ def initialize
12
+ @result = nil
13
+ end
14
+
15
+ class << self
16
+ def fun(node, mod, fun, *args)
17
+ rpc(node, mod, fun, args)
18
+ end
19
+
20
+ def rpc(node, mod, fun, args)
21
+ n = self.new
22
+ setup = proc{ n.do_connect(node, mod, fun, args) }
23
+
24
+ if EM.reactor_running?
25
+ setup.call
26
+ else
27
+ EM.run(&setup)
28
+ end
29
+ n.result
30
+ end
31
+ #alias :fun :rpc
32
+ end
33
+
34
+ def do_connect(node,mod,fun,args)
35
+ epmd = EpmdConnection.lookup_node(node)
36
+ epmd.callback do |port|
37
+ conn = NodeConnection.rpc_call(node,port,mod,fun,args) do |c|
38
+ c.destnode = node
39
+ c.mod = mod
40
+ c.fun = fun
41
+ c.args = args
42
+ c.port = port
43
+ c.myname = build_nodename
44
+ c.cookie = get_cookie
45
+ end
46
+ conn.callback do |r|
47
+ # Check for bad rpc response
48
+ # this is where I miss the patten matching in Erlang
49
+ if r.is_a?(Array)
50
+ if !r.empty? && r[0] == :badrpc
51
+ @result = [:badrpc,r[1]]
52
+ else
53
+ @result = [:ok,r]
54
+ end
55
+ else
56
+ @result = [:ok,r]
57
+ end
58
+ EM.stop
59
+ end
60
+ conn.errback do |err|
61
+ # never called??
62
+ @result = [:badrpc,err]
63
+ EM.stop
64
+ end
65
+ end
66
+ epmd.errback do |err|
67
+ # return bad RPC no port found (0)
68
+ @result = [:badrpc,"no port found for service"]
69
+ EM.stop
70
+ end
71
+ end
72
+
73
+ end
74
+
75
+
76
+ class NodeConnection < EM::Connection
77
+ include EM::Deferrable
78
+
79
+ attr_accessor :host,:myname,:destnode,:port,:cookie,:mod,:fun,:args
80
+
81
+ # node = destination node
82
+ # port = the port of the Erlang node (from epmd)
83
+ # mod = the module to call
84
+ # fun = the function to call
85
+ # args = args to pass to fun
86
+ def self.rpc_call(node,port,mod,fun,args)
87
+ EM.connect("127.0.0.1",port,self) do |c|
88
+ c.destnode = node
89
+ c.mod = mod
90
+ c.fun = fun
91
+ c.args = args
92
+ c.port = port
93
+ c.myname = build_nodename
94
+ c.cookie = get_cookie
95
+ end
96
+ end
97
+
98
+
99
+ # Get the Cookie from the home directory
100
+ def self.get_cookie
101
+ # ... I did it all for the cookie, come on the cookie ...
102
+ fp = File.expand_path("~#{ENV['USER']}/.erlang.cookie")
103
+ fh = File.open(fp,'r')
104
+ fh.readline.strip
105
+ end
106
+
107
+ # Build a nodename for us
108
+ def self.build_nodename
109
+ require 'socket'
110
+ myhostname = Socket.gethostname.split(".")[0]
111
+ "ruby_client@#{myhostname}"
112
+ end
113
+
114
+ def post_init
115
+ @responder = :determine_message
116
+ end
117
+
118
+ def connection_completed
119
+ send_data send_name
120
+ end
121
+
122
+ def receive_data data
123
+ @resp = data
124
+ send @responder
125
+ end
126
+
127
+ def handle_any_response
128
+ result = ""
129
+ decoder = Decode.read_bits(@resp)
130
+ s = decoder.read_4
131
+ #puts "Size: #{s}"
132
+ code = decoder.read_string(1)
133
+ if code == 'p'
134
+ #puts "found the p"
135
+ # read the control message and ignore
136
+ decoder.read_any
137
+ # read the message
138
+ result = decoder.read_any
139
+ #puts "Raw Response: #{result.inspect}"
140
+ set_deferred_success result[1]
141
+ else
142
+ # This seems to never happen...always 'p'
143
+ result = decoder.read_any
144
+ set_deferred_failure result
145
+ end
146
+ end
147
+
148
+ def send_name
149
+ full_host_name = self.myname
150
+ encode = Encoder.new
151
+ encode.write_2(full_host_name.length + 7)
152
+ # node type
153
+ encode.write_1(110)
154
+ # distChoose
155
+ encode.write_2(5)
156
+ # flags
157
+ encode.write_4(4|256|1024|2048)
158
+ # node name
159
+ encode.write_string(full_host_name)
160
+ encode.out.string
161
+ end
162
+
163
+ def determine_message
164
+ decoder = Decode.read_bits(@resp)
165
+ packet_size = decoder.read_2
166
+ #puts "PacketSize: #{packet_size}"
167
+ status_code = decoder.read_string(1)
168
+ case status_code
169
+ when 's' then receive_status(packet_size,decoder)
170
+ when 'n' then receive_challenge(packet_size,decoder)
171
+ when 'a' then receive_challenge_ack(packet_size,decoder)
172
+ else "Got back a weird packet"
173
+ end
174
+ end
175
+
176
+ def receive_status(packet_size,decoder)
177
+ status = decoder.read_string(packet_size-1)
178
+ if decoder.in.size > (packet_size + 2)
179
+ # Hack when both receive packets are crammed together into 1
180
+ next_packet_size = decoder.read_2 # read size
181
+ status_code = decoder.read_string(1)
182
+ receive_challenge(next_packet_size, decoder)
183
+ end
184
+ set_deferred_failure "Failed on Recv Status: #{status_code}" unless status == 'ok'
185
+ end
186
+
187
+ def receive_challenge(packet_size,decoder)
188
+ dist_code = decoder.read_2
189
+ #puts "Code: #{dist_code}"
190
+ flags = decoder.read_4
191
+ #puts "Flags #{flags}"
192
+ challenge = decoder.read_4
193
+ his_name = decoder.read_string(decoder.in.size-13)
194
+ #puts "His name: #{his_name}"
195
+ #puts "Got the challenge #{challenge}"
196
+ #out_challenge = make_challenge(challenge)
197
+ send_data make_challenge(challenge)
198
+ end
199
+
200
+ def make_challenge(her_challenge)
201
+ incr_digest = Digest::MD5.new()
202
+ incr_digest << @cookie
203
+ incr_digest << her_challenge.to_s
204
+ digest = incr_digest.digest
205
+ our_challenge = rand(10000)
206
+ encoder = Encoder.new
207
+ encoder.write_2(21)
208
+ encoder.write_string('r')
209
+ encoder.write_4(our_challenge)
210
+ encoder.write_string(digest)
211
+ encoder.out.string
212
+ end
213
+
214
+ # Handshake complete...send the RPC
215
+ def receive_challenge_ack(packet_size,decoder)
216
+ @responder = :handle_any_response
217
+ call_remote
218
+ end
219
+
220
+ def call_remote
221
+ myPid = Erlang::Terms::Pid.new(self.myname.intern,5,5,5)
222
+ call_tuple = [:call,self.mod.intern,self.fun.intern,Erlang::Terms::List.new(self.args),:user]
223
+ rpc_tuple = [myPid,call_tuple]
224
+ ctl_msg = [6,myPid,self.cookie.intern,:rex]
225
+
226
+ encode_data = Encoder.new
227
+ encode_data.term_to_binary(ctl_msg)
228
+ encode_data.term_to_binary(rpc_tuple)
229
+ data = encode_data.out.string
230
+
231
+ f = Encoder.new
232
+ f.write_4(data.length + 1)
233
+ final_out = f.out.string + 'p' + data
234
+ send_data final_out
235
+ end
236
+
237
+ end
238
+ end
239
+
240
+
241
+
242
+
data/lib/rinterface.rb ADDED
@@ -0,0 +1,13 @@
1
+ $:.unshift(File.dirname(__FILE__)) unless
2
+ $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
3
+
4
+ require "rubygems"
5
+ require "eventmachine"
6
+ require "digest/md5"
7
+ require "rinterface/erlang/external_format"
8
+ require "rinterface/erlang/types"
9
+ require "rinterface/erlang/encoder"
10
+ require "rinterface/erlang/decoder"
11
+ require "rinterface/epmd"
12
+ require "rinterface/node"
13
+ require "rinterface/erl"
@@ -0,0 +1,71 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{rinterface}
8
+ s.version = "0.0.3"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Dave Bryson", "\303\211verton Ribeiro", "Marcos Piccinini"]
12
+ s.date = %q{2010-03-31}
13
+ s.description = %q{Pure Ruby client that can send RPC calls to an Erlang node}
14
+ s.email = %q{r@interf.ace}
15
+ s.extra_rdoc_files = [
16
+ "README.md"
17
+ ]
18
+ s.files = [
19
+ ".gitignore",
20
+ "README.md",
21
+ "Rakefile",
22
+ "VERSION",
23
+ "examples/client.rb",
24
+ "examples/math_server.erl",
25
+ "lib/rinterface.rb",
26
+ "lib/rinterface/epmd.rb",
27
+ "lib/rinterface/erl.rb",
28
+ "lib/rinterface/erlang/decoder.rb",
29
+ "lib/rinterface/erlang/encoder.rb",
30
+ "lib/rinterface/erlang/external_format.rb",
31
+ "lib/rinterface/erlang/types.rb",
32
+ "lib/rinterface/node.rb",
33
+ "rinterface.gemspec",
34
+ "spec/rinterface/erl_spec.rb",
35
+ "spec/rinterface/erlang/external_format_spec.rb",
36
+ "spec/rinterface/node_spec.rb",
37
+ "spec/rinterface_spec.rb",
38
+ "spec/spec_helper.rb",
39
+ "spec/spec_server.erl"
40
+ ]
41
+ s.homepage = %q{http://github.com/davebryson/rinterface}
42
+ s.rdoc_options = ["--charset=UTF-8"]
43
+ s.require_paths = ["lib"]
44
+ s.rubygems_version = %q{1.3.6}
45
+ s.summary = %q{Erlang RPC Ruby Client}
46
+ s.test_files = [
47
+ "spec/rinterface_spec.rb",
48
+ "spec/spec_helper.rb",
49
+ "spec/rinterface/node_spec.rb",
50
+ "spec/rinterface/erl_spec.rb",
51
+ "spec/rinterface/erlang/external_format_spec.rb",
52
+ "examples/client.rb"
53
+ ]
54
+
55
+ if s.respond_to? :specification_version then
56
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
57
+ s.specification_version = 3
58
+
59
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
60
+ s.add_runtime_dependency(%q<eventmachine>, [">= 0"])
61
+ s.add_development_dependency(%q<rspec>, [">= 0"])
62
+ else
63
+ s.add_dependency(%q<eventmachine>, [">= 0"])
64
+ s.add_dependency(%q<rspec>, [">= 0"])
65
+ end
66
+ else
67
+ s.add_dependency(%q<eventmachine>, [">= 0"])
68
+ s.add_dependency(%q<rspec>, [">= 0"])
69
+ end
70
+ end
71
+
@@ -0,0 +1,24 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
+ include Rinterface
3
+
4
+ describe Erl do
5
+
6
+ it "should shotcut method calling" do
7
+ Erl::SpecServer.echo(:spec, "Rock").should eql([:ok, "Rock"])
8
+ end
9
+
10
+ it "should shotcut method calling" do
11
+ Erl::SpecServer.power(:spec, 10).should eql([:ok, 100])
12
+ end
13
+
14
+ it "should shotcut method calling" do
15
+ Erl::SpecServer.add(:spec, 2, 2).should be_ok_with(4)
16
+ end
17
+
18
+ it "should snake case" do
19
+ Erl.snake("SpecServer").should eql("spec_server")
20
+ Erl.snake("Fu").should eql("fu")
21
+ end
22
+
23
+ end
24
+
@@ -0,0 +1,5 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
2
+
3
+ describe "Erlang Terms" do
4
+
5
+ end
@@ -0,0 +1,5 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
+
3
+ describe "Node" do
4
+
5
+ end
@@ -0,0 +1,134 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ describe "Rinterface" do
4
+
5
+ describe "Erlang Spec Server" do
6
+
7
+ it "should work with arity 1" do
8
+ r = Erlang::Node.fun("spec","spec_server","power", 10)
9
+ r.should eql([:ok, 100])
10
+ end
11
+
12
+ it "should sum" do
13
+ r = Erlang::Node.fun("spec","spec_server","add", 10, 10)
14
+ r.should eql([:ok, 20])
15
+ end
16
+
17
+ it "should work the old way" do
18
+ r = Erlang::Node.rpc("spec","spec_server","add", [10, 10])
19
+ r.should eql([:ok, 20])
20
+ end
21
+
22
+ it "should have a nice matcher" do
23
+ r = Erlang::Node.fun("spec","spec_server","add", 10, 20)
24
+ r.should be_ok
25
+ end
26
+
27
+ it "should have a nicer matcher" do
28
+ r = Erlang::Node.fun("spec","spec_server","add", 10, 20)
29
+ r.should be_ok_with(30)
30
+ end
31
+ end
32
+
33
+ describe "Numbers" do
34
+
35
+ it "should work small ints" do
36
+ r = Erlang::Node.fun("spec","spec_server", "echo", 99)
37
+ r.should eql([:ok, 99])
38
+ end
39
+
40
+ it "should work with negative ints" do
41
+ r = Erlang::Node.fun("spec","spec_server", "echo", -99)
42
+ r.should eql([:ok, -99])
43
+ end
44
+
45
+ it "should work small ints 8bits" do
46
+ r = Erlang::Node.fun("spec","spec_server", "echo", 255)
47
+ r.should eql([:ok, 255])
48
+ end
49
+
50
+ it "should work medium ints +8bits" do
51
+ pending
52
+ r = Erlang::Node.fun("spec","spec_server", "echo", 255)
53
+ r.should eql([:ok, 256])
54
+ end
55
+
56
+ it "should work with floats" do
57
+ r = Erlang::Node.fun("spec","spec_server", "echo", 0.005)
58
+ r.should eql([:ok, 0.005])
59
+ r[1].should be_instance_of(Float)
60
+ end
61
+
62
+ it "should work with negative floats" do
63
+ r = Erlang::Node.fun("spec","spec_server", "echo", -0.5)
64
+ r.should eql([:ok, -0.5])
65
+ r[1].should be_instance_of(Float)
66
+ end
67
+
68
+ it "should work with floats" do
69
+ r = Erlang::Node.fun("spec","spec_server", "echo", 1234.5)
70
+ r.should eql([:ok, 1234.5])
71
+ end
72
+
73
+ it "should work with floats" do
74
+ r = Erlang::Node.fun("spec","spec_server", "echo", 1234.59999)
75
+ r.should eql([:ok, 1234.59999])
76
+ end
77
+
78
+ end
79
+
80
+ describe "Strings" do
81
+
82
+ it "should work with strings" do
83
+ r = Erlang::Node.fun("spec","spec_server", "dump", ["hi from ruby"])
84
+ r.should eql([:ok, :ok])
85
+ end
86
+
87
+ it "should work with strings" do
88
+ r = Erlang::Node.fun("spec","spec_server", "echo", "ping")
89
+ r.should eql([:ok, "ping"])
90
+ end
91
+
92
+ it "should work with strange chars I" do
93
+ r = Erlang::Node.fun("spec","spec_server", "echo", "($)[&]{@}")
94
+ r.should be_ok_with("($)[&]{@}")
95
+ end
96
+
97
+
98
+ end
99
+
100
+ describe "Collections" do
101
+
102
+ it "should work with atoms {sqr, 10}" do
103
+ r = Erlang::Node.rpc("spec","spec_server","area", {:sqr => 10})
104
+ r.should be_ok_with(100)
105
+ end
106
+
107
+ it "should work with atoms and integers {circ, 10}" do
108
+ r = Erlang::Node.rpc("spec","spec_server","area", {:circ => 10})
109
+ r.should be_ok_with(314.159)
110
+ end
111
+
112
+ it "should work with atoms and floats {circ, 10.1}" do
113
+ r = Erlang::Node.rpc("spec","spec_server","area", {:circ => 10.1})
114
+ r.should be_ok_with(320.47359589999996)
115
+ end
116
+
117
+ it "should work with atoms {sqr, 10}" do
118
+ r = Erlang::Node.rpc("spec","spec_server","echo", {:sqr => 10})
119
+ r.should eql([:ok, [:sqr, 10]])
120
+ end
121
+
122
+ it "should work with arrays" do
123
+ r = Erlang::Node.fun("spec","spec_server","distance", [1,1], [2,2])
124
+ r.should be_ok_with(1.4142135623730951)
125
+ end
126
+
127
+ it "should return errors" do
128
+ r = Erlang::Node.fun("p","spec_server","add", 10, 20)
129
+ r.should_not be_ok
130
+ end
131
+
132
+ end
133
+
134
+ end
@@ -0,0 +1,63 @@
1
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
2
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
3
+ require 'rinterface'
4
+ require 'spec'
5
+ require 'spec/autorun'
6
+ module RinterfaceMatchers
7
+ class BeOk
8
+
9
+ def initialize(expect=nil)
10
+ @expect = expect
11
+ end
12
+
13
+ def matches?(actual)
14
+ code, @actual = actual
15
+ cc = code == :ok
16
+ @expect ? (@actual == @expect && cc) : cc
17
+ end
18
+
19
+ def failure_message; "expected #{@expect} but received #{@actual.inspect}"; end
20
+ def negative_failure_message; "expected something else then '#{@expect}' but got '#{@actual}'"; end
21
+ end
22
+
23
+ def be_ok; BeOk.new; end
24
+
25
+ def be_ok_with(v)
26
+ BeOk.new(v)
27
+ end
28
+ end
29
+
30
+
31
+ # Hack-ish
32
+ module Daemon
33
+ NAME = "spec_server"
34
+ COMM = "erl -noshell -W -pa spec -sname spec -s #{NAME}&"
35
+
36
+ def self.start!
37
+ if is_running?
38
+ puts "Spec daemon running. (PID #{@pid})"
39
+ else
40
+ puts "Starting daemon.."
41
+ system("rake spec:run")#erl -noshell -W -pa spec -sname spec -s spec_server&")
42
+ sleep 1
43
+ start!
44
+ end
45
+ end
46
+
47
+ def self.is_running?
48
+ # Avoids grep from greping itself
49
+ n2, n1 = NAME[-1].chr, NAME.chop
50
+ if pid = `ps x | grep #{n1}[#{n2}]`.split(/\s/)[1]
51
+ @pid = pid.to_i
52
+ else
53
+ false
54
+ end
55
+ end
56
+ end
57
+
58
+ puts Daemon.start!
59
+
60
+ Spec::Runner.configure do |config|
61
+ config.include RinterfaceMatchers
62
+ end
63
+
@@ -0,0 +1,29 @@
1
+ -module(spec_server).
2
+ -export([start/0, power/1, echo/1, dump/1, add/2, area/1, distance/2]).
3
+
4
+ start() -> ok.
5
+
6
+ echo(R) -> R.
7
+
8
+ add(X, Y) -> X + Y.
9
+
10
+ power(P) -> P * P.
11
+
12
+ dump(W) -> io:format("Received ~p~n", [W]).
13
+
14
+ area({sqr, S}) -> S * S;
15
+ area({ret, W, H}) -> W * H;
16
+ area({circ, R}) ->
17
+ io:format("Area received: ~p~n", [R]),
18
+ Result = 3.14159 * R * R,
19
+ io:format("Result ~p~n", [Result]),
20
+ Result.
21
+
22
+ distance(P1, P2) ->
23
+ io:format("Distance received: ~p, ~p~n", [P1, P2]),
24
+ {X1, Y1} = P1,
25
+ {X2, Y2} = P2,
26
+ Result = math:sqrt(math:pow(X2 - X1, 2) + math:pow(Y2 - Y1, 2)),
27
+ io:format("Result: ~p~n", [Result]),
28
+ Result.
29
+