Page MenuHomePhabricator

D11232.id38334.diff
No OneTemporary

D11232.id38334.diff

diff --git a/native/cpp/CommonCpp/NativeModules/CommCoreModule.h b/native/cpp/CommonCpp/NativeModules/CommCoreModule.h
--- a/native/cpp/CommonCpp/NativeModules/CommCoreModule.h
+++ b/native/cpp/CommonCpp/NativeModules/CommCoreModule.h
@@ -28,7 +28,9 @@
const std::string secureStoreAccountDataKey = "cryptoAccountDataKey";
const std::string publicCryptoAccountID = "publicCryptoAccountID";
- std::unique_ptr<crypto::CryptoModule> cryptoModule;
+ std::unique_ptr<crypto::CryptoModule> contentCryptoModule;
+ const std::string notifsCryptoAccountID = "notifsCryptoAccountID";
+ std::unique_ptr<crypto::CryptoModule> notifsCryptoModule;
DraftStore draftStore;
ThreadStore threadStore;
MessageStore messageStore;
@@ -38,7 +40,8 @@
CommunityStore communityStore;
IntegrityStore integrityStore;
- void persistCryptoModule();
+ void
+ persistCryptoModules(bool persistContentModule, bool persistNotifsModule);
virtual jsi::Value getDraft(jsi::Runtime &rt, jsi::String key) override;
virtual jsi::Value
diff --git a/native/cpp/CommonCpp/NativeModules/CommCoreModule.cpp b/native/cpp/CommonCpp/NativeModules/CommCoreModule.cpp
--- a/native/cpp/CommonCpp/NativeModules/CommCoreModule.cpp
+++ b/native/cpp/CommonCpp/NativeModules/CommCoreModule.cpp
@@ -351,7 +351,9 @@
TerminateApp::terminate();
}
-void CommCoreModule::persistCryptoModule() {
+void CommCoreModule::persistCryptoModules(
+ bool persistContentModule,
+ bool persistNotifsModule) {
folly::Optional<std::string> storedSecretKey =
CommSecureStore::get(this->secureStoreAccountDataKey);
if (!storedSecretKey.hasValue()) {
@@ -360,17 +362,37 @@
this->secureStoreAccountDataKey, storedSecretKey.value());
}
- crypto::Persist newPersist =
- this->cryptoModule->storeAsB64(storedSecretKey.value());
+ if (!persistContentModule && !persistNotifsModule) {
+ return;
+ }
+
+ crypto::Persist newContentPersist;
+ if (persistContentModule) {
+ newContentPersist =
+ this->contentCryptoModule->storeAsB64(storedSecretKey.value());
+ }
+
+ crypto::Persist newNotifsPersist;
+ if (persistNotifsModule) {
+ newNotifsPersist =
+ this->notifsCryptoModule->storeAsB64(storedSecretKey.value());
+ }
std::promise<void> persistencePromise;
std::future<void> persistenceFuture = persistencePromise.get_future();
GlobalDBSingleton::instance.scheduleOrRunCancellable(
[=, &persistencePromise]() {
try {
- DatabaseManager::getQueryExecutor().storeOlmPersistData(
- DatabaseManager::getQueryExecutor().getContentAccountID(),
- newPersist);
+ if (persistContentModule) {
+ DatabaseManager::getQueryExecutor().storeOlmPersistData(
+ DatabaseManager::getQueryExecutor().getContentAccountID(),
+ newContentPersist);
+ }
+ if (persistNotifsModule) {
+ DatabaseManager::getQueryExecutor().storeOlmPersistData(
+ DatabaseManager::getQueryExecutor().getNotifsAccountID(),
+ newNotifsPersist);
+ }
persistencePromise.set_value();
} catch (std::system_error &e) {
persistencePromise.set_exception(std::make_exception_ptr(e));
@@ -391,15 +413,16 @@
return createPromiseAsJSIValue(
rt, [=](jsi::Runtime &innerRt, std::shared_ptr<Promise> promise) {
taskType job = [=]() {
- crypto::Persist persist;
+ crypto::Persist contentPersist;
+ crypto::Persist notifsPersist;
std::string error;
try {
- std::optional<std::string> accountData =
+ std::optional<std::string> contentAccountData =
DatabaseManager::getQueryExecutor().getOlmPersistAccountData(
DatabaseManager::getQueryExecutor().getContentAccountID());
- if (accountData.has_value()) {
- persist.account =
- crypto::OlmBuffer(accountData->begin(), accountData->end());
+ if (contentAccountData.has_value()) {
+ contentPersist.account = crypto::OlmBuffer(
+ contentAccountData->begin(), contentAccountData->end());
// handle sessions data
std::vector<OlmPersistSession> sessionsData =
DatabaseManager::getQueryExecutor()
@@ -408,65 +431,51 @@
crypto::OlmBuffer sessionDataBuffer(
sessionsDataItem.session_data.begin(),
sessionsDataItem.session_data.end());
- persist.sessions.insert(std::make_pair(
+ contentPersist.sessions.insert(std::make_pair(
sessionsDataItem.target_user_id, sessionDataBuffer));
}
}
+
+ std::optional<std::string> notifsAccountData =
+ DatabaseManager::getQueryExecutor().getOlmPersistAccountData(
+ DatabaseManager::getQueryExecutor().getNotifsAccountID());
+ if (notifsAccountData.has_value()) {
+ notifsPersist.account = crypto::OlmBuffer(
+ notifsAccountData->begin(), notifsAccountData->end());
+ }
+
} catch (std::system_error &e) {
error = e.what();
}
this->cryptoThread->scheduleTask([=]() {
std::string error;
- this->cryptoModule.reset(new crypto::CryptoModule(
- this->publicCryptoAccountID, storedSecretKey.value(), persist));
- if (persist.isEmpty()) {
- crypto::Persist newPersist =
- this->cryptoModule->storeAsB64(storedSecretKey.value());
- GlobalDBSingleton::instance.scheduleOrRunCancellable(
- [=]() {
- std::string error;
- try {
- DatabaseManager::getQueryExecutor().storeOlmPersistData(
- DatabaseManager::getQueryExecutor()
- .getContentAccountID(),
- newPersist);
- } catch (std::system_error &e) {
- error = e.what();
- }
- this->jsInvoker_->invokeAsync([=]() {
- if (error.size()) {
- promise->reject(error);
- return;
- }
- });
- },
- promise,
- this->jsInvoker_);
+ this->contentCryptoModule.reset(new crypto::CryptoModule(
+ this->publicCryptoAccountID,
+ storedSecretKey.value(),
+ contentPersist));
+
+ this->notifsCryptoModule.reset(new crypto::CryptoModule(
+ this->notifsCryptoAccountID,
+ storedSecretKey.value(),
+ notifsPersist));
- } else {
- this->cryptoModule->restoreFromB64(
- storedSecretKey.value(), persist);
- this->jsInvoker_->invokeAsync([=]() {
- if (error.size()) {
- promise->reject(error);
- return;
- }
- });
- }
try {
- NotificationsCryptoModule::initializeNotificationsCryptoAccount(
- "Comm");
+ this->persistCryptoModules(
+ contentPersist.isEmpty(), notifsPersist.isEmpty());
} catch (const std::exception &e) {
error = e.what();
}
+
this->jsInvoker_->invokeAsync([=]() {
if (error.size()) {
promise->reject(error);
return;
}
- promise->resolve(jsi::Value::undefined());
});
+
+ this->jsInvoker_->invokeAsync(
+ [=]() { promise->resolve(jsi::Value::undefined()); });
});
};
GlobalDBSingleton::instance.scheduleOrRunCancellable(
@@ -481,19 +490,13 @@
std::string error;
std::string primaryKeysResult;
std::string notificationsKeysResult;
- if (this->cryptoModule == nullptr) {
+ if (this->contentCryptoModule == nullptr ||
+ this->notifsCryptoModule == nullptr) {
error = "user has not been initialized";
} else {
- primaryKeysResult = this->cryptoModule->getIdentityKeys();
- }
- try {
- if (!error.size()) {
- notificationsKeysResult =
- NotificationsCryptoModule::getNotificationsIdentityKeys(
- "Comm");
- }
- } catch (const std::exception &e) {
- error = e.what();
+ primaryKeysResult = this->contentCryptoModule->getIdentityKeys();
+ notificationsKeysResult =
+ this->notifsCryptoModule->getIdentityKeys();
}
std::string notificationsCurve25519Cpp, notificationsEd25519Cpp,
@@ -534,7 +537,8 @@
"curve25519", notificationsCurve25519Cpp));
blobPayloadCpp = folly::toJson(blobPayloadJSON);
- signatureCpp = this->cryptoModule->signMessage(blobPayloadCpp);
+ signatureCpp =
+ this->contentCryptoModule->signMessage(blobPayloadCpp);
}
}
}
@@ -650,19 +654,20 @@
std::string error;
std::string contentResult;
std::string notifResult;
- if (this->cryptoModule == nullptr) {
+ if (this->contentCryptoModule == nullptr ||
+ this->notifsCryptoModule == nullptr) {
this->jsInvoker_->invokeAsync([=, &innerRt]() {
promise->reject("user has not been initialized");
});
return;
}
try {
- contentResult = this->cryptoModule->getOneTimeKeysForPublishing(
+ contentResult =
+ this->contentCryptoModule->getOneTimeKeysForPublishing(
+ oneTimeKeysAmount);
+ notifResult = this->notifsCryptoModule->getOneTimeKeysForPublishing(
oneTimeKeysAmount);
- this->persistCryptoModule();
- notifResult = NotificationsCryptoModule::
- getNotificationsOneTimeKeysForPublishing(
- oneTimeKeysAmount, "Comm");
+ this->persistCryptoModules(true, true);
} catch (const std::exception &e) {
error = e.what();
}
@@ -679,18 +684,6 @@
});
}
-std::pair<std::string, std::string> getNotificationsPrekeyAndSignature() {
- // TODO: Implement notifs prekey rotation.
- // Notifications prekey is not rotated at this moment. It
- // is fetched with signature to match identity service API.
- std::string notificationsPrekey =
- NotificationsCryptoModule::getNotificationsPrekey("Comm");
- std::string notificationsPrekeySignature =
- NotificationsCryptoModule::getNotificationsPrekeySignature("Comm");
-
- return std::make_pair(notificationsPrekey, notificationsPrekeySignature);
-}
-
jsi::Value CommCoreModule::validateAndUploadPrekeys(
jsi::Runtime &rt,
jsi::String authUserID,
@@ -703,9 +696,11 @@
rt, [=](jsi::Runtime &innerRt, std::shared_ptr<Promise> promise) {
taskType job = [=, &innerRt]() {
std::string error;
- std::optional<std::string> maybePrekeyToUpload;
+ std::optional<std::string> maybeContentPrekeyToUpload;
+ std::optional<std::string> maybeNotifsPrekeyToUpload;
- if (this->cryptoModule == nullptr) {
+ if (this->contentCryptoModule == nullptr ||
+ this->notifsCryptoModule == nullptr) {
this->jsInvoker_->invokeAsync([=, &innerRt]() {
promise->reject("user has not been initialized");
});
@@ -713,10 +708,19 @@
}
try {
- maybePrekeyToUpload = this->cryptoModule->validatePrekey();
- this->persistCryptoModule();
- if (!maybePrekeyToUpload.has_value()) {
- maybePrekeyToUpload = this->cryptoModule->getUnpublishedPrekey();
+ maybeContentPrekeyToUpload =
+ this->contentCryptoModule->validatePrekey();
+ maybeNotifsPrekeyToUpload =
+ this->notifsCryptoModule->validatePrekey();
+ this->persistCryptoModules(true, true);
+
+ if (!maybeContentPrekeyToUpload.has_value()) {
+ maybeContentPrekeyToUpload =
+ this->contentCryptoModule->getUnpublishedPrekey();
+ }
+ if (!maybeNotifsPrekeyToUpload.has_value()) {
+ maybeNotifsPrekeyToUpload =
+ this->notifsCryptoModule->getUnpublishedPrekey();
}
} catch (const std::exception &e) {
error = e.what();
@@ -726,21 +730,36 @@
this->jsInvoker_->invokeAsync(
[=, &innerRt]() { promise->reject(error); });
return;
- } else if (!maybePrekeyToUpload.has_value()) {
+ }
+
+ if (!maybeContentPrekeyToUpload.has_value() &&
+ !maybeNotifsPrekeyToUpload.has_value()) {
this->jsInvoker_->invokeAsync(
[=]() { promise->resolve(jsi::Value::undefined()); });
return;
}
- std::string prekeyToUpload = maybePrekeyToUpload.value();
+ std::string contentPrekeyToUpload;
+ if (maybeContentPrekeyToUpload.has_value()) {
+ contentPrekeyToUpload = maybeContentPrekeyToUpload.value();
+ } else {
+ contentPrekeyToUpload = this->contentCryptoModule->getPrekey();
+ }
+
+ std::string notifsPrekeyToUpload;
+ if (maybeNotifsPrekeyToUpload.has_value()) {
+ notifsPrekeyToUpload = maybeNotifsPrekeyToUpload.value();
+ } else {
+ notifsPrekeyToUpload = this->notifsCryptoModule->getPrekey();
+ }
+
std::string prekeyUploadError;
try {
- std::string prekeySignature =
- this->cryptoModule->getPrekeySignature();
- std::string notificationsPrekey, notificationsPrekeySignature;
- std::tie(notificationsPrekey, notificationsPrekeySignature) =
- getNotificationsPrekeyAndSignature();
+ std::string contentPrekeySignature =
+ this->contentCryptoModule->getPrekeySignature();
+ std::string notifsPrekeySignature =
+ this->notifsCryptoModule->getPrekeySignature();
try {
std::promise<folly::dynamic> prekeyPromise;
@@ -750,20 +769,20 @@
std::move(prekeyPromise)};
auto currentID = RustPromiseManager::instance.addPromise(
std::move(promiseInfo));
- auto prekeyToUploadRust =
- rust::String(parseOLMPrekey(prekeyToUpload));
- auto prekeySignatureRust = rust::string(prekeySignature);
- auto notificationsPrekeyRust =
- rust::String(parseOLMPrekey(notificationsPrekey));
+ auto contentPrekeyToUploadRust =
+ rust::String(parseOLMPrekey(contentPrekeyToUpload));
+ auto prekeySignatureRust = rust::string(contentPrekeySignature);
+ auto notifsPrekeyToUploadRust =
+ rust::String(parseOLMPrekey(notifsPrekeyToUpload));
auto notificationsPrekeySignatureRust =
- rust::string(notificationsPrekeySignature);
+ rust::string(notifsPrekeySignature);
::identityRefreshUserPrekeys(
authUserIDRust,
authDeviceIDRust,
authAccessTokenRust,
- prekeyToUploadRust,
+ contentPrekeyToUploadRust,
prekeySignatureRust,
- notificationsPrekeyRust,
+ notifsPrekeyToUploadRust,
notificationsPrekeySignatureRust,
currentID);
prekeyFuture.get();
@@ -772,8 +791,9 @@
}
if (!prekeyUploadError.size()) {
- this->cryptoModule->markPrekeyAsPublished();
- this->persistCryptoModule();
+ this->contentCryptoModule->markPrekeyAsPublished();
+ this->notifsCryptoModule->markPrekeyAsPublished();
+ this->persistCryptoModules(true, true);
}
} catch (std::exception &e) {
error = e.what();
@@ -801,32 +821,44 @@
taskType job = [=, &innerRt]() {
std::string error;
std::string contentPrekey, notifPrekey, contentPrekeySignature,
- notifPrekeyBlob, notifPrekeySignature;
+ notifPrekeySignature;
std::optional<std::string> contentPrekeyBlob;
+ std::optional<std::string> notifPrekeyBlob;
- if (this->cryptoModule == nullptr) {
+ if (this->contentCryptoModule == nullptr ||
+ this->notifsCryptoModule == nullptr) {
this->jsInvoker_->invokeAsync([=, &innerRt]() {
promise->reject("user has not been initialized");
});
return;
}
try {
- contentPrekeyBlob = this->cryptoModule->validatePrekey();
+ contentPrekeyBlob = this->contentCryptoModule->validatePrekey();
if (!contentPrekeyBlob) {
- contentPrekeyBlob = this->cryptoModule->getUnpublishedPrekey();
+ contentPrekeyBlob =
+ this->contentCryptoModule->getUnpublishedPrekey();
}
if (!contentPrekeyBlob) {
- contentPrekeyBlob = this->cryptoModule->getPrekey();
+ contentPrekeyBlob = this->contentCryptoModule->getPrekey();
}
- this->persistCryptoModule();
- contentPrekeySignature = this->cryptoModule->getPrekeySignature();
+ notifPrekeyBlob = this->notifsCryptoModule->validatePrekey();
+ if (!notifPrekeyBlob) {
+ notifPrekeyBlob =
+ this->notifsCryptoModule->getUnpublishedPrekey();
+ }
+ if (!notifPrekeyBlob) {
+ notifPrekeyBlob = this->notifsCryptoModule->getPrekey();
+ }
+ this->persistCryptoModules(true, true);
- std::tie(notifPrekeyBlob, notifPrekeySignature) =
- getNotificationsPrekeyAndSignature();
+ contentPrekeySignature =
+ this->contentCryptoModule->getPrekeySignature();
+ notifPrekeySignature =
+ this->notifsCryptoModule->getPrekeySignature();
contentPrekey = parseOLMPrekey(contentPrekeyBlob.value());
- notifPrekey = parseOLMPrekey(notifPrekeyBlob);
+ notifPrekey = parseOLMPrekey(notifPrekeyBlob.value());
} catch (const std::exception &e) {
error = e.what();
}
@@ -880,6 +912,9 @@
std::string error;
crypto::EncryptedData result;
try {
+ // Introduced temporarily to make this diff non-breaking change
+ NotificationsCryptoModule::initializeNotificationsCryptoAccount(
+ "Comm");
result = NotificationsCryptoModule::initializeNotificationsSession(
identityKeysCpp,
prekeyCpp,
@@ -1075,7 +1110,7 @@
std::string error;
crypto::EncryptedData initialEncryptedMessage;
try {
- this->cryptoModule->initializeOutboundForSendingSession(
+ this->contentCryptoModule->initializeOutboundForSendingSession(
deviceIDCpp,
std::vector<uint8_t>(
identityKeysCpp.begin(), identityKeysCpp.end()),
@@ -1087,8 +1122,8 @@
const std::string initMessage = "{\"type\": \"init\"}";
initialEncryptedMessage =
- cryptoModule->encrypt(deviceIDCpp, initMessage);
- this->persistCryptoModule();
+ contentCryptoModule->encrypt(deviceIDCpp, initMessage);
+ this->persistCryptoModules(true, false);
} catch (const std::exception &e) {
error = e.what();
}
@@ -1122,7 +1157,7 @@
std::string error;
std::string decryptedMessage;
try {
- this->cryptoModule->initializeInboundForReceivingSession(
+ this->contentCryptoModule->initializeInboundForReceivingSession(
deviceIDCpp,
std::vector<uint8_t>(
encryptedMessageCpp.begin(), encryptedMessageCpp.end()),
@@ -1131,8 +1166,8 @@
crypto::EncryptedData encryptedData{std::vector<uint8_t>(
encryptedMessageCpp.begin(), encryptedMessageCpp.end())};
decryptedMessage =
- cryptoModule->decrypt(deviceIDCpp, encryptedData);
- this->persistCryptoModule();
+ this->contentCryptoModule->decrypt(deviceIDCpp, encryptedData);
+ this->persistCryptoModules(true, false);
} catch (const std::exception &e) {
error = e.what();
}
@@ -1161,8 +1196,9 @@
std::string error;
crypto::EncryptedData encryptedMessage;
try {
- encryptedMessage = cryptoModule->encrypt(deviceIDCpp, messageCpp);
- this->persistCryptoModule();
+ encryptedMessage =
+ contentCryptoModule->encrypt(deviceIDCpp, messageCpp);
+ this->persistCryptoModules(true, false);
} catch (const std::exception &e) {
error = e.what();
}
@@ -1198,8 +1234,8 @@
std::vector<uint8_t>(messageCpp.begin(), messageCpp.end()),
ENCRYPTED_MESSAGE_TYPE};
decryptedMessage =
- cryptoModule->decrypt(deviceIDCpp, encryptedData);
- this->persistCryptoModule();
+ this->contentCryptoModule->decrypt(deviceIDCpp, encryptedData);
+ this->persistCryptoModules(true, false);
} catch (const std::exception &e) {
error = e.what();
}
@@ -1224,7 +1260,7 @@
std::string error;
std::string signature;
try {
- signature = this->cryptoModule->signMessage(messageStr);
+ signature = this->contentCryptoModule->signMessage(messageStr);
} catch (const std::exception &e) {
error = "signing message failed with: " + std::string(e.what());
}
@@ -1650,7 +1686,7 @@
try {
pickleKey = crypto::Tools::generateRandomString(64);
crypto::Persist persist =
- this->cryptoModule->storeAsB64(pickleKey);
+ this->contentCryptoModule->storeAsB64(pickleKey);
pickledAccount =
std::string(persist.account.begin(), persist.account.end());
} catch (const std::exception &e) {
diff --git a/native/cpp/CommonCpp/Notifications/BackgroundDataStorage/NotificationsCryptoModule.h b/native/cpp/CommonCpp/Notifications/BackgroundDataStorage/NotificationsCryptoModule.h
--- a/native/cpp/CommonCpp/Notifications/BackgroundDataStorage/NotificationsCryptoModule.h
+++ b/native/cpp/CommonCpp/Notifications/BackgroundDataStorage/NotificationsCryptoModule.h
@@ -31,15 +31,6 @@
static void
initializeNotificationsCryptoAccount(const std::string &callingProcessName);
static void clearSensitiveData();
- static std::string
- getNotificationsIdentityKeys(const std::string &callingProcessName);
- static std::string
- getNotificationsPrekey(const std::string &callingProcessName);
- static std::string
- getNotificationsPrekeySignature(const std::string &callingProcessName);
- static std::string getNotificationsOneTimeKeysForPublishing(
- const size_t oneTimeKeysAmount,
- const std::string &callingProcessName);
static crypto::EncryptedData initializeNotificationsSession(
const std::string &identityKeys,
const std::string &prekey,
diff --git a/native/cpp/CommonCpp/Notifications/BackgroundDataStorage/NotificationsCryptoModule.cpp b/native/cpp/CommonCpp/Notifications/BackgroundDataStorage/NotificationsCryptoModule.cpp
--- a/native/cpp/CommonCpp/Notifications/BackgroundDataStorage/NotificationsCryptoModule.cpp
+++ b/native/cpp/CommonCpp/Notifications/BackgroundDataStorage/NotificationsCryptoModule.cpp
@@ -192,51 +192,6 @@
callingProcessName);
}
-std::string NotificationsCryptoModule::getNotificationsIdentityKeys(
- const std::string &callingProcessName) {
- std::string identityKeys;
- auto caller = [&identityKeys](
- const std::unique_ptr<crypto::CryptoModule> &cryptoModule) {
- identityKeys = cryptoModule->getIdentityKeys();
- };
- NotificationsCryptoModule::callCryptoModule(caller, callingProcessName);
- return identityKeys;
-}
-
-std::string NotificationsCryptoModule::getNotificationsPrekey(
- const std::string &callingProcessName) {
- std::string prekey;
- auto caller =
- [&prekey](const std::unique_ptr<crypto::CryptoModule> &cryptoModule) {
- prekey = cryptoModule->getPrekey();
- };
- NotificationsCryptoModule::callCryptoModule(caller, callingProcessName);
- return prekey;
-}
-
-std::string NotificationsCryptoModule::getNotificationsPrekeySignature(
- const std::string &callingProcessName) {
- std::string prekeySignature;
- auto caller = [&prekeySignature](
- const std::unique_ptr<crypto::CryptoModule> &cryptoModule) {
- prekeySignature = cryptoModule->getPrekeySignature();
- };
- NotificationsCryptoModule::callCryptoModule(caller, callingProcessName);
- return prekeySignature;
-}
-
-std::string NotificationsCryptoModule::getNotificationsOneTimeKeysForPublishing(
- const size_t oneTimeKeysAmount,
- const std::string &callingProcessName) {
- std::string oneTimeKeys;
- auto caller = [&oneTimeKeys, oneTimeKeysAmount](
- const std::unique_ptr<crypto::CryptoModule> &cryptoModule) {
- oneTimeKeys = cryptoModule->getOneTimeKeysForPublishing(oneTimeKeysAmount);
- };
- NotificationsCryptoModule::callCryptoModule(caller, callingProcessName);
- return oneTimeKeys;
-}
-
crypto::EncryptedData NotificationsCryptoModule::initializeNotificationsSession(
const std::string &identityKeys,
const std::string &prekey,

File Metadata

Mime Type
text/plain
Expires
Sun, Sep 29, 5:50 PM (19 h, 33 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
2201979
Default Alt Text
D11232.id38334.diff (26 KB)

Event Timeline