imobile 0.0.2 → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG CHANGED
@@ -1,3 +1,5 @@
1
- v0.0.2. CryptoSupport fingerprints computation.
1
+ v0.0.3. Push Notification support.
2
+
3
+ v0.0.2. CryptoSupport finger-prints computation.
2
4
 
3
5
  v0.0.1. Initial release. In-App Purchase receipt validation.
data/Manifest CHANGED
@@ -1,6 +1,6 @@
1
1
  CHANGELOG
2
- imobile.gemspec
3
2
  lib/imobile/crypto_app_fprint.rb
3
+ lib/imobile/push_notification.rb
4
4
  lib/imobile/validate_receipt.rb
5
5
  lib/imobile.rb
6
6
  LICENSE
@@ -8,7 +8,13 @@ Manifest
8
8
  Rakefile
9
9
  README
10
10
  test/crypto_app_fprint_test.rb
11
+ test/push_notification_test.rb
11
12
  test/validate_receipt_test.rb
13
+ testdata/apns_developer.p12
14
+ testdata/apns_production.p12
12
15
  testdata/device_attributes.yml
16
+ testdata/encoded_notification
13
17
  testdata/forged_sandbox_receipt
18
+ testdata/sandbox_device_token
19
+ testdata/sandbox_device_token.bin
14
20
  testdata/valid_sandbox_receipt
data/imobile.gemspec CHANGED
@@ -2,22 +2,22 @@
2
2
 
3
3
  Gem::Specification.new do |s|
4
4
  s.name = %q{imobile}
5
- s.version = "0.0.2"
5
+ s.version = "0.0.3"
6
6
 
7
7
  s.required_rubygems_version = Gem::Requirement.new(">= 1.2") if s.respond_to? :required_rubygems_version=
8
8
  s.authors = ["Victor Costan"]
9
- s.date = %q{2009-07-24}
9
+ s.date = %q{2009-07-25}
10
10
  s.description = %q{Library for servers backing iPhone applications.}
11
11
  s.email = %q{victor@zergling.net}
