google_anymote 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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/.rvmrc ADDED
@@ -0,0 +1 @@
1
+ rvm use 1.9.3@google_anymote
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in google_anymote.gemspec
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 Richard Hurt
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,66 @@
1
+ # Ruby Gem for the Google TV Pairing and Anymote Protocols
2
+
3
+ This gem implements the Google Anymote Protocol which is used to send events to Google TVs.
4
+ The protocol is based on a client-server model, with communications based on protocol buffers.
5
+ Clients search for a server on the local network. When a client wants to connect to a server
6
+ it has discovered, it does pairing authentication. After a successful pairing, both the client
7
+ and the server have certificates specific to the client app, and can communicate in the future
8
+ without the need to authenticate again. The transport layer uses TSL/SSL to protect messages
9
+ against sniffing.
10
+
11
+ Note: I couldn't have made this without [Steven Le's Python client](https://github.com/stevenle/googletv-anymote).
12
+
13
+ ## Installation
14
+
15
+ Add this line to your application's Gemfile:
16
+
17
+ gem 'google_anymote'
18
+
19
+ And then execute:
20
+
21
+ $ bundle
22
+
23
+ Or install it yourself as:
24
+
25
+ $ gem install google_anymote
26
+
27
+ ## Prerequisites
28
+
29
+ In order to send commands to a Google TV you must have an OpenSSL certificate. This certificate
30
+ can be self-signed and is pretty easy to generate. Just follow these steps
31
+ (taken from http://www.akadia.com/services/ssh_test_certificate.html):
32
+
33
+ $ openssl genrsa -des3 -out server.key 1024
34
+ $ openssl req -new -key server.key -out server.csr
35
+ $ openssl rsa -in server.key -out server.key
36
+ $ openssl x509 -req -days 365 -in server.csr -signkey server.key -out server.crt
37
+ $ cat server.key server.crt > cert.pem
38
+
39
+ Use the `cert.pem` as your certificate when you pair with the Google TV.
40
+
41
+ ## Usage
42
+
43
+ ### Command Line Utilities
44
+
45
+ This gem includes several command line utilities to get you up and running.
46
+
47
+ * <del>discover - searches your network for compatible Google TVs</del> - coming soon
48
+ * pair - helps you pair your computer to a particular Google TV
49
+
50
+ ### As a gem
51
+
52
+ 1. Create a GoogleAnymote::TV object
53
+
54
+ gtv = GoogleAnymote::TV.new(my_cert, hostname)
55
+
56
+ 2. Fling URIs to that TV
57
+
58
+ gtv.fling_uri('http://github.com')
59
+
60
+ ## Contributing
61
+
62
+ 1. Fork it
63
+ 2. Create your feature branch ('git checkout -b my-new-feature')
64
+ 3. Commit your changes ('git commit -am 'Added some feature')
65
+ 4. Push to the branch ('git push origin my-new-feature')
66
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,18 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
3
+
4
+ desc "Generate documentation"
5
+ task :doc => :yard
6
+
7
+ desc "Generated YARD documentation"
8
+ task :yard do
9
+ require "yard"
10
+
11
+ opts = []
12
+ opts.push("--protected")
13
+ opts.push("--no-private")
14
+ opts.push("--private")
15
+ opts.push("--title", "GoogleAnymote")
16
+
17
+ YARD::CLI::Yardoc.run(*opts)
18
+ end
data/bin/pair ADDED
@@ -0,0 +1,29 @@
1
+ #!/usr/bin/env ruby
2
+ require 'google_anymote'
3
+
4
+ unless ARGV.count == 3
5
+ abort "Usage: pair name host_name certificate\n\n"
6
+ end
7
+
8
+ # Collect arguments
9
+ name = ARGV.shift
10
+ host = ARGV.shift
11
+ cert = File.read ARGV.shift
12
+ port = 9551 + 1
13
+
14
+ # Make a connection to the TV
15
+ pair = GoogleAnymote::Pair.new(cert, host, name)
16
+
17
+ # Ask the TV to pair
18
+ pair.start_pairing
19
+
20
+ # Collect pairing code
21
+ print 'Enter the code from the TV: '
22
+ code = gets.chomp
23
+
24
+ # Complete the pairing process
25
+ begin
26
+ pair.complete_pairing(code)
27
+ rescue Exception => e
28
+ abort e
29
+ end
@@ -0,0 +1,21 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/google_anymote/version', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.authors = ["Richard Hurt"]
6
+ gem.email = ["rnhurt@gmail.com"]
7
+ gem.description = %q{Ruby implementation of the Google Anymote Protocol.}
8
+ gem.summary = %q{This library uses the Google Anymote protocol to send events to Google TV servers.}
9
+ gem.homepage = "https://github.com/rnhurt/google_anymote"
10
+
11
+ gem.files = `git ls-files`.split($\)
12
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
13
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
14
+ gem.name = "google_anymote"
15
+ gem.require_paths = ["lib"]
16
+ gem.version = GoogleAnymote::VERSION
17
+
18
+ gem.add_development_dependency 'yard'
19
+ gem.add_development_dependency 'redcarpet'
20
+ gem.add_dependency 'ruby_protobuf', '~> 0.4.11'
21
+ end
@@ -0,0 +1,7 @@
1
+ module GoogleAnymote
2
+ # A general purpose error
3
+ class Error < StandardError; end
4
+
5
+ # Raised when a pairing operation fails for some reason
6
+ class PairingFailed < Error; end
7
+ end
@@ -0,0 +1,150 @@
1
+ require "google_anymote/version"
2
+ require 'socket'
3
+ require 'openssl'
4
+
5
+ ##
6
+ # Module that understands the Google Anymote protocol.
7
+ module GoogleAnymote
8
+ ##
9
+ # Class to send events to a connected GoogleTV
10
+ class Pair
11
+ attr_reader :pair, :cert, :host, :gtv
12
+
13
+ ##
14
+ # Initializes the Pair class
15
+ #
16
+ # @param [Object] cert SSL certificate for this client
17
+ # @param [String] host hostname or IP address of the Google TV
18
+ # @param [String] client_name name of the client your connecting from
19
+ # @param [String] service_name name of the service (generally 'AnyMote')
20
+ # @return an instance of Pair
21
+ def initialize(cert, host, client_name = '', service_name = 'AnyMote')
22
+ @pair = PairingRequest.new
23
+ @cert = cert
24
+ @host = host
25
+ @pair.client_name = client_name
26
+ @pair.service_name = service_name
27
+ end
28
+
29
+
30
+ ##
31
+ # Start the pairing process
32
+ #
33
+ # Once the TV recieves the pairing request it will display a 4 digit number.
34
+ # This number needs to be feed into the next step in the process, complete_pairing().
35
+ #
36
+ def start_pairing
37
+ @gtv = GoogleAnymote::TV.new(@cert, host, 9551 + 1)
38
+
39
+ # Let the TV know that we want to pair with it
40
+ send_message(pair, OuterMessage::MessageType::MESSAGE_TYPE_PAIRING_REQUEST)
41
+
42
+ # Build the options and send them to the TV
43
+ options = Options.new
44
+ encoding = Options::Encoding.new
45
+ encoding.type = Options::Encoding::EncodingType::ENCODING_TYPE_HEXADECIMAL
46
+ encoding.symbol_length = 4
47
+ options.input_encodings << encoding
48
+ options.output_encodings << encoding
49
+ send_message(options, OuterMessage::MessageType::MESSAGE_TYPE_OPTIONS)
50
+
51
+ # Build configuration and send it to the TV
52
+ config = Configuration.new
53
+ encoding = Options::Encoding.new
54
+ encoding.type = Options::Encoding::EncodingType::ENCODING_TYPE_HEXADECIMAL
55
+ config.encoding = encoding
56
+ config.encoding.symbol_length = 4
57
+ config.client_role = Options::RoleType::ROLE_TYPE_INPUT
58
+ outer = send_message(config, OuterMessage::MessageType::MESSAGE_TYPE_CONFIGURATION)
59
+
60
+ raise PairingFailed, outer.status unless OuterMessage::Status::STATUS_OK == outer.status
61
+ end
62
+
63
+ ##
64
+ # Complete the pairing process
65
+ # @param [String] code The code displayed on the Google TV we are trying to pair with.
66
+ #
67
+ def complete_pairing(code)
68
+ # Send secret code to the TV to compete the pairing process
69
+ secret = Secret.new
70
+ secret.secret = encode_hex_secret(code)
71
+ outer = send_message(secret, OuterMessage::MessageType::MESSAGE_TYPE_SECRET)
72
+
73
+ # Clean up
74
+ @gtv.ssl_client.close
75
+
76
+ raise PairingFailed, outer.status unless OuterMessage::Status::STATUS_OK == outer.status
77
+ end
78
+
79
+
80
+
81
+ private
82
+
83
+ ##
84
+ # Format and send the message to the GoogleTV
85
+ #
86
+ # @param [String] msg message to send
87
+ # @param [Object] type type of message to send
88
+ # @return [Object] the OuterMessage response from the TV
89
+ def send_message(msg, type)
90
+ # Build the message and get it's size
91
+ message = wrap_message(msg, type).serialize_to_string
92
+ message_size = [message.length].pack('N')
93
+
94
+ # Write the message to the SSL client and get the response
95
+ @gtv.ssl_client.write(message_size + message)
96
+ data = ""
97
+ @gtv.ssl_client.readpartial(1000,data)
98
+ @gtv.ssl_client.readpartial(1000,data)
99
+
100
+ # Extract the response from the Google TV
101
+ outer = OuterMessage.new
102
+ outer.parse_from_string(data)
103
+
104
+ return outer
105
+ end
106
+
107
+ ##
108
+ # Wrap the message in an OuterMessage
109
+ #
110
+ # @param [String] msg message to send
111
+ # @param [Object] type type of message to send
112
+ # @return [Object] a properly formatted OuterMessage
113
+ def wrap_message(msg, type)
114
+ # Wrap it in an envelope
115
+ outer = OuterMessage.new
116
+ outer.protocol_version = 1
117
+ outer.status = OuterMessage::Status::STATUS_OK
118
+ outer.type = type
119
+ outer.payload = msg.serialize_to_string
120
+
121
+ return outer
122
+ end
123
+
124
+ ##
125
+ # Encode the secret from the TV into an OpenSSL Digest
126
+ #
127
+ # @param [String] secret pairing code from the TV's screen
128
+ # @return [Digest] OpenSSL Digest containing the encoded secret
129
+ def encode_hex_secret secret
130
+ # TODO(stevenle): Something further encodes the secret to a 64-char hex
131
+ # string. For now, use adb logcat to figure out what the expected challenge
132
+ # is. Eventually, make sure the encoding matches the server reference
133
+ # implementation:
134
+ # http://code.google.com/p/google-tv-pairing-protocol/source/browse/src/com/google/polo/pairing/PoloChallengeResponse.java
135
+
136
+ encoded_secret = [secret.to_i(16)].pack("N").unpack("cccc")[2..3].pack("c*")
137
+
138
+ # Per "Polo Implementation Overview", section 6.1, client key material is
139
+ # hashed first, followed by the server key material, followed by the nonce.
140
+ digest = OpenSSL::Digest::Digest.new('sha256')
141
+ digest << @gtv.ssl_client.cert.public_key.n.to_s(2) # client modulus
142
+ digest << @gtv.ssl_client.cert.public_key.e.to_s(2) # client exponent
143
+ digest << @gtv.ssl_client.peer_cert.public_key.n.to_s(2) # server modulus
144
+ digest << @gtv.ssl_client.peer_cert.public_key.e.to_s(2) # server exponent
145
+
146
+ digest << encoded_secret[encoded_secret.size / 2]
147
+ return digest.digest
148
+ end
149
+ end
150
+ end
@@ -0,0 +1,96 @@
1
+ require "google_anymote/version"
2
+ require 'socket'
3
+ require 'openssl'
4
+
5
+ ##
6
+ # Module that understands the Google Anymote protocol.
7
+ module GoogleAnymote
8
+ ##
9
+ # Class to send events to a connected GoogleTV
10
+ class TV
11
+ attr_reader :host, :port, :cert, :cotext, :ssl_client, :remote, :request, :fling
12
+
13
+ ##
14
+ # Initializes the TV class.
15
+ #
16
+ # @param [Object] cert SSL certificate for this client
17
+ # @param [String] host hostname or IP address of the Google TV
18
+ # @param [Number] port port number of the Google TV
19
+ # @return an instance of TV
20
+ def initialize(cert, host, port = 9551)
21
+ @host = host
22
+ @port = port
23
+ @cert = cert
24
+ @remote = RemoteMessage.new
25
+ @request = RequestMessage.new
26
+ @fling = Fling.new
27
+
28
+ # Build the SSL stuff
29
+ @context = OpenSSL::SSL::SSLContext.new
30
+ @context.key = OpenSSL::PKey::RSA.new @cert
31
+ @context.cert = OpenSSL::X509::Certificate.new @cert
32
+
33
+ connect_to_unit
34
+ end
35
+
36
+ ##
37
+ # Connect this object to a Google TV
38
+ def connect_to_unit
39
+ puts "Connecting to '#{@host}..."
40
+ begin
41
+ tcp_client = TCPSocket.new @host, @port
42
+ @ssl_client = OpenSSL::SSL::SSLSocket.new tcp_client, @context
43
+ @ssl_client.connect
44
+ rescue Exception => e
45
+ puts "Could not connect to '#{@host}: #{e}"
46
+ end
47
+ end
48
+
49
+ ##
50
+ # Clean up any sockets or other garbage.
51
+ def finalize()
52
+ @ssl_client.close
53
+ end
54
+
55
+ ##
56
+ # Fling a URI to the Google TV connected to this object
57
+ def fling_uri(uri)
58
+ @fling.uri = uri
59
+ @request.fling_message = fling
60
+ @remote.request_message = @request
61
+ send_message(@remote)
62
+ end
63
+
64
+ private
65
+ ##
66
+ # Send a message to the Google TV
67
+ # @param [String] msg message to send to the TV
68
+ # @return [String] raw data sent back from the TV
69
+ def send_message(msg)
70
+ # Build the message and get it's size
71
+ message = msg.serialize_to_string
72
+ message_size = [message.length].pack('N')
73
+
74
+ # Try to send the message
75
+ try_again = true
76
+ begin
77
+ data = ""
78
+ @ssl_client.write(message_size + message)
79
+ @ssl_client.readpartial(1000,data)
80
+ rescue
81
+ # Sometimes our connection might drop or something, so
82
+ # we'll reconnect to the unit and try to send the message again.
83
+ if try_again
84
+ try_again = false
85
+ connect_to_unit
86
+ retry
87
+ else
88
+ # Looks like we couldn't connect to the unit after all.
89
+ puts "message not sent"
90
+ end
91
+ end
92
+
93
+ return data
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,4 @@
1
+ module GoogleAnymote
2
+ # Gem version
3
+ VERSION = "0.0.1"
4
+ end
@@ -0,0 +1,7 @@
1
+ require "google_anymote/version"
2
+ require "google_anymote/tv"
3
+ require "google_anymote/pair"
4
+ require "google_anymote/exceptions"
5
+
6
+ require "proto/remote.pb"
7
+ require "proto/polo.pb"