Skip to content

Commit f72e6d8

Browse files
smilesa-maurice
authored andcommitted
Added Instance ID check-in request.
This still requires the mock to be setup to test the request. It's possible to disable the mock right now and manually verify the test against the backend. PiperOrigin-RevId: 248428482
1 parent 2c28251 commit f72e6d8

File tree

3 files changed

+297
-13
lines changed

3 files changed

+297
-13
lines changed

app/instance_id/iid_data.fbs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,11 @@ table InstanceIdDesktopData {
2828
// Security token returned by the check-in service. This is used to request
2929
// tokens from the IID service.
3030
security_token:string;
31-
// When the device ID and security token expire in UTC milliseconds since the
31+
// Check-in digest, required for refresh requests.
32+
digest:string;
33+
// Time of the last succeessful checkin request in UTC milliseconds since the
3234
// epoch.
33-
expiration_time:uint64;
35+
last_checkin_time_ms:uint64;
3436
}
3537

3638
root_type InstanceIdDesktopData;

app/instance_id/instance_id_desktop_impl.cc

Lines changed: 191 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,19 +19,45 @@
1919
#include "app/instance_id/iid_data_generated.h"
2020
#include "app/src/app_identifier.h"
2121
#include "app/src/cleanup_notifier.h"
22+
#include "app/src/time.h"
23+
#include "flatbuffers/flexbuffers.h"
2224

