| 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 |