12
- s.extra_rdoc_files = ["CHANGELOG", "lib/imobile/crypto_app_fprint.rb", "lib/imobile/validate_receipt.rb", "lib/imobile.rb", "LICENSE", "README"]
13
- s.files = ["CHANGELOG", "imobile.gemspec", "lib/imobile/crypto_app_fprint.rb", "lib/imobile/validate_receipt.rb", "lib/imobile.rb", "LICENSE", "Manifest", "Rakefile", "README", "test/crypto_app_fprint_test.rb", "test/validate_receipt_test.rb", "testdata/device_attributes.yml", "testdata/forged_sandbox_receipt", "testdata/valid_sandbox_receipt"]
12
+ s.extra_rdoc_files = ["CHANGELOG", "lib/imobile/crypto_app_fprint.rb", "lib/imobile/push_notification.rb", "lib/imobile/validate_receipt.rb", "lib/imobile.rb", "LICENSE", "README"]
13
+ s.files = ["CHANGELOG", "lib/imobile/crypto_app_fprint.rb", "lib/imobile/push_notification.rb", "lib/imobile/validate_receipt.rb", "lib/imobile.rb", "LICENSE", "Manifest", "Rakefile", "README", "test/crypto_app_fprint_test.rb", "test/push_notification_test.rb", "test/validate_receipt_test.rb", "testdata/apns_developer.p12", "testdata/apns_production.p12", "testdata/device_attributes.yml", "testdata/encoded_notification", "testdata/forged_sandbox_receipt", "testdata/sandbox_device_token", "testdata/sandbox_device_token.bin", "testdata/valid_sandbox_receipt", "imobile.gemspec"]
14
14
  s.homepage = %q{http://github.com/costan/imobile}
15
15
  s.rdoc_options = ["--line-numbers", "--inline-source", "--title", "Imobile", "--main", "README"]
16
16
  s.require_paths = ["lib"]
17
17
  s.rubyforge_project = %q{zerglings}
18
18
  s.rubygems_version = %q{1.3.5}
19
19
  s.summary = %q{Library for servers backing iPhone applications.}
20
- s.test_files = ["test/crypto_app_fprint_test.rb", "test/validate_receipt_test.rb"]
20
+ s.test_files = ["test/crypto_app_fprint_test.rb", "test/push_notification_test.rb", "test/validate_receipt_test.rb"]
21
21
 
22
22
  if s.respond_to? :specification_version then
23
23
  current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
@@ -115,6 +115,6 @@ module CryptoSupportAppFprint
115
115
  manifest_data = File.read binary_path
116
116
  app_fprint_from_raw_data device_attributes, manifest_data
117
117
  end
118
- end
118
+ end # module CryptoSupportAppFprint
119
119
 
120
120
  end # namespace Imobile
@@ -0,0 +1,261 @@
1
+ # Apple Push Notifications support.
2
+ #
3
+ # Author:: Victor Costan
4
+ # Copyright:: Copyright (C) 2009 Zergling.Net
5
+ # License:: MIT
6
+
7
+ require 'net/http'
8
+ require 'net/https'
9
+
10
+ require 'socket'
11
+ require 'openssl'
12
+
13
+ require 'rubygems'
14
+ require 'json'
15
+
16
+
17
+ # :nodoc: namespace
18
+ module Imobile
19
+
20
+ # Sends a push notification to an iMobile device.
21
+ #
22
+ # Args:
23
+ # notification:: ruby Hash indicating the desired notification; the hash
24
+ # should have an extra key named :device_token, containing
25
+ # the binary-encoded (not hexadecimally-encoded) iMobile device
26
+ # token, as provided by the UIApplicationDelegate method
27
+ # application:didRegisterForRemoteNotificationsWithDeviceToken:
28
+ # path_or_certificate:: the certificate required to talk to APNs; this can be
29
+ # a path to a .p12 file, a string with the contens of
30
+ # the .p12 file, or a previously-read certificate
31
+ #
32
+ # Raises a RuntimeException if Apple's Push Notification service doesn't behave.
33
+ def self.push_notification(notification, path_or_certificate)
34
+ PushNotifications.push_notification notification, path_or_certificate
35
+ end
36
+
37
+ # Bulk-transmission of push notifications to Apple's service.
38
+ #
39
+ # Args:
40
+ # path_or_certificate:: see push_notification
41
+ # notifications:: an array of notification hashes; see push_notification
42
+ #
43
+ # If the method receives a block, it yields to its block indefinitely. Each
44
+ # time, the block should +next+ a notification or array of notifications to be
45
+ # pushed. The block should +break+ when it's done.
46
+ def self.push_notifications(path_or_certificate, notifications = [], &block)
47
+ PushNotifications.push_notifications path_or_certificate, notifications,
48
+ &block
49
+ end
50
+
51
+ # Reads the available feedback from Apple's Push Notification service.
52
+ #
53
+ # Args:
54
+ # certificate_or_path:: see Imobile.push_notification
55
+ #
56
+ # The currently provided feedback is the tokens for the devices which rejected
57
+ # notifications. Each piece of feedback is a hash with the following keys:
58
+ # :device_token:: the device's token, in binary (not hexadecimal) format
59
+ # :time:: the last time when the device rejected notifications; according to
60
+ # Apple, the rejection can be discarded if the device sent a
61
+ # token after this time
62
+ #
63
+ # The method reads all the feedback available from the Push Notification
64
+ # service. If a block is given, each piece of feedback is yielded to the
65
+ # method's block, and the method returns nil. If no block is given, the
66
+ # method returns an array containing all pieces of feedback.
67
+ def self.push_feedback(path_or_certificate, &block)
68
+ PushNotifications.push_feedback path_or_certificate, &block
69
+ end
70
+
71
+ # Checks if a notification is valid for Apple's Push Notification service.
72
+ #
73
+ # Currently, notifications are valid if their JSON encodings don't exceed 256
74
+ # bytes.
75
+ def self.valid_notification?(notification)
76
+ PushNotifications.encode_notification(notification) ? true : false
77
+ end
78
+
79
+ # Packs a hexadecimal iMobile device token into binary form.
80
+ def self.pack_hex_device_token(device_token)
81
+ [device_token.gsub(/\s/, '')].pack('H*')
82
+ end
83
+
84
+ # Implementation details for push_notification.
85
+ module PushNotifications
86
+ # Reads an APNs certificate from a string or a file.
87
+ def self.read_certificate(certificate_blob_or_path)
88
+ unless certificate_blob_or_path.respond_to? :to_str
89
+ return certificate_blob_or_path
90
+ end
91
+ begin
92
+ decode_push_certificate File.read(certificate_blob_or_path)
93
+ rescue
94
+ decode_push_certificate certificate_blob_or_path
95
+ end
96
+ end
97
+
98
+ # Decodes an APNs certificate.
99
+ def self.decode_push_certificate(certificate_blob)
100
+ pkcs12 = OpenSSL::PKCS12.new certificate_blob
101
+
102
+ certificate = pkcs12.certificate
103
+ key = pkcs12.key
104
+ case certificate.subject.to_s
105
+ when /Apple Development Push/
106
+ server_type = :sandbox
107
+ when /Apple Production Push/
108
+ server_type = :production
109
+ else
110
+ raise "Invalid push certificate - #{certificate.inspect}"
111
+ end
112
+
113
+ { :certificate => certificate, :key => key, :server_type => server_type }
114
+ end
115
+
116
+ # Encodes a push notification in a binary string for APNs consumption.
117
+ #
118
+ # Returns a string suitable for transmission over an APNs, or nil if the
119
+ # notification is invalid (i.e. the json encoding exceeds 256 bytes).
120
+ def self.encode_notification(notification)
121
+ device_token = notification[:device_token] || ''
122
+ notification = notification.dup
123
+ notification.delete :device_token
124
+ json_notification = notification.to_json
125
+ return nil if json_notification.length > 256
126
+
127
+ ["\0", [device_token.length].pack('n'), device_token,
128
+ [json_notification.length].pack('n'), json_notification].join
129
+ end
130
+
131
+ # Creates a socket to an Apple Push Notification Server.
132
+ #
133
+ # Args:
134
+ # push_certificate:: the APNs client certificate data, obtained by a call to
135
+ # read_certificate
136
+ # service:: either :feedback or :push
137
+ #
138
+ # The returned socket is connected and ready for use.
139
+ def self.apns_socket(push_certificate, service = :push)
140
+ context = OpenSSL::SSL::SSLContext.new
141
+ context.cert = push_certificate[:certificate]
142
+ context.key = push_certificate[:key]
143
+
144
+ server_type = push_certificate[:server_type]
145
+ raw_socket = TCPSocket.new apns_host(server_type, service),
146
+ apns_port(server_type, service)
147
+
148
+ socket = OpenSSL::SSL::SSLSocket.new raw_socket, context
149
+ # Magic for closing the raw socket when the SSL socket is closed.
150
+ (class <<socket; self; end).send :define_method, :close do
151
+ super
152
+ raw_socket.close
153
+ end
154
+ socket.sync = true
155
+ socket.connect
156
+ end
157
+
158
+ # The host name for an Apple Push Notification Server.
159
+ #
160
+ # Args:
161
+ # server_type:: either :production or :sandbox
162
+ # service:: either :push or :feedback
163
+ def self.apns_host(server_type, service = :push)
164
+ {
165
+ :feedback => {
166
+ :sandbox => 'feedback.sandbox.push.apple.com',
167
+ :production => 'feedback.push.apple.com'
168
+ },
169
+ :push => {
170
+ :sandbox => 'gateway.sandbox.push.apple.com',
171
+ :production => 'gateway.push.apple.com'
172
+ }
173
+ }[service][server_type]
174
+ end
175
+
176
+ # The port for an Apple Push Notification Server.
177
+ #
178
+ # Args:
179
+ # server_type:: either :production or :sandbox
180
+ # service:: either :push or :feedback
181
+ def self.apns_port(server_type, service = :push)
182
+ {
183
+ :feedback => 2196,
184
+ :push => 2195
185
+ }[service]
186
+ end
187
+
188
+ # Real implementation of Imobile.push_notifications
189
+ def self.push_notifications(certificate_or_path, notifications)
190
+ socket = apns_socket read_certificate(certificate_or_path), :push
191
+ notifications = [notifications] if notifications.kind_of? Hash
192
+ notifications.each { |n| socket.write encode_notification(n) }
193
+ if Kernel.block_given?
194
+ loop do
195
+ notifications = yield
196
+ notifications = [notifications] if notifications.kind_of? Hash
197
+ notifications.each { |n| socket.write encode_notification(n) }
198
+ end
199
+ end
200
+ socket.close
201
+ end
202
+
203
+ # Real implementation of Imobile.push_notification
204
+ def self.push_notification(notification, certificate_or_path)
205
+ push_notifications certificate_or_path, [notification]
206
+ end
207
+
208
+ # Real implementation of Imobile.push_feedback
209
+ def self.push_feedback(certificate_or_path, &block)
210
+ if Kernel.block_given?
211
+ raw_push_feedback certificate_or_path, &block
212
+ nil
213
+ else
214
+ feedback = []
215
+ raw_push_feedback certificate_or_path do |feedback_item|
216
+ feedback << feedback_item
217
+ end
218
+ feedback
219
+ end
220
+ end
221
+
222
+ # Reads the available feedback from Apple's Push Notification service.
223
+ #
224
+ # Args:
225
+ # certificate_or_path:: see Imobile.push_notification
226
+ #
227
+ # The currently provided feedback is the tokens for the devices which rejected
228
+ # notifications. Each piece of feedback is a hash with the following keys:
229
+ # :device_token:: the device's token, in binary (not hexadecimal) format
230
+ # :time:: the last time when the device rejected notifications; according to
231
+ # Apple, the rejection can be discarded if the device sent a
232
+ # token after this time
233
+ #
234
+ # The method reads all the feedback available from the Push Notification
235
+ # service, and yields each piece of feedback to the method's block.
236
+ def self.raw_push_feedback(certificate_or_path)
237
+ socket = apns_socket read_certificate(certificate_or_path), :feedback
238
+ loop do
239
+ break unless header = fixed_socket_read(socket, 6)
240
+ time = Time.at header[0, 4].unpack('N').first
241
+ device_token = fixed_socket_read(socket, header[4, 2].unpack('n').first)
242
+ break unless device_token
243
+ feedback_item = { :device_token => device_token, :time => time }
244
+ yield feedback_item
245
+ end
246
+ socket.close
247
+ end
248
+
249
+ # Reads a fixed number of bytes from a socket.
250
+ def self.fixed_socket_read(socket, num_bytes)
251
+ data = ''
252
+ while data.length < num_bytes
253
+ new_data = socket.read(num_bytes - data.length)
254
+ return nil if new_data.nil? or new_data.empty? # Socket closed.
255
+ data += new_data
256
+ end
257
+ data
258
+ end
259
+ end # module PushNotifications
260
+
261
+ end # namespace Imobile
@@ -99,6 +99,6 @@ module AppStoreReceiptValidation
99
99
 
100
100
  process_response issue_request(request(receipt_blob, uri), uri)
101
101
  end
102
- end
102
+ end # module AppStoreReceiptValidation
103
103
 
104
104
  end # namespace Imobile
data/lib/imobile.rb CHANGED
@@ -5,4 +5,5 @@
5
5
  # License:: MIT
6
6
 
7
7
  require 'imobile/crypto_app_fprint.rb'
8
+ require 'imobile/push_notification.rb'
8
9
  require 'imobile/validate_receipt.rb'
@@ -0,0 +1,177 @@
1
+ # Author:: Victor Costan
2
+ # Copyright:: Copyright (C) 2009 Zergling.Net
3
+ # License:: MIT
4
+
5
+ require 'imobile'
6
+
7
+ require 'time'
8
+ require 'test/unit'
9
+
10
+ require 'rubygems'
11
+ require 'flexmock/test_unit'
12
+
13
+
14
+ class PushNotificationTest < Test::Unit::TestCase
15
+ def setup
16
+ testdata_path = File.join(File.dirname(__FILE__), '..', 'testdata')
17
+ @dev_cert_path = File.join(testdata_path, 'apns_developer.p12')
18
+ @prod_cert_path = File.join(testdata_path, 'apns_production.p12')
19
+
20
+ @hex_dev_token = File.read File.join(testdata_path, 'sandbox_device_token')
21
+ @dev_token = File.read File.join(testdata_path, 'sandbox_device_token.bin')
22
+
23
+ @notification = {:aps => {:alert => 'imobile test notification'}}
24
+ @encoded_notification = File.read File.join(testdata_path,
25
+ 'encoded_notification')
26
+ end
27
+
28
+ def test_read_push_certificate
29
+ cert_data = Imobile::PushNotifications.read_certificate @dev_cert_path
30
+ assert cert_data, "Dev certificate didn't load"
31
+ assert_equal :sandbox, cert_data[:server_type],
32
+ "Dev certificate mistaken for prod"
33
+ assert cert_data[:key].kind_of?(OpenSSL::PKey::PKey),
34
+ "Dev certificate does not contain a key"
35
+ assert(/Q686F7Z6YU/ =~ cert_data[:certificate].subject.to_s,
36
+ "Wrong data in dev certificate #{cert_data[:certificate].inspect}")
37
+
38
+ prod_blob = File.read @prod_cert_path
39
+ cert_data = Imobile::PushNotifications.read_certificate prod_blob
40
+ assert cert_data, "Prod certificate didn't load"
41
+ assert_equal :production, cert_data[:server_type],
42
+ "Prod certificate mistaken for dev"
43
+ assert cert_data[:key].kind_of?(OpenSSL::PKey::PKey),
44
+ "Prod certificate does not contain a key"
45
+ assert(/Q686F7Z6YU/ =~ cert_data[:certificate].subject.to_s,
46
+ "Wrong data in prod certificate #{cert_data[:certificate].inspect}")
47
+ end
48
+
49
+ def test_pack_device_token
50
+ assert_equal @dev_token, Imobile.pack_hex_device_token(@hex_dev_token)
51
+ end
52
+
53
+ def test_encode_notification
54
+ notification = @notification.merge :device_token => @dev_token
55
+ encoded = Imobile::PushNotifications.encode_notification notification
56
+
57
+ assert_equal @encoded_notification, encoded
58
+ end
59
+
60
+ def test_valid_notification
61
+ assert Imobile.valid_notification?(@notification),
62
+ 'Failed on easy valid notification'
63
+ notification = @notification.merge :device_token => '1' * 512
64
+ assert Imobile.valid_notification?(notification),
65
+ 'Failed on notification with large device token'
66
+ notification[:aps][:alert] = '1' * 512
67
+ assert !Imobile.valid_notification?(notification),
68
+ 'Passed notification with large alert'
69
+ end
70
+
71
+ def test_apns_host_port
72
+ [
73
+ [:push, @dev_cert_path, 'gateway.sandbox.push.apple.com', 2195],
74
+ [:push, @prod_cert_path, 'gateway.push.apple.com', 2195],
75
+ [:feedback, @dev_cert_path, 'feedback.sandbox.push.apple.com', 2196],
76
+ [:feedback, @prod_cert_path, 'feedback.push.apple.com', 2196]
77
+ ].each do |service, cert_path, gold_host, gold_port|
78
+ cert = Imobile::PushNotifications.read_certificate cert_path
79
+ server_type = cert[:server_type]
80
+ assert_equal gold_host,
81
+ Imobile::PushNotifications.apns_host(server_type, service)
82
+ assert_equal gold_port,
83
+ Imobile::PushNotifications.apns_port(server_type, service)
84
+ end
85
+ end
86
+
87
+ def test_smoke_push
88
+ notification = @notification.merge :device_token => @dev_token
89
+ Imobile.push_notification notification, @dev_cert_path
90
+ end
91
+
92
+ def test_feedback_decoding
93
+ time1 = Time.at((Time.now - 3600).to_i)
94
+ time2 = Time.at((Time.now - 3600 * 72).to_i)
95
+ token1 = @dev_token
96
+ token2 = @dev_token.reverse
97
+ golden_feedback = [
98
+ {:device_token => token1, :time => time1},
99
+ {:device_token => token2, :time => time2}
100
+ ]
101
+
102
+ reads = [
103
+ [6, [time1.to_i].pack('N')],
104
+ [2, [token1.length].pack('n')],
105
+ [token1.length, token1[0, 10]],
106
+ [token1.length - 10, token1[10, token1.length - 10]],
107
+ [6, [time2.to_i].pack('N')[0, 3]],
108
+ [3, [time2.to_i].pack('N')[3, 1] + [token2.length].pack('n')],
109
+ [token2.length, token2],
110
+ [6, '']
111
+ ]
112
+
113
+ dev_cert = Imobile::PushNotifications.read_certificate @dev_cert_path
114
+ prod_cert = Imobile::PushNotifications.read_certificate @prod_cert_path
115
+ socket1, socket2 = MockSocket.new(reads), MockSocket.new(reads)
116
+ flexmock(Imobile::PushNotifications).should_receive(:apns_socket).
117
+ with(dev_cert, :feedback).and_return(socket1).once
118
+ flexmock(Imobile::PushNotifications).should_receive(:apns_socket).
119
+ with(prod_cert, :feedback).and_return(socket2)
120
+
121
+ # Test no-block codepath.
122
+ assert_equal golden_feedback, Imobile.push_feedback(dev_cert),
123
+ "No-block codepath failed."
124
+ assert socket1.closed, "No-block codepath didn't close socket"
125
+
126
+ # Test block codepath
127
+ output_feedback = []
128
+ Imobile.push_feedback prod_cert do |feedback_item|
129
+ output_feedback << feedback_item
130
+ end
131
+ assert_equal golden_feedback, output_feedback, "Block codepath failed."
132
+ assert socket2.closed, "Vlock codepath didn't close socket"
133
+ end
134
+
135
+ def test_smoke_feedback
136
+ Imobile.push_feedback @dev_cert_path do |feedback_item|
137
+ check_feedback_item feedback_item
138
+ end
139
+
140
+ feedback_items = Imobile.push_feedback @prod_cert_path
141
+ feedback_items.each { |feedback_item| check_feedback_item feedback_item }
142
+ end
143
+
144
+ # Verifies that a piece of feedback looks valid.
145
+ def check_feedback_item(feedback)
146
+ assert feedback[:device_token],
147
+ "A feedback item does not contain a device token"
148
+ assert feedback[:time],
149
+ "A feedback item does not contain a timestamp"
150
+ end
151
+ end
152
+
153
+
154
+ class MockSocket
155
+ def initialize(expected_reads)
156
+ @expected_reads = expected_reads.dup
157
+ @closed = false
158
+ end
159
+ attr_reader :closed
160
+
161
+ def read(bytes)
162
+ expected_read = @expected_reads.shift
163
+ unless expected_read
164
+ raise "Unexpected read of #{bytes.inspect} (done sending data)"
165
+ end
166
+
167
+ unless bytes == expected_read[0]
168
+ raise "Expected read size #{expected_read[0]}, got #{bytes.inspect}"
169
+ end
170
+ expected_read[1]
171
+ end
172
+
173
+ def close
174
+ raise "Closed prematurely" unless @expected_reads.empty?
175
+ @closed = true
176
+ end
177
+ end
Binary file
Binary file
Binary file
@@ -0,0 +1 @@
1
+ af9788cb 35d74ed3 4ed94bfc 1fbd4ad6 cdc7b460 3c47a04a 98bf7162 ad1305c6
@@ -0,0 +1 @@
1
+ ����5�N�N�K��J��Ǵ`<G�J��qb��
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: imobile
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2
4
+ version: 0.0.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Victor Costan
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-07-24 00:00:00 -04:00
12
+ date: 2009-07-25 00:00:00 -04:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
@@ -51,14 +51,15 @@ extensions: []
51
51
  extra_rdoc_files:
52
52
  - CHANGELOG
53
53
  - lib/imobile/crypto_app_fprint.rb
54
+ - lib/imobile/push_notification.rb
54
55
  - lib/imobile/validate_receipt.rb
55
56
  - lib/imobile.rb
56
57
  - LICENSE
57
58
  - README
58
59
  files:
59
60
  - CHANGELOG
60
- - imobile.gemspec
61
61
  - lib/imobile/crypto_app_fprint.rb
62
+ - lib/imobile/push_notification.rb
62
63
  - lib/imobile/validate_receipt.rb
63
64
  - lib/imobile.rb
64
65
  - LICENSE
@@ -66,10 +67,17 @@ files:
66
67
  - Rakefile
67
68
  - README
68
69
  - test/crypto_app_fprint_test.rb
70
+ - test/push_notification_test.rb
69
71
  - test/validate_receipt_test.rb
72
+ - testdata/apns_developer.p12
73
+ - testdata/apns_production.p12
70
74
  - testdata/device_attributes.yml
75
+ - testdata/encoded_notification
71
76
  - testdata/forged_sandbox_receipt
77
+ - testdata/sandbox_device_token
78
+ - testdata/sandbox_device_token.bin
72
79
  - testdata/valid_sandbox_receipt
80
+ - imobile.gemspec
73
81
  has_rdoc: true
74
82
  homepage: http://github.com/costan/imobile
75
83
  licenses: []
@@ -105,4 +113,5 @@ specification_version: 3
105
113
  summary: Library for servers backing iPhone applications.
106
114
  test_files:
107
115
  - test/crypto_app_fprint_test.rb
116
+ - test/push_notification_test.rb
108
117
  - test/validate_receipt_test.rb