caldecott 0.0.1 → 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.
data/README.md CHANGED
@@ -1,3 +1,2 @@
1
1
  # caldecott
2
-
3
- TBD
2
+ TCP over HTTP tunnel.
data/lib/caldecott.rb ADDED
@@ -0,0 +1,11 @@
1
+ # Copyright (c) 2009-2011 VMware, Inc.
2
+
3
+ require 'rubygems'
4
+ require 'bundler'
5
+
6
+ require 'caldecott/client'
7
+ require 'caldecott/server'
8
+
9
+ require 'caldecott/session_logger'
10
+ require 'caldecott/tcp_connection'
11
+ require 'caldecott/version'
@@ -0,0 +1,7 @@
1
+ # Copyright (c) 2009-2011 VMware, Inc.
2
+
3
+ require 'caldecott'
4
+ require 'caldecott/client/client'
5
+ require 'caldecott/client/tunnel'
6
+ require 'caldecott/client/http_tunnel'
7
+ require 'caldecott/client/websocket_tunnel'
@@ -0,0 +1,79 @@
1
+ # Copyright (c) 2009-2011 VMware, Inc.
2
+
3
+ require 'eventmachine'
4
+
5
+ module Caldecott
6
+ module Client
7
+ def self.sanitize_url(tun_url)
8
+ tun_url = tun_url =~ /(http|https|ws).*/i ? tun_url : "https://#{tun_url}"
9
+ end
10
+
11
+ def self.start(opts)
12
+ local_port = opts[:local_port]
13
+ tun_url = opts[:tun_url]
14
+ dst_host = opts[:dst_host]
15
+ dst_port = opts[:dst_port]
16
+ log_file = opts[:log_file]
17
+ log_level = opts[:log_level]
18
+ auth_token = opts[:auth_token]
19
+
20
+ @quiet = opts[:quiet]
21
+
22
+ trap("TERM") { stop }
23
+ trap("INT") { stop }
24
+
25
+ tun_url = sanitize_url(tun_url)
26
+
27
+ EM.run do
28
+ unless @quiet
29
+ puts "Starting local server on port #{local_port} to #{tun_url}"
30
+ end
31
+
32
+ EM.start_server("localhost", local_port, TcpConnection) do |conn|
33
+ # avoid races between tunnel setup and incoming local data
34
+ conn.pause
35
+
36
+ log = SessionLogger.new("client", log_file)
37
+ log.level = SessionLogger.severity_from_string(log_level)
38
+
39
+ tun = nil
40
+
41
+ conn.onopen do
42
+ log.debug "local connected"
43
+ tun = Tunnel.start(log, tun_url, dst_host, dst_port, auth_token)
44
+ end
45
+
46
+ tun.onopen do
47
+ log.debug "tunnel connected"
48
+ conn.resume
49
+ end
50
+
51
+ conn.onreceive do |data|
52
+ log.debug "l -> t #{data.length}"
53
+ tun.send_data(data)
54
+ end
55
+
56
+ tun.onreceive do |data|
57
+ log.debug("l <- t #{data.length}")
58
+ conn.send_data(data)
59
+ end
60
+
61
+ conn.onclose do
62
+ log.debug "local closed"
63
+ tun.close
64
+ end
65
+
66
+ tun.onclose do
67
+ log.debug "tunnel closed"
68
+ conn.close_connection_after_writing
69
+ end
70
+ end
71
+ end
72
+ end
73
+
74
+ def self.stop
75
+ puts "Caldecott shutting down" unless @quiet
76
+ EM.stop
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,236 @@
1
+ # Copyright (c) 2009-2011 VMware, Inc.
2
+
3
+ require 'em-http'
4
+ require 'json'
5
+
6
+ module Caldecott
7
+ module Client
8
+ class HttpTunnel
9
+ MAX_RETRIES = 10
10
+
11
+ def initialize(logger, url, dst_host, dst_port, auth_token)
12
+ @log, @auth_token = logger, auth_token
13
+ @closing = false
14
+ @retries = 0
15
+ init_msg = ""
16
+
17
+ # FIXME: why is this optional?
18
+ if dst_host
19
+ init_msg = { :host => dst_host, :port => dst_port }.to_json
20
+ end
21
+
22
+ start(url, init_msg)
23
+ end
24
+
25
+ def onopen(&blk)
26
+ @onopen = blk
27
+ @onopen.call if @opened
28
+ end
29
+
30
+ def onclose(&blk)
31
+ @onclose = blk
32
+ @onclose.call if @closed
33
+ end
34
+
35
+ def onreceive(&blk)
36
+ @onreceive = blk
37
+ end
38
+
39
+ def send_data(data)
40
+ @writer.send_data(data)
41
+ end
42
+
43
+ def close
44
+ return if @closing or @closed
45
+ @closing = true
46
+ @writer.close if @writer
47
+ @reader.close if @reader
48
+ stop
49
+ end
50
+
51
+ def trigger_on_open
52
+ @opened = true
53
+ @onopen.call if @onopen
54
+ end
55
+
56
+ def trigger_on_close
57
+ close
58
+ @closed = true
59
+ @onclose.call if @onclose
60
+ @onclose = nil
61
+ end
62
+
63
+ def trigger_on_receive(data)
64
+ @onreceive.call(data)
65
+ end
66
+
67
+ def start(base_uri, init_msg)
68
+ if (@retries += 1) > MAX_RETRIES
69
+ trigger_on_close
70
+ return
71
+ end
72
+
73
+ begin
74
+ parsed_uri = Addressable::URI.parse(base_uri)
75
+ parsed_uri.path = '/tunnels'
76
+
77
+ @log.debug "post #{parsed_uri.to_s}"
78
+ req = EM::HttpRequest.new(parsed_uri.to_s).post :body => init_msg, :head => { "Auth-Token" => @auth_token }
79
+
80
+ req.callback do
81
+ @log.debug "post #{parsed_uri.to_s} #{req.response_header.status}"
82
+ unless [200, 201, 204].include?(req.response_header.status)
83
+ start(base_uri, init_msg)
84
+ else
85
+ @retries = 0
86
+ resp = JSON.parse(req.response)
87
+
88
+ parsed_uri.path = resp["path"]
89
+ @tun_uri = parsed_uri.to_s
90
+
91
+ parsed_uri.path = resp["path_out"]
92
+ @reader = Reader.new(@log, parsed_uri.to_s, self, @auth_token)
93
+
94
+ parsed_uri.path = resp["path_in"]
95
+ @writer = Writer.new(@log, parsed_uri.to_s, self, @auth_token)
96
+ trigger_on_open
97
+ end
98
+ end
99
+
100
+ req.errback do
101
+ @log.debug "post #{parsed_uri.to_s} error"
102
+ start(base_uri, init_msg)
103
+ end
104
+
105
+ rescue Exception => e
106
+ @log.error e
107
+ trigger_on_close
108
+ raise e
109
+ end
110
+ end
111
+
112
+ def stop
113
+ if (@retries += 1) > MAX_RETRIES
114
+ trigger_on_close
115
+ return
116
+ end
117
+
118
+ return if @tun_uri.nil?
119
+
120
+ @log.debug "delete #{@tun_uri}"
121
+ req = EM::HttpRequest.new("#{@tun_uri}").delete :head => { "Auth-Token" => @auth_token }
122
+
123
+ req.errback do
124
+ @log.debug "delete #{@tun_uri} error"
125
+ stop
126
+ end
127
+
128
+ req.callback do
129
+ @log.debug "delete #{@tun_uri} #{req.response_header.status}"
130
+ if [200, 202, 204, 404].include?(req.response_header.status)
131
+ trigger_on_close
132
+ else
133
+ stop
134
+ end
135
+ end
136
+ end
137
+
138
+ class Reader
139
+ def initialize(log, uri, conn, auth_token)
140
+ @log, @base_uri, @conn, @auth_token = log, uri, conn, auth_token
141
+ @retries = 0
142
+ @closing = false
143
+ start
144
+ end
145
+
146
+ def close
147
+ @closing = true
148
+ end
149
+
150
+ def start(seq = 1)
151
+ if (@retries += 1) > MAX_RETRIES
152
+ @conn.trigger_on_close
153
+ return
154
+ end
155
+
156
+ return if @closing
157
+ uri = "#{@base_uri}/#{seq}"
158
+ @log.debug "get #{uri}"
159
+ req = EM::HttpRequest.new(uri).get :timeout => 0, :head => { "Auth-Token" => @auth_token }
160
+
161
+ req.errback do
162
+ @log.debug "get #{uri} error"
163
+ start(seq)
164
+ end
165
+
166
+ req.callback do
167
+ @log.debug "get #{uri} #{req.response_header.status}"
168
+ case req.response_header.status
169
+ when 200
170
+ @conn.trigger_on_receive(req.response)
171
+ @retries = 0
172
+ start(seq + 1)
173
+ when 404
174
+ @conn.trigger_on_close
175
+ else
176
+ start(seq)
177
+ end
178
+ end
179
+ end
180
+ end
181
+
182
+ class Writer
183
+ def initialize(log, uri, conn, auth_token)
184
+ @log, @uri, @conn, @auth_token = log, uri, conn, auth_token
185
+ @retries = 0
186
+ @seq, @write_buffer = 1, ""
187
+ @closing = @writing = false
188
+ end
189
+
190
+ def send_data(data)
191
+ @write_buffer << data
192
+ send_data_buffered
193
+ end
194
+
195
+ def close
196
+ @closing = true
197
+ end
198
+
199
+ def send_data_buffered
200
+ if (@retries += 1) > MAX_RETRIES
201
+ @conn.trigger_on_close
202
+ return
203
+ end
204
+
205
+ return if @closing
206
+ data, @write_buffer = @write_buffer, "" unless @writing
207
+
208
+ @writing = true
209
+ uri = "#{@uri}/#{@seq}"
210
+ @log.debug "put #{uri}"
211
+ req = EM::HttpRequest.new(uri).put :body => data, :head => { "Auth-Token" => @auth_token }
212
+
213
+ req.errback do
214
+ @log.debug "put #{uri} error"
215
+ send_data_buffered
216
+ end
217
+
218
+ req.callback do
219
+ @log.debug "put #{uri} #{req.response_header.status}"
220
+ case req.response_header.status
221
+ when 200, 202, 204
222
+ @writing = false
223
+ @seq += 1
224
+ @retries = 0
225
+ send_data_buffered unless @write_buffer.empty?
226
+ when 404
227
+ @conn.trigger_on_close
228
+ else
229
+ send_data_buffered
230
+ end
231
+ end
232
+ end
233
+ end
234
+ end
235
+ end
236
+ end
@@ -0,0 +1,23 @@
1
+ # Copyright (c) 2009-2011 VMware, Inc.
2
+
3
+ require 'addressable/uri'
4
+
5
+ module Caldecott
6
+ module Client
7
+ module Tunnel
8
+ # Note: I wanted to do this with self#new but had issues
9
+ # with getting send :initialize to figure out the right
10
+ # number of arguments
11
+ def self.start(logger, tun_url, dst_host, dst_port, auth_token)
12
+ case Addressable::URI.parse(tun_url).normalized_scheme
13
+ when "http", "https"
14
+ HttpTunnel.new(logger, tun_url, dst_host, dst_port, auth_token)
15
+ when "ws"
16
+ WebSocketTunnel.new(logger, tun_url, dst_host, dst_port, auth_token)
17
+ else
18
+ raise "invalid url"
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,36 @@
1
+ # Copyright (c) 2009-2011 VMware, Inc.
2
+
3
+ require 'em-http'
4
+
5
+ module Caldecott
6
+ module Client
7
+ class WebSocketTunnel
8
+ def initialize(logger, url, dst_host, dst_port, auth_token)
9
+ @ws = EM::HttpRequest.new("#{url}/websocket/#{dst_host}/#{dst_port}").get :timeout => 0
10
+ end
11
+
12
+ def onopen(&blk)
13
+ @ws.callback { blk.call }
14
+ end
15
+
16
+ def onclose(&blk)
17
+ @ws.errback { blk.call }
18
+ @ws.disconnect { blk.call }
19
+ end
20
+
21
+ def onreceive(&blk)
22
+ @ws.stream { |data| blk.call(Base64.decode64(data)) }
23
+ end
24
+
25
+ def send_data(data)
26
+ # Um.. as soon as the em websocket object adds a better named
27
+ # method for this, start using it.
28
+ @ws.send(Base64.encode64(data))
29
+ end
30
+
31
+ def close
32
+ @ws.close_connection_after_writing
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,4 @@
1
+ # Copyright (c) 2009-2011 VMware, Inc.
2
+
3
+ require 'caldecott/server/http_tunnel'
4
+ require 'caldecott/server/websocket_tunnel'
@@ -0,0 +1,212 @@
1
+ # Copyright (c) 2009-2011 VMware, Inc.
2
+
3
+ require 'rubygems'
4
+ require 'logger'
5
+ require 'sinatra'
6
+ require 'sinatra/async'
7
+ require 'json'
8
+ require 'uuidtools'
9
+ require 'eventmachine'
10
+ require 'caldecott/tcp_connection.rb'
11
+ require 'caldecott/session_logger.rb'
12
+
13
+ module Caldecott
14
+ module Server
15
+ class Tunnel
16
+ attr_reader :tun_id, :log, :last_active_at
17
+ DEFAULT_MAX_DATA_TO_BUFFER = 1 * 1024 * 1024 # 1MB
18
+
19
+ def initialize(log, tunnels, host, port, max_data_to_buffer = DEFAULT_MAX_DATA_TO_BUFFER)
20
+ @log, @tunnels, @host, @port = log, tunnels, host, port
21
+ @tun_id = UUIDTools::UUID.random_create.to_s
22
+ @data = @data_next = ""
23
+ @seq_out = @seq_in = 0
24
+ @max_data_to_buffer = max_data_to_buffer
25
+ @last_active_at = Time.now
26
+ end
27
+
28
+ def open(resp)
29
+ EM::connect(@host, @port, TcpConnection) do |dst_conn|
30
+ @dst_conn = dst_conn
31
+
32
+ @dst_conn.onopen do
33
+ @log.debug "dst connected"
34
+ @tunnels[@tun_id] = self
35
+ resp.content_type :json
36
+ resp.status 201
37
+ resp.body safe_hash.to_json
38
+ end
39
+
40
+ @dst_conn.onreceive do |data|
41
+ @log.debug "t <- d #{data.length}"
42
+ @data_next << data
43
+ trigger_reader
44
+ @dst_conn.pause if @data_next.length > @max_data_to_buffer
45
+ end
46
+
47
+ @dst_conn.onclose do
48
+ @log.debug "target disconnected"
49
+ @dst_conn = nil
50
+ trigger_reader
51
+ @tunnels.delete(@tun_id) if @data_next.empty?
52
+ end
53
+ end
54
+ @tunnel_created_at = Time.now
55
+ end
56
+
57
+ def delete
58
+ @log.debug "target disconnected"
59
+ if @dst_conn
60
+ @dst_conn.close_connection_after_writing
61
+ else
62
+ @tunnels.delete(@tun_id)
63
+ end
64
+ end
65
+
66
+ def get(resp, seq)
67
+ @last_active_at = Time.now
68
+ resp.halt(400, "invalid sequence #{seq} for server seq #{@seq_out}") unless (seq == @seq_out or seq == @seq_out + 1)
69
+ if seq == @seq_out + 1
70
+ @data, @data_next = @data_next, ""
71
+ @seq_out = seq
72
+ end
73
+
74
+ if @data.empty?
75
+ resp.halt(410, "destination socket closed\n") if @dst_conn.nil?
76
+ @log.debug "get: waiting for data"
77
+ @reader = EM.Callback do
78
+ @data, @data_next = @data_next, ""
79
+ resp.ahalt(410, "destination socket closed\n") if @data.empty?
80
+ @log.debug "get: returning data (async)"
81
+ resp.body @data
82
+ end
83
+ else
84
+ @log.debug "get: returning data (immediate)"
85
+ resp.body @data
86
+ @dst_conn.resume
87
+ end
88
+ end
89
+
90
+ def put(resp, seq)
91
+ @last_active_at = Time.now
92
+ resp.halt(400, "invalid sequence #{seq} for server seq #{@seq_in}") unless (seq == @seq_in or seq == @seq_in + 1)
93
+ if seq == @seq_in
94
+ resp.status 201
95
+ else
96
+ @seq_in = seq
97
+ @log.debug "t -> d #{resp.request.body.length}"
98
+ @dst_conn.send_data(resp.request.body.read)
99
+ resp.status 202
100
+ end
101
+ end
102
+
103
+ def trigger_reader
104
+ return unless @reader
105
+ reader = @reader
106
+ @reader = nil
107
+ reader.call
108
+ end
109
+
110
+ def safe_hash
111
+ {
112
+ :path => "/tunnels/#{@tun_id}",
113
+ :path_in => "/tunnels/#{@tun_id}/in",
114
+ :path_out => "/tunnels/#{@tun_id}/out",
115
+ :dst_host => @host,
116
+ :dst_port => @port,
117
+ :dst_connected => @dst_conn.nil? == false,
118
+ :seq_out => @seq_out,
119
+ :seq_in => @seq_in
120
+ }
121
+ end
122
+
123
+ end
124
+
125
+ class HttpTunnel < Sinatra::Base
126
+ register Sinatra::Async
127
+
128
+ @@tunnels = {}
129
+
130
+ def self.tunnels
131
+ @@tunnels
132
+ end
133
+
134
+ # defaults are 1 hour of inactivity with sweeps every 5 minutes
135
+ def self.start_timer(inactive_timeout = 3600, sweep_interval = 300)
136
+ EventMachine::add_periodic_timer sweep_interval do
137
+ # This is needed because there seems to have a bug on the
138
+ # Connection#set_comm_inactivity_timeout (int overflow )
139
+ # Look at eventmachine/ext/em.cpp 2289
140
+ # It reaps the inactive connections
141
+ #
142
+ # We also can not seem to add our own timer per tunnel instance.
143
+ # When we do, the ruby interpreter freaks out and starts throwing
144
+ #
145
+ # errors like:
146
+ # undefined method `cancel' for 57:Fixnum
147
+ #
148
+ # for code like the following during shutdown:
149
+ # @inactivity_timer.cancel if @inactivity_timer
150
+ # @inactivity_timer.cancel
151
+ # @inactivity_timer = nil
152
+ @@tunnels.each do |id, t|
153
+ t.delete if (Time.now - t.last_active_at) > inactive_timeout
154
+ end
155
+ end
156
+ end
157
+
158
+ def tunnel_from_id(tun_id)
159
+ tun = @@tunnels[tun_id]
160
+ not_found("tunnel #{tun_id} does not exist\n") unless tun
161
+ tun.log.debug "#{request.request_method} #{request.url}"
162
+ tun
163
+ end
164
+
165
+ before do
166
+ @log = SessionLogger.new("server", STDOUT)
167
+ @log.debug "#{request.request_method} #{request.url}"
168
+ if env['HTTP_AUTH_TOKEN'] != settings.auth_token
169
+ @log.debug "AUTH FAILURE #{env.inspect}"
170
+ not_found
171
+ end
172
+ end
173
+
174
+ get '/' do
175
+ return "Caldecott Tunnel (HTTP Transport) #{VERSION}\n"
176
+ end
177
+
178
+ get '/tunnels' do
179
+ content_type :json
180
+ resp = @@tunnels.values.collect { |t| t.safe_hash }
181
+ resp.to_json
182
+ end
183
+
184
+ apost '/tunnels' do
185
+ req = JSON.parse(request.body.read, :symbolize_names => true)
186
+ Tunnel.new(@log, @@tunnels, req[:host], req[:port]).open(self)
187
+ end
188
+
189
+ get '/tunnels/:tun' do |tun_id|
190
+ tun = tunnel_from_id(tun_id)
191
+ tun.safe_hash.to_json
192
+ end
193
+
194
+ delete '/tunnels/:tun' do |tun_id|
195
+ tun = tunnel_from_id(tun_id)
196
+ tun.delete
197
+ end
198
+
199
+ aget '/tunnels/:tun_id/out/:seq' do |tun_id, seq|
200
+ tun = tunnel_from_id(tun_id)
201
+ seq = seq.to_i
202
+ tun.get(self, seq)
203
+ end
204
+
205
+ put '/tunnels/:tun_id/in/:seq' do |tun_id, seq|
206
+ tun = tunnel_from_id(tun_id)
207
+ seq = seq.to_i
208
+ tun.put(self, seq)
209
+ end
210
+ end
211
+ end
212
+ end
@@ -0,0 +1,57 @@
1
+ # Copyright (c) 2009-2011 VMware, Inc.
2
+
3
+ require 'em-websocket'
4
+ require 'base64'
5
+
6
+ module Caldecott
7
+ module Server
8
+ class WebSocketTunnel
9
+
10
+ # quack like sinatra
11
+ def self.run!(opts)
12
+ WebSocketTunnel.new.start(opts[:port])
13
+ end
14
+
15
+ def start(port)
16
+ EM::WebSocket.start(:host => "0.0.0.0", :port => port) do |ws|
17
+ log = SessionLogger::new("server", STDOUT)
18
+ dst_conn = nil
19
+
20
+ ws.onopen do
21
+ log.debug "tunnel connected"
22
+ slash, tunnel, host, port = ws.request['Path'].split('/')
23
+
24
+ EM::connect(host, port, TcpConnection) do |d|
25
+ dst_conn = d
26
+
27
+ dst_conn.onopen do
28
+ log.debug "target connected"
29
+ end
30
+
31
+ dst_conn.onreceive do |data|
32
+ log.debug("t <- d #{data.length}")
33
+ ws.send(Base64.encode64(data))
34
+ end
35
+
36
+ dst_conn.onclose do
37
+ log.debug "target disconnected"
38
+ ws.close_connection
39
+ end
40
+ end
41
+ end
42
+
43
+ ws.onmessage do |msg|
44
+ decoded = Base64.decode64(msg)
45
+ log.debug("t -> d #{decoded.length}")
46
+ dst_conn.send_data(decoded)
47
+ end
48
+
49
+ ws.onclose do
50
+ log.debug "tunnel disconnected"
51
+ dst_conn.close_connection_after_writing if dst_conn
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,39 @@
1
+ # Copyright (c) 2009-2011 VMware, Inc.
2
+
3
+ require 'logger'
4
+
5
+ module Caldecott
6
+ class SessionLogger < Logger
7
+ attr_reader :component, :session
8
+ @@session = 0
9
+
10
+ def initialize(component, *args)
11
+ super(*args)
12
+ @component = component
13
+ @session = @@session += 1
14
+ end
15
+
16
+ def format_message(severity, timestamp, progname, msg)
17
+ "#{@component} [#{@session}] #{msg}\n"
18
+ end
19
+
20
+ def self.severity_from_string(str)
21
+ case str.upcase
22
+ when 'DEBUG'
23
+ Logger::DEBUG
24
+ when 'INFO'
25
+ Logger::INFO
26
+ when 'WARN'
27
+ Logger::WARN
28
+ when 'ERROR'
29
+ Logger::ERROR
30
+ when 'FATAL'
31
+ Logger::FATAL
32
+ when 'UNKNOWN'
33
+ Logger::UNKNOWN
34
+ else
35
+ Logger::ERROR
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,38 @@
1
+ # Copyright (c) 2009-2011 VMware, Inc.
2
+
3
+ require 'eventmachine'
4
+
5
+ module Caldecott
6
+ # wrapper to avoid callback and state passing spaghetti
7
+ class TcpConnection < EventMachine::Connection
8
+ @initialzied = false
9
+
10
+ # callbacks
11
+ def onopen(&blk)
12
+ @initialized ? blk.call : @onopen = blk
13
+ end
14
+
15
+ def onreceive(&blk)
16
+ @onreceive = blk
17
+ end
18
+
19
+ def onclose(&blk)
20
+ @onclose = blk
21
+ end
22
+
23
+ # handle EventMachine::Connection methods
24
+ def post_init
25
+ @initialized = true
26
+ @onopen.call if @onopen
27
+ end
28
+
29
+ def receive_data(data)
30
+ @onreceive.call(data) if @onreceive
31
+ end
32
+
33
+ def unbind
34
+ @onclose.call if @onclose
35
+ end
36
+
37
+ end
38
+ end
@@ -1,5 +1,5 @@
1
1
  # Copyright (c) 2009-2011 VMware, Inc.
