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.
9 #include "base/base64.h"
10 #include "base/bind.h"
11 #include "base/guid.h"
12 #include "base/lazy_instance.h"
13 #include "base/logging.h"
14 #include "base/memory/scoped_ptr.h"
15 #include "base/stl_util.h"
16 #include "base/strings/string_util.h"
17 #include "base/timer/timer.h"
18 #include "chrome/browser/copresence/chrome_whispernet_client.h"
19 #include "chrome/browser/extensions/api/audio_modem/audio_modem_api.h"
20 #include "chrome/common/extensions/api/audio_modem.h"
21 #include "extensions/browser/event_router.h"
23 // TODO(ckehoe): Implement transmit fail checking.
25 namespace extensions
{
27 using api::audio_modem::AUDIOBAND_AUDIBLE
;
28 using api::audio_modem::AUDIOBAND_INAUDIBLE
;
29 using api::audio_modem::Audioband
;
30 using api::audio_modem::STATUS_CODERERROR
;
31 using api::audio_modem::STATUS_INUSE
;
32 using api::audio_modem::STATUS_INVALIDREQUEST
;
33 using api::audio_modem::STATUS_SUCCESS
;
34 using api::audio_modem::ReceivedToken
;
35 using api::audio_modem::RequestParams
;
36 using api::audio_modem::Status
;
38 namespace Transmit
= api::audio_modem::Transmit
;
39 namespace StopTransmit
= api::audio_modem::StopTransmit
;
40 namespace Receive
= api::audio_modem::Receive
;
41 namespace StopReceive
= api::audio_modem::StopReceive
;
42 namespace OnReceived
= api::audio_modem::OnReceived
;
44 using audio_modem::AUDIBLE
;
45 using audio_modem::AudioToken
;
46 using audio_modem::AudioType
;
47 using audio_modem::INAUDIBLE
;
48 using audio_modem::TokenParameters
;
52 const char kInitFailedError
[] = "The audio modem is not available. "
53 "Failed to initialize the token encoder/decoder.";
54 const char kInvalidTokenLengthError
[] =
55 "The token length must be greater than zero.";
56 const char kIncorrectTokenLengthError
[] =
57 "The token provided did not match the declared token length.";
58 const char kInvalidTimeoutError
[] =
59 "Transmit and receive timeouts must be greater than zero.";
61 const int kMaxTransmitTimeout
= 10 * 60 * 1000; // 10 minutes
62 const int kMaxReceiveTimeout
= 60 * 60 * 1000; // 1 hour
64 base::LazyInstance
<BrowserContextKeyedAPIFactory
<AudioModemAPI
>>
65 g_factory
= LAZY_INSTANCE_INITIALIZER
;
67 AudioType
AudioTypeForBand(Audioband band
) {
69 case AUDIOBAND_AUDIBLE
:
71 case AUDIOBAND_INAUDIBLE
:
75 return audio_modem::AUDIO_TYPE_UNKNOWN
;
79 TokenParameters
TokenParamsForEncoding(
80 const api::audio_modem::TokenEncoding
& encoding
) {
81 return TokenParameters(encoding
.token_length
,
82 encoding
.crc
? *encoding
.crc
: false,
83 encoding
.parity
? *encoding
.parity
: true);
86 const std::string
DecodeBase64Token(std::string encoded_token
) {
87 // Make sure the token is padded correctly.
88 while (encoded_token
.size() % 4 > 0)
91 // Decode and return the token.
92 std::string raw_token
;
93 bool decode_success
= base::Base64Decode(encoded_token
, &raw_token
);
94 DCHECK(decode_success
);
103 AudioModemAPI::AudioModemAPI(content::BrowserContext
* context
)
104 : AudioModemAPI(context
,
105 make_scoped_ptr(new ChromeWhispernetClient(context
)),
106 audio_modem::Modem::Create()) {}
108 AudioModemAPI::AudioModemAPI(
109 content::BrowserContext
* context
,
110 scoped_ptr
<audio_modem::WhispernetClient
> whispernet_client
,
111 scoped_ptr
<audio_modem::Modem
> modem
)
112 : browser_context_(context
),
113 whispernet_client_(whispernet_client
.Pass()),
114 modem_(modem
.Pass()),
115 init_failed_(false) {
116 // We own these objects, so these callbacks will not outlive us.
117 whispernet_client_
->Initialize(
118 base::Bind(&AudioModemAPI::WhispernetInitComplete
,
119 base::Unretained(this)));
120 modem_
->Initialize(whispernet_client_
.get(),
121 base::Bind(&AudioModemAPI::TokensReceived
,
122 base::Unretained(this)));
125 AudioModemAPI::~AudioModemAPI() {
126 for (const auto& timer_entry
: receive_timers_
[0])
127 delete timer_entry
.second
;
128 for (const auto& timer_entry
: receive_timers_
[1])
129 delete timer_entry
.second
;
132 Status
AudioModemAPI::StartTransmit(const std::string
& app_id
,
133 const RequestParams
& params
,
134 const std::string
& token
) {
135 AudioType audio_type
= AudioTypeForBand(params
.band
);
136 if (transmitters_
[audio_type
].empty())
137 transmitters_
[audio_type
] = app_id
;
138 else if (transmitters_
[audio_type
] != app_id
)
141 DVLOG(3) << "Starting transmit for app " << app_id
;
143 std::string encoded_token
;
144 base::Base64Encode(token
, &encoded_token
);
145 base::RemoveChars(encoded_token
, "=", &encoded_token
);
147 modem_
->SetTokenParams(audio_type
, TokenParamsForEncoding(params
.encoding
));
148 modem_
->SetToken(audio_type
, encoded_token
);
149 modem_
->StartPlaying(audio_type
);
151 transmit_timers_
[audio_type
].Start(
153 base::TimeDelta::FromMilliseconds(params
.timeout_millis
),
154 base::Bind(base::IgnoreResult(&AudioModemAPI::StopTransmit
),
155 base::Unretained(this),
158 return STATUS_SUCCESS
;
161 Status
AudioModemAPI::StopTransmit(const std::string
& app_id
,
162 AudioType audio_type
) {
163 if (transmitters_
[audio_type
] != app_id
)
164 return STATUS_INVALIDREQUEST
;
166 DVLOG(3) << "Stopping transmit for app " << app_id
;
167 transmitters_
[audio_type
].clear();
168 modem_
->StopPlaying(audio_type
);
169 return STATUS_SUCCESS
;
172 void AudioModemAPI::StartReceive(const std::string
& app_id
,
173 const RequestParams
& params
) {
174 DVLOG(3) << "Starting receive for app " << app_id
;
176 AudioType audio_type
= AudioTypeForBand(params
.band
);
177 modem_
->SetTokenParams(audio_type
, TokenParamsForEncoding(params
.encoding
));
178 modem_
->StartRecording(audio_type
);
180 if (receive_timers_
[audio_type
].count(app_id
) == 0)
181 receive_timers_
[audio_type
][app_id
] = new base::OneShotTimer
<AudioModemAPI
>;
182 DCHECK(receive_timers_
[audio_type
][app_id
]);
183 receive_timers_
[audio_type
][app_id
]->Start(
185 base::TimeDelta::FromMilliseconds(params
.timeout_millis
),
186 base::Bind(base::IgnoreResult(&AudioModemAPI::StopReceive
),
187 base::Unretained(this),
192 Status
AudioModemAPI::StopReceive(const std::string
& app_id
,
193 AudioType audio_type
) {
194 if (receive_timers_
[audio_type
].count(app_id
) == 0)
195 return STATUS_INVALIDREQUEST
;
197 DCHECK(receive_timers_
[audio_type
][app_id
]);
198 delete receive_timers_
[audio_type
][app_id
];
199 receive_timers_
[audio_type
].erase(app_id
);
201 DVLOG(3) << "Stopping receive for app " << app_id
;
202 if (receive_timers_
[audio_type
].empty())
203 modem_
->StopRecording(audio_type
);
204 return STATUS_SUCCESS
;
208 BrowserContextKeyedAPIFactory
<AudioModemAPI
>*
209 AudioModemAPI::GetFactoryInstance() {
210 return g_factory
.Pointer();
214 // Private functions.
216 void AudioModemAPI::WhispernetInitComplete(bool success
) {
218 VLOG(2) << "Whispernet initialized successfully.";
220 LOG(ERROR
) << "Failed to initialize Whispernet!";
225 void AudioModemAPI::TokensReceived(const std::vector
<AudioToken
>& tokens
) {
226 // Distribute the tokens to the appropriate app(s).
227 std::map
<std::string
, std::vector
<linked_ptr
<ReceivedToken
>>> tokens_by_app
;
228 for (const AudioToken
& token
: tokens
) {
229 linked_ptr
<ReceivedToken
> api_token(new ReceivedToken
);
230 const std::string
& raw_token
= DecodeBase64Token(token
.token
);
231 api_token
->token
.assign(raw_token
.c_str(),
232 raw_token
.c_str() + raw_token
.size());
233 api_token
->band
= token
.audible
? AUDIOBAND_AUDIBLE
: AUDIOBAND_INAUDIBLE
;
234 for (const auto& receiver
:
235 receive_timers_
[token
.audible
? AUDIBLE
: INAUDIBLE
]) {
236 tokens_by_app
[receiver
.first
].push_back(api_token
);
240 // Send events to the appropriate app(s).
241 for (const auto& app_entry
: tokens_by_app
) {
242 const std::string
& app_id
= app_entry
.first
;
243 const auto& tokens
= app_entry
.second
;
247 EventRouter::Get(browser_context_
)
248 ->DispatchEventToExtension(
249 app_id
, make_scoped_ptr(new Event(events::AUDIO_MODEM_ON_RECEIVED
,
250 OnReceived::kEventName
,
251 OnReceived::Create(tokens
))));
256 // Functions outside the API scope.
260 BrowserContextKeyedAPIFactory
<AudioModemAPI
>::DeclareFactoryDependencies() {
261 DependsOn(ExtensionsBrowserClient::Get()->GetExtensionSystemFactory());
264 ExtensionFunction::ResponseAction
AudioModemTransmitFunction::Run() {
265 scoped_ptr
<Transmit::Params
> params(Transmit::Params::Create(*args_
));
266 EXTENSION_FUNCTION_VALIDATE(params
.get());
268 AudioModemAPI::GetFactoryInstance()->Get(browser_context());
269 if (api
->init_failed()) {
270 return RespondNow(ErrorWithArguments(
271 Transmit::Results::Create(STATUS_CODERERROR
),
275 // Check the token length.
276 int token_length
= params
->params
.encoding
.token_length
;
277 if (token_length
<= 0) {
278 return RespondNow(ErrorWithArguments(
279 Transmit::Results::Create(STATUS_INVALIDREQUEST
),
280 kInvalidTokenLengthError
));
282 const char* token
= vector_as_array(¶ms
->token
);
283 std::string
token_str(token
, params
->token
.size());
284 if (static_cast<int>(token_str
.size()) != token_length
) {
285 return RespondNow(ErrorWithArguments(
286 Transmit::Results::Create(STATUS_INVALIDREQUEST
),
287 kIncorrectTokenLengthError
));
290 // Check the timeout.
291 int64_t timeout_millis
= params
->params
.timeout_millis
;
292 if (timeout_millis
<= 0) {
293 return RespondNow(ErrorWithArguments(
294 Transmit::Results::Create(STATUS_INVALIDREQUEST
),
295 kInvalidTimeoutError
));
297 if (timeout_millis
> kMaxTransmitTimeout
)
298 timeout_millis
= kMaxTransmitTimeout
;
300 // Start transmission.
301 Status status
= api
->StartTransmit(extension_id(), params
->params
, token_str
);
302 return RespondNow(ArgumentList(Transmit::Results::Create(status
)));
305 ExtensionFunction::ResponseAction
AudioModemStopTransmitFunction::Run() {
306 scoped_ptr
<StopTransmit::Params
> params(StopTransmit::Params::Create(*args_
));
307 EXTENSION_FUNCTION_VALIDATE(params
.get());
309 Status status
= AudioModemAPI::GetFactoryInstance()->Get(browser_context())
310 ->StopTransmit(extension_id(), AudioTypeForBand(params
->band
));
311 return RespondNow(ArgumentList(StopTransmit::Results::Create(status
)));
314 ExtensionFunction::ResponseAction
AudioModemReceiveFunction::Run() {
315 scoped_ptr
<Receive::Params
> params(Receive::Params::Create(*args_
));
316 EXTENSION_FUNCTION_VALIDATE(params
.get());
318 AudioModemAPI::GetFactoryInstance()->Get(browser_context());
319 if (api
->init_failed()) {
320 return RespondNow(ErrorWithArguments(
321 Transmit::Results::Create(STATUS_CODERERROR
),
325 // Check the timeout.
326 int64_t timeout_millis
= params
->params
.timeout_millis
;
327 if (timeout_millis
<= 0) {
328 return RespondNow(ErrorWithArguments(
329 Receive::Results::Create(STATUS_INVALIDREQUEST
),
330 kInvalidTimeoutError
));
332 if (timeout_millis
> kMaxReceiveTimeout
)
333 timeout_millis
= kMaxReceiveTimeout
;
336 api
->StartReceive(extension_id(), params
->params
);
337 return RespondNow(ArgumentList(Receive::Results::Create(STATUS_SUCCESS
)));
340 ExtensionFunction::ResponseAction
AudioModemStopReceiveFunction::Run() {
341 scoped_ptr
<StopReceive::Params
> params(StopReceive::Params::Create(*args_
));
342 EXTENSION_FUNCTION_VALIDATE(params
.get());
344 Status status
= AudioModemAPI::GetFactoryInstance()->Get(browser_context())
345 ->StopReceive(extension_id(), AudioTypeForBand(params
->band
));
346 return RespondNow(ArgumentList(StopReceive::Results::Create(status
)));
349 } // namespace extensions