2325
namespace firebase {
2426
namespace instance_id {
2527
namespace internal {
2628

2729
using firebase::app::secure::UserSecureManager;
2830

31+
// Check-in backend.
32+
static const char kCheckinUrl[] =
33+
"https://device-provisioning.googleapis.com/checkin";
34+
// Check-in refresh period (7 days) in milliseconds.
35+
static const uint64_t kCheckinRefreshPeriodMs =
36+
7 * 24 * 60 * firebase::internal::kMillisecondsPerMinute;
37+
// Request type and protocol for check-in requests.
38+
static const int kCheckinRequestType = 2;
39+
static const int kCheckinProtocolVersion = 2;
40+
// Instance ID backend.
41+
static const char kInstanceIdUrl[] = "https://fcmtoken.googleapis.com/register";
42+
2943
std::map<App*, InstanceIdDesktopImpl*>
3044
InstanceIdDesktopImpl::instance_id_by_app_; // NOLINT
3145
Mutex InstanceIdDesktopImpl::instance_id_by_app_mutex_; // NOLINT
3246

3347
InstanceIdDesktopImpl::InstanceIdDesktopImpl(App* app)
34-
: storage_semaphore_(0), app_(app) {
48+
: storage_semaphore_(0),
49+
app_(app),
50+
locale_("en_US" /* TODO(b/132732303) */),
51+
timezone_("America/Los_Angeles" /* TODO(b/132733022) */),
52+
logging_id_(rand()), // NOLINT
53+
ios_device_model_("iPhone 8" /* TODO */),
54+
ios_device_version_("8.0" /* TODO */),
55+
network_operation_complete_(0),
56+
terminating_(false) {
57+
rest::InitTransportCurl();
58+
transport_.reset(new rest::TransportCurl());
59+
(void)kInstanceIdUrl; // TODO(smiles): Remove this when registration is in.
60+
3561
future_manager().AllocFutureApi(this, kInstanceIdFnCount);
3662

3763
std::string package_name = app->options().package_name();
@@ -66,6 +92,19 @@ InstanceIdDesktopImpl::InstanceIdDesktopImpl(App* app)
6692
}
6793

6894
InstanceIdDesktopImpl::~InstanceIdDesktopImpl() {
95+
// Cancel any pending network operations.
96+
{
97+
MutexLock lock(network_operation_mutex_);
98+
// All outstanding operations should complete with an error.
99+
terminating_ = true;
100+
NetworkOperation* operation = network_operation_.get();
101+
if (operation) operation->Cancel();
102+
}
103+
// Cancel scheduled tasks and shut down the scheduler to prevent any
104+
// additional tasks being executed.
105+
scheduler_.CancelAllAndShutdownWorkerThread();
106+
107+
rest::CleanupTransportCurl();
69108
{
70109
MutexLock lock(instance_id_by_app_mutex_);
71110
auto it = instance_id_by_app_.find(app_);
@@ -77,9 +116,6 @@ InstanceIdDesktopImpl::~InstanceIdDesktopImpl() {
77116
CleanupNotifier* notifier = CleanupNotifier::FindByOwner(app_);
78117
assert(notifier);
79118
notifier->UnregisterObject(this);
80-
81-
// Make sure all the pending REST requests are either cancelled or resolved
82-
// here.
83119
}
84120

85121
InstanceIdDesktopImpl* InstanceIdDesktopImpl::GetInstance(App* app) {
@@ -147,12 +183,15 @@ Future<void> InstanceIdDesktopImpl::DeleteTokenLastResult() {
147183

148184
// Save the instance ID to local secure storage.
149185
bool InstanceIdDesktopImpl::SaveToStorage() {
186+
if (terminating_) return false;
187+
150188
// Build up a serialized buffer algorithmically:
151189
flatbuffers::FlatBufferBuilder builder;
152190

153191
auto iid_data_table = CreateInstanceIdDesktopDataDirect(
154192
builder, instance_id_.c_str(), checkin_data_.device_id.c_str(),
155-
checkin_data_.security_token.c_str(), expiration_time_);
193+
checkin_data_.security_token.c_str(), checkin_data_.digest.c_str(),
194+
checkin_data_.last_checkin_time_ms);
156195
builder.Finish(iid_data_table);
157196

158197
std::string save_string;
@@ -195,6 +234,8 @@ bool InstanceIdDesktopImpl::LoadFromStorage() {
195234

196235
// Delete the instance ID from local secure storage.
197236
bool InstanceIdDesktopImpl::DeleteFromStorage() {
237+
if (terminating_) return false;
238+
198239
Future<void> future = user_secure_manager_->DeleteUserData(app_->name());
199240

200241
future.OnCompletion(
@@ -233,7 +274,151 @@ bool InstanceIdDesktopImpl::ReadStoredInstanceIdData(
233274
instance_id_ = iid_data_fb->instance_id()->c_str();
234275
checkin_data_.device_id = iid_data_fb->device_id()->c_str();
235276
checkin_data_.security_token = iid_data_fb->security_token()->c_str();
236-
expiration_time_ = iid_data_fb->expiration_time();
277+
checkin_data_.digest = iid_data_fb->digest()->c_str();
278+
checkin_data_.last_checkin_time_ms = iid_data_fb->last_checkin_time_ms();
279+
return true;
280+
}
281+
282+
bool InstanceIdDesktopImpl::InitialOrRefreshCheckin() {
283+
if (terminating_) return false;
284+
285+
// Load check-in data from storage if it hasn't already been loaded.
286+
if (checkin_data_.last_checkin_time_ms == 0) {
287+
// Try loading from storage. Since we can't tell whether this failed
288+
// because the data doesn't exist or if there is an I/O error, just
289+
// continue.
290+
LoadFromStorage();
291+
}
292+
293+
// If we've already checked in.
294+
if (checkin_data_.last_checkin_time_ms > 0) {
295+
FIREBASE_ASSERT(!checkin_data_.device_id.empty() &&
296+
!checkin_data_.security_token.empty() &&
297+
!checkin_data_.digest.empty());
298+
// Make sure the device ID and token aren't expired.
299+
uint64_t time_elapsed_ms =
300+
firebase::internal::GetTimestamp() - checkin_data_.last_checkin_time_ms;
301+
if (time_elapsed_ms < kCheckinRefreshPeriodMs) {
302+
// Everything is up to date.
303+
return true;
304+
}
305+
checkin_data_.Clear();
306+
}
307+
308+
// Construct the JSON request.
309+
flexbuffers::Builder fbb;
310+
struct BuilderScope {
311+
BuilderScope(flexbuffers::Builder* fbb_, const CheckinData* checkin_data_,
312+
const char* locale_, const char* timezone_, int logging_id_,
313+
const char* ios_device_model_, const char* ios_device_version_)
314+
: fbb(fbb_),
315+
checkin_data(checkin_data_),
316+
locale(locale_),
317+
timezone(timezone_),
318+
logging_id(logging_id_),
319+
ios_device_model(ios_device_model_),
320+
ios_device_version(ios_device_version_) {}
321+
322+
flexbuffers::Builder* fbb;
323+
const CheckinData* checkin_data;
324+
const char* locale;
325+
const char* timezone;
326+
int logging_id;
327+
const char* ios_device_model;
328+
const char* ios_device_version;
329+
} builder_scope(&fbb, &checkin_data_, locale_.c_str(), timezone_.c_str(),
330+
logging_id_, ios_device_model_.c_str(),
331+
ios_device_version_.c_str());
332+
fbb.Map(
333+
[](BuilderScope& builder_scope) {
334+
flexbuffers::Builder* fbb = builder_scope.fbb;
335+
const CheckinData& checkin_data = *builder_scope.checkin_data;
336+
fbb->Map(
337+
"checkin",
338+
[](BuilderScope& builder_scope) {
339+
flexbuffers::Builder* fbb = builder_scope.fbb;
340+
const CheckinData& checkin_data = *builder_scope.checkin_data;
341+
fbb->Map(
342+
"iosbuild",
343+
[](BuilderScope& builder_scope) {
344+
flexbuffers::Builder* fbb = builder_scope.fbb;
345+
fbb->String("model", builder_scope.ios_device_model);
346+
fbb->String("os_version", builder_scope.ios_device_version);
347+
},
348+
builder_scope);
349+
fbb->Int("type", kCheckinRequestType);
350+
fbb->Int("user_number", 0 /* unused at the moment */);
351+
fbb->Int("last_checkin_msec", checkin_data.last_checkin_time_ms);
352+
},
353+
builder_scope);
354+
fbb->Int("fragment", 0 /* unused at the moment */);
355+
fbb->Int("logging_id", builder_scope.logging_id);
356+
fbb->String("locale", builder_scope.locale);
357+
fbb->Int("version", kCheckinProtocolVersion);
358+
fbb->String("digest", checkin_data.digest.c_str());
359+
fbb->String("timezone", builder_scope.timezone);
360+
fbb->Int("user_serial_number", 0 /* unused at the moment */);
361+
fbb->Int("id",
362+
flatbuffers::StringToInt(checkin_data.device_id.c_str()));
363+
fbb->Int("security_token",
364+
flatbuffers::StringToInt(checkin_data.security_token.c_str()));
365+
},
366+
builder_scope);
367+
fbb.Finish();
368+
request_buffer_.clear();
369+
flexbuffers::GetRoot(fbb.GetBuffer()).ToString(true, true, request_buffer_);
370+
// Send request to the server then wait for the response or for the request
371+
// to be canceled by another thread.
372+
{
373+
MutexLock lock(network_operation_mutex_);
374+
network_operation_.reset(
375+
new NetworkOperation(request_buffer_, &network_operation_complete_));
376+
rest::Request* request = &network_operation_->request;
377+
request->set_url(kCheckinUrl);
378+
request->set_method(rest::util::kPost);
379+
request->add_header(rest::util::kContentType, rest::util::kApplicationJson);
380+
network_operation_->Perform(transport_.get());
381+
}
382+
network_operation_complete_.Wait();
383+
384+
logging_id_ = rand(); // NOLINT
385+
{
386+
MutexLock lock(network_operation_mutex_);
387+
assert(network_operation_.get());
388+
const rest::Response& response = network_operation_->response;
389+
// Check for errors
390+
if (response.status() != rest::util::HttpSuccess) {
391+
LogError("Check-in failed with response %d '%s'", response.status(),
392+
response.GetBody());
393+
network_operation_.reset(nullptr);
394+
return false;
395+
}
396+
// Parse the response.
397+
flexbuffers::Builder fbb;
398+
flatbuffers::Parser parser;
399+
if (!parser.ParseFlexBuffer(response.GetBody(), nullptr, &fbb)) {
400+
LogError("Unable to parse response '%s'", response.GetBody());
401+
network_operation_.reset(nullptr);
402+
return false;
403+
}
404+
auto root = flexbuffers::GetRoot(fbb.GetBuffer()).AsMap();
405+
if (!root["stats_ok"].AsBool()) {
406+
LogError("Unexpected stats_ok field '%s' vs 'true'",
407+
root["stats_ok"].ToString().c_str());
408+
network_operation_.reset(nullptr);
409+
return false;
410+
}
411+
checkin_data_.device_id = root["android_id"].AsString().c_str();
412+
checkin_data_.security_token = root["security_token"].AsString().c_str();
413+
checkin_data_.digest = root["digest"].AsString().c_str();
414+
checkin_data_.last_checkin_time_ms = firebase::internal::GetTimestamp();
415+
network_operation_.reset(nullptr);
416+
if (!SaveToStorage()) {
417+
checkin_data_.Clear();
418+
LogError("Unable to save check-in information to storage.");
419+
return false;
420+
}
421+
}
237422
return true;
238423
}
239424

0 commit comments

Comments
 (0)