2
2
 
3
3
  module Caldecott
4
- VERSION = '0.0.1'
4
+ VERSION = '0.0.3'
5
5
  end
metadata CHANGED
@@ -2,7 +2,7 @@
2
2
  name: caldecott
3
3
  version: !ruby/object:Gem::Version
4
4
  prerelease:
5
- version: 0.0.1
5
+ version: 0.0.3
6
6
  platform: ruby
7
7
  authors:
8
8
  - VMware
@@ -10,11 +10,131 @@ autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
12
 
13
- date: 2011-10-20 00:00:00 -07:00
13
+ date: 2011-11-08 00:00:00 -08:00
14
14
  default_executable:
15
- dependencies: []
16
-
17
- description: TBD
15
+ dependencies:
16
+ - !ruby/object:Gem::Dependency
17
+ name: em-http-request
18
+ prerelease: false
19
+ requirement: &id001 !ruby/object:Gem::Requirement
20
+ none: false
21
+ requirements:
22
+ - - "="
23
+ - !ruby/object:Gem::Version
24
+ version: 0.3.0
25
+ type: :runtime
26
+ version_requirements: *id001
27
+ - !ruby/object:Gem::Dependency
28
+ name: em-websocket
29
+ prerelease: false
30
+ requirement: &id002 !ruby/object:Gem::Requirement
31
+ none: false
32
+ requirements:
33
+ - - "="
34
+ - !ruby/object:Gem::Version
35
+ version: 0.3.1
36
+ type: :runtime
37
+ version_requirements: *id002
38
+ - !ruby/object:Gem::Dependency
39
+ name: async_sinatra
40
+ prerelease: false
41
+ requirement: &id003 !ruby/object:Gem::Requirement
42
+ none: false
43
+ requirements:
44
+ - - "="
45
+ - !ruby/object:Gem::Version
46
+ version: 0.5.0
47
+ type: :runtime
48
+ version_requirements: *id003
49
+ - !ruby/object:Gem::Dependency
50
+ name: addressable
51
+ prerelease: false
52
+ requirement: &id004 !ruby/object:Gem::Requirement
53
+ none: false
54
+ requirements:
55
+ - - "="
56
+ - !ruby/object:Gem::Version
57
+ version: 2.2.6
58
+ type: :runtime
59
+ version_requirements: *id004
60
+ - !ruby/object:Gem::Dependency
61
+ name: json
62
+ prerelease: false
63
+ requirement: &id005 !ruby/object:Gem::Requirement
64
+ none: false
65
+ requirements:
66
+ - - "="
67
+ - !ruby/object:Gem::Version
68
+ version: 1.6.1
69
+ type: :runtime
70
+ version_requirements: *id005
71
+ - !ruby/object:Gem::Dependency
72
+ name: uuidtools
73
+ prerelease: false
74
+ requirement: &id006 !ruby/object:Gem::Requirement
75
+ none: false
76
+ requirements:
77
+ - - "="
78
+ - !ruby/object:Gem::Version
79
+ version: 2.1.2
80
+ type: :runtime
81
+ version_requirements: *id006
82
+ - !ruby/object:Gem::Dependency
83
+ name: rake
84
+ prerelease: false
85
+ requirement: &id007 !ruby/object:Gem::Requirement
86
+ none: false
87
+ requirements:
88
+ - - "="
89
+ - !ruby/object:Gem::Version
90
+ version: 0.9.2
91
+ type: :development
92
+ version_requirements: *id007
93
+ - !ruby/object:Gem::Dependency
94
+ name: rcov
95
+ prerelease: false
96
+ requirement: &id008 !ruby/object:Gem::Requirement
97
+ none: false
98
+ requirements:
99
+ - - "="
100
+ - !ruby/object:Gem::Version
101
+ version: 0.9.10
102
+ type: :development
103
+ version_requirements: *id008
104
+ - !ruby/object:Gem::Dependency
105
+ name: rack-test
106
+ prerelease: false
107
+ requirement: &id009 !ruby/object:Gem::Requirement
108
+ none: false
109
+ requirements:
110
+ - - "="
111
+ - !ruby/object:Gem::Version
112
+ version: 0.6.1
113
+ type: :development
114
+ version_requirements: *id009
115
+ - !ruby/object:Gem::Dependency
116
+ name: rspec
117
+ prerelease: false
118
+ requirement: &id010 !ruby/object:Gem::Requirement
119
+ none: false
120
+ requirements:
121
+ - - "="
122
+ - !ruby/object:Gem::Version
123
+ version: 2.6.0
124
+ type: :development
125
+ version_requirements: *id010
126
+ - !ruby/object:Gem::Dependency
127
+ name: webmock
128
+ prerelease: false
129
+ requirement: &id011 !ruby/object:Gem::Requirement
130
+ none: false
131
+ requirements:
132
+ - - "="
133
+ - !ruby/object:Gem::Version
134
+ version: 1.7.6
135
+ type: :development
136
+ version_requirements: *id011
137
+ description: Caldecott HTTP/Websocket Tunneling Library
18
138
  email: support@vmware.com
19
139
  executables: []
20
140
 
@@ -26,7 +146,18 @@ extra_rdoc_files:
26
146
  files:
27
147
  - LICENSE
28
148
  - README.md
149
+ - lib/caldecott/client/client.rb
150
+ - lib/caldecott/client/http_tunnel.rb
151
+ - lib/caldecott/client/tunnel.rb
152
+ - lib/caldecott/client/websocket_tunnel.rb
153
+ - lib/caldecott/client.rb
154
+ - lib/caldecott/server/http_tunnel.rb
155
+ - lib/caldecott/server/websocket_tunnel.rb
156
+ - lib/caldecott/server.rb
157
+ - lib/caldecott/session_logger.rb
158
+ - lib/caldecott/tcp_connection.rb
29
159
  - lib/caldecott/version.rb
160
+ - lib/caldecott.rb
30
161
  has_rdoc: true
31
162
  homepage: http://vmware.com
32
163
  licenses: []
@@ -54,6 +185,6 @@ rubyforge_project:
54
185
  rubygems_version: 1.6.2
55
186
  signing_key:
56
187
  specification_version: 3
57
- summary: TBD
188
+ summary: Caldecott HTTP/Websocket Tunneling Library
58
189
  test_files: []
59
190