OLD | NEW |
| (Empty) |
1 // Copyright 2015 The Chromium Authors. All rights reserved. | |
2 // Use of this source code is governed by a BSD-style license that can be | |
3 // found in the LICENSE file. | |
4 | |
5 #include "chrome/browser/extensions/api/audio_modem/audio_modem_api.h" | |
6 | |
7 #include <stdint.h> | |
8 | |
9 #include <map> | |
10 #include <memory> | |
11 #include <string> | |
12 #include <utility> | |
13 #include <vector> | |
14 | |
15 #include "base/base64.h" | |
16 #include "base/bind.h" | |
17 #include "base/guid.h" | |
18 #include "base/lazy_instance.h" | |
19 #include "base/logging.h" | |
20 #include "base/memory/ptr_util.h" | |
21 #include "base/strings/string_util.h" | |
22 #include "base/timer/timer.h" | |
23 #include "chrome/browser/copresence/chrome_whispernet_client.h" | |
24 #include "chrome/common/extensions/api/audio_modem.h" | |
25 #include "extensions/browser/event_router.h" | |
26 | |
27 // TODO(ckehoe): Implement transmit fail checking. | |
28 | |
29 namespace extensions { | |
30 | |
31 using api::audio_modem::AUDIOBAND_AUDIBLE; | |
32 using api::audio_modem::AUDIOBAND_INAUDIBLE; | |
33 using api::audio_modem::Audioband; | |
34 using api::audio_modem::STATUS_CODERERROR; | |
35 using api::audio_modem::STATUS_INUSE; | |
36 using api::audio_modem::STATUS_INVALIDREQUEST; | |
37 using api::audio_modem::STATUS_SUCCESS; | |
38 using api::audio_modem::ReceivedToken; | |
39 using api::audio_modem::RequestParams; | |
40 using api::audio_modem::Status; | |
41 | |
42 namespace Transmit = api::audio_modem::Transmit; | |
43 namespace StopTransmit = api::audio_modem::StopTransmit; | |
44 namespace Receive = api::audio_modem::Receive; | |
45 namespace StopReceive = api::audio_modem::StopReceive; | |
46 namespace OnReceived = api::audio_modem::OnReceived; | |
47 | |
48 using audio_modem::AUDIBLE; | |
49 using audio_modem::AudioToken; | |
50 using audio_modem::AudioType; | |
51 using audio_modem::INAUDIBLE; | |
52 using audio_modem::TokenParameters; | |
53 | |
54 namespace { | |
55 | |
56 const char kInitFailedError[] = "The audio modem is not available. " | |
57 "Failed to initialize the token encoder/decoder."; | |
58 const char kInvalidTokenLengthError[] = | |
59 "The token length must be greater than zero."; | |
60 const char kIncorrectTokenLengthError[] = | |
61 "The token provided did not match the declared token length."; | |
62 const char kInvalidTimeoutError[] = | |
63 "Transmit and receive timeouts must be greater than zero."; | |
64 | |
65 const int kMaxTransmitTimeout = 10 * 60 * 1000; // 10 minutes | |
66 const int kMaxReceiveTimeout = 60 * 60 * 1000; // 1 hour | |
67 | |
68 base::LazyInstance<BrowserContextKeyedAPIFactory<AudioModemAPI>> | |
69 g_factory = LAZY_INSTANCE_INITIALIZER; | |
70 | |
71 AudioType AudioTypeForBand(Audioband band) { | |
72 switch (band) { | |
73 case AUDIOBAND_AUDIBLE: | |
74 return AUDIBLE; | |
75 case AUDIOBAND_INAUDIBLE: | |
76 return INAUDIBLE; | |
77 default: | |
78 NOTREACHED(); | |
79 return audio_modem::AUDIO_TYPE_UNKNOWN; | |
80 } | |
81 } | |
82 | |
83 TokenParameters TokenParamsForEncoding( | |
84 const api::audio_modem::TokenEncoding& encoding) { | |
85 return TokenParameters(encoding.token_length, | |
86 encoding.crc ? *encoding.crc : false, | |
87 encoding.parity ? *encoding.parity : true); | |
88 } | |
89 | |
90 const std::string DecodeBase64Token(std::string encoded_token) { | |
91 // Make sure the token is padded correctly. | |
92 while (encoded_token.size() % 4 > 0) | |
93 encoded_token += "="; | |
94 | |
95 // Decode and return the token. | |
96 std::string raw_token; | |
97 bool decode_success = base::Base64Decode(encoded_token, &raw_token); | |
98 DCHECK(decode_success); | |
99 return raw_token; | |
100 } | |
101 | |
102 } // namespace | |
103 | |
104 | |
105 // Public functions. | |
106 | |
107 AudioModemAPI::AudioModemAPI(content::BrowserContext* context) | |
108 : AudioModemAPI(context, | |
109 base::WrapUnique(new ChromeWhispernetClient(context)), | |
110 audio_modem::Modem::Create()) {} | |
111 | |
112 AudioModemAPI::AudioModemAPI( | |
113 content::BrowserContext* context, | |
114 std::unique_ptr<audio_modem::WhispernetClient> whispernet_client, | |
115 std::unique_ptr<audio_modem::Modem> modem) | |
116 : browser_context_(context), | |
117 whispernet_client_(std::move(whispernet_client)), | |
118 modem_(std::move(modem)), | |
119 init_failed_(false) { | |
120 // We own these objects, so these callbacks will not outlive us. | |
121 whispernet_client_->Initialize( | |
122 base::Bind(&AudioModemAPI::WhispernetInitComplete, | |
123 base::Unretained(this))); | |
124 modem_->Initialize(whispernet_client_.get(), | |
125 base::Bind(&AudioModemAPI::TokensReceived, | |
126 base::Unretained(this))); | |
127 } | |
128 | |
129 AudioModemAPI::~AudioModemAPI() { | |
130 for (const auto& timer_entry : receive_timers_[0]) | |
131 delete timer_entry.second; | |
132 for (const auto& timer_entry : receive_timers_[1]) | |
133 delete timer_entry.second; | |
134 } | |
135 | |
136 Status AudioModemAPI::StartTransmit(const std::string& app_id, | |
137 const RequestParams& params, | |
138 const std::string& token) { | |
139 AudioType audio_type = AudioTypeForBand(params.band); | |
140 if (transmitters_[audio_type].empty()) | |
141 transmitters_[audio_type] = app_id; | |
142 else if (transmitters_[audio_type] != app_id) | |
143 return STATUS_INUSE; | |
144 | |
145 DVLOG(3) << "Starting transmit for app " << app_id; | |
146 | |
147 std::string encoded_token; | |
148 base::Base64Encode(token, &encoded_token); | |
149 base::RemoveChars(encoded_token, "=", &encoded_token); | |
150 | |
151 modem_->SetTokenParams(audio_type, TokenParamsForEncoding(params.encoding)); | |
152 modem_->SetToken(audio_type, encoded_token); | |
153 modem_->StartPlaying(audio_type); | |
154 | |
155 transmit_timers_[audio_type].Start( | |
156 FROM_HERE, | |
157 base::TimeDelta::FromMilliseconds(params.timeout_millis), | |
158 base::Bind(base::IgnoreResult(&AudioModemAPI::StopTransmit), | |
159 base::Unretained(this), | |
160 app_id, | |
161 audio_type)); | |
162 return STATUS_SUCCESS; | |
163 } | |
164 | |
165 Status AudioModemAPI::StopTransmit(const std::string& app_id, | |
166 AudioType audio_type) { | |
167 if (transmitters_[audio_type] != app_id) | |
168 return STATUS_INVALIDREQUEST; | |
169 | |
170 DVLOG(3) << "Stopping transmit for app " << app_id; | |
171 transmitters_[audio_type].clear(); | |
172 modem_->StopPlaying(audio_type); | |
173 return STATUS_SUCCESS; | |
174 } | |
175 | |
176 void AudioModemAPI::StartReceive(const std::string& app_id, | |
177 const RequestParams& params) { | |
178 DVLOG(3) << "Starting receive for app " << app_id; | |
179 | |
180 AudioType audio_type = AudioTypeForBand(params.band); | |
181 modem_->SetTokenParams(audio_type, TokenParamsForEncoding(params.encoding)); | |
182 modem_->StartRecording(audio_type); | |
183 | |
184 if (receive_timers_[audio_type].count(app_id) == 0) | |
185 receive_timers_[audio_type][app_id] = new base::OneShotTimer; | |
186 DCHECK(receive_timers_[audio_type][app_id]); | |
187 receive_timers_[audio_type][app_id]->Start( | |
188 FROM_HERE, | |
189 base::TimeDelta::FromMilliseconds(params.timeout_millis), | |
190 base::Bind(base::IgnoreResult(&AudioModemAPI::StopReceive), | |
191 base::Unretained(this), | |
192 app_id, | |
193 audio_type)); | |
194 } | |
195 | |
196 Status AudioModemAPI::StopReceive(const std::string& app_id, | |
197 AudioType audio_type) { | |
198 if (receive_timers_[audio_type].count(app_id) == 0) | |
199 return STATUS_INVALIDREQUEST; | |
200 | |
201 DCHECK(receive_timers_[audio_type][app_id]); | |
202 delete receive_timers_[audio_type][app_id]; | |
203 receive_timers_[audio_type].erase(app_id); | |
204 | |
205 DVLOG(3) << "Stopping receive for app " << app_id; | |
206 if (receive_timers_[audio_type].empty()) | |
207 modem_->StopRecording(audio_type); | |
208 return STATUS_SUCCESS; | |
209 } | |
210 | |
211 // static | |
212 BrowserContextKeyedAPIFactory<AudioModemAPI>* | |
213 AudioModemAPI::GetFactoryInstance() { | |
214 return g_factory.Pointer(); | |
215 } | |
216 | |
217 | |
218 // Private functions. | |
219 | |
220 void AudioModemAPI::WhispernetInitComplete(bool success) { | |
221 if (success) { | |
222 VLOG(2) << "Whispernet initialized successfully."; | |
223 } else { | |
224 LOG(ERROR) << "Failed to initialize Whispernet!"; | |
225 init_failed_ = true; | |
226 } | |
227 } | |
228 | |
229 void AudioModemAPI::TokensReceived(const std::vector<AudioToken>& tokens) { | |
230 // Distribute the tokens to the appropriate app(s). | |
231 std::list<ReceivedToken> all_tokens; | |
232 std::map<std::string, std::vector<ReceivedToken*>> tokens_by_app; | |
233 for (const AudioToken& token : tokens) { | |
234 ReceivedToken api_token; | |
235 const std::string& raw_token = DecodeBase64Token(token.token); | |
236 api_token.token.assign(raw_token.c_str(), | |
237 raw_token.c_str() + raw_token.size()); | |
238 api_token.band = token.audible ? AUDIOBAND_AUDIBLE : AUDIOBAND_INAUDIBLE; | |
239 all_tokens.push_back(std::move(api_token)); | |
240 for (const auto& receiver : | |
241 receive_timers_[token.audible ? AUDIBLE : INAUDIBLE]) { | |
242 tokens_by_app[receiver.first].push_back(&all_tokens.back()); | |
243 } | |
244 } | |
245 | |
246 // Send events to the appropriate app(s). | |
247 for (const auto& app_entry : tokens_by_app) { | |
248 const std::string& app_id = app_entry.first; | |
249 const auto& app_tokens = app_entry.second; | |
250 if (app_id.empty()) | |
251 continue; | |
252 | |
253 // Construct the event arguments by hand because a given token can be | |
254 // present for multiple listeners, so constructing a | |
255 // std::vector<ReceivedToken> for each is inefficient. | |
256 std::unique_ptr<base::ListValue> tokens_value(new base::ListValue()); | |
257 for (const ReceivedToken* token : app_tokens) | |
258 tokens_value->Append(token->ToValue()); | |
259 std::unique_ptr<base::ListValue> args(new base::ListValue()); | |
260 args->Append(std::move(tokens_value)); | |
261 | |
262 EventRouter::Get(browser_context_) | |
263 ->DispatchEventToExtension( | |
264 app_id, base::WrapUnique(new Event(events::AUDIO_MODEM_ON_RECEIVED, | |
265 OnReceived::kEventName, | |
266 std::move(args)))); | |
267 } | |
268 } | |
269 | |
270 | |
271 // Functions outside the API scope. | |
272 | |
273 template <> | |
274 void | |
275 BrowserContextKeyedAPIFactory<AudioModemAPI>::DeclareFactoryDependencies() { | |
276 DependsOn(ExtensionsBrowserClient::Get()->GetExtensionSystemFactory()); | |
277 } | |
278 | |
279 ExtensionFunction::ResponseAction AudioModemTransmitFunction::Run() { | |
280 std::unique_ptr<Transmit::Params> params(Transmit::Params::Create(*args_)); | |
281 EXTENSION_FUNCTION_VALIDATE(params.get()); | |
282 AudioModemAPI* api = | |
283 AudioModemAPI::GetFactoryInstance()->Get(browser_context()); | |
284 if (api->init_failed()) { | |
285 return RespondNow(ErrorWithArguments( | |
286 Transmit::Results::Create(STATUS_CODERERROR), | |
287 kInitFailedError)); | |
288 } | |
289 | |
290 // Check the token length. | |
291 int token_length = params->params.encoding.token_length; | |
292 if (token_length <= 0) { | |
293 return RespondNow(ErrorWithArguments( | |
294 Transmit::Results::Create(STATUS_INVALIDREQUEST), | |
295 kInvalidTokenLengthError)); | |
296 } | |
297 const char* token = params->token.data(); | |
298 std::string token_str(token, params->token.size()); | |
299 if (static_cast<int>(token_str.size()) != token_length) { | |
300 return RespondNow(ErrorWithArguments( | |
301 Transmit::Results::Create(STATUS_INVALIDREQUEST), | |
302 kIncorrectTokenLengthError)); | |
303 } | |
304 | |
305 // Check the timeout. | |
306 int64_t timeout_millis = params->params.timeout_millis; | |
307 if (timeout_millis <= 0) { | |
308 return RespondNow(ErrorWithArguments( | |
309 Transmit::Results::Create(STATUS_INVALIDREQUEST), | |
310 kInvalidTimeoutError)); | |
311 } | |
312 if (timeout_millis > kMaxTransmitTimeout) | |
313 timeout_millis = kMaxTransmitTimeout; | |
314 | |
315 // Start transmission. | |
316 Status status = api->StartTransmit(extension_id(), params->params, token_str); | |
317 return RespondNow(ArgumentList(Transmit::Results::Create(status))); | |
318 } | |
319 | |
320 ExtensionFunction::ResponseAction AudioModemStopTransmitFunction::Run() { | |
321 std::unique_ptr<StopTransmit::Params> params( | |
322 StopTransmit::Params::Create(*args_)); | |
323 EXTENSION_FUNCTION_VALIDATE(params.get()); | |
324 | |
325 Status status = AudioModemAPI::GetFactoryInstance()->Get(browser_context()) | |
326 ->StopTransmit(extension_id(), AudioTypeForBand(params->band)); | |
327 return RespondNow(ArgumentList(StopTransmit::Results::Create(status))); | |
328 } | |
329 | |
330 ExtensionFunction::ResponseAction AudioModemReceiveFunction::Run() { | |
331 std::unique_ptr<Receive::Params> params(Receive::Params::Create(*args_)); | |
332 EXTENSION_FUNCTION_VALIDATE(params.get()); | |
333 AudioModemAPI* api = | |
334 AudioModemAPI::GetFactoryInstance()->Get(browser_context()); | |
335 if (api->init_failed()) { | |
336 return RespondNow(ErrorWithArguments( | |
337 Transmit::Results::Create(STATUS_CODERERROR), | |
338 kInitFailedError)); | |
339 } | |
340 | |
341 // Check the timeout. | |
342 int64_t timeout_millis = params->params.timeout_millis; | |
343 if (timeout_millis <= 0) { | |
344 return RespondNow(ErrorWithArguments( | |
345 Receive::Results::Create(STATUS_INVALIDREQUEST), | |
346 kInvalidTimeoutError)); | |
347 } | |
348 if (timeout_millis > kMaxReceiveTimeout) | |
349 timeout_millis = kMaxReceiveTimeout; | |
350 | |
351 // Start receiving. | |
352 api->StartReceive(extension_id(), params->params); | |
353 return RespondNow(ArgumentList(Receive::Results::Create(STATUS_SUCCESS))); | |
354 } | |
355 | |
356 ExtensionFunction::ResponseAction AudioModemStopReceiveFunction::Run() { | |
357 std::unique_ptr<StopReceive::Params> params( | |
358 StopReceive::Params::Create(*args_)); | |
359 EXTENSION_FUNCTION_VALIDATE(params.get()); | |
360 | |
361 Status status = AudioModemAPI::GetFactoryInstance()->Get(browser_context()) | |
362 ->StopReceive(extension_id(), AudioTypeForBand(params->band)); | |
363 return RespondNow(ArgumentList(StopReceive::Results::Create(status))); | |
364 } | |
365 | |
366 } // namespace extensions | |
OLD | NEW |