rinterface 0.0.3

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