public async Task UploadSelfExposureKeysToServer(IEnumerable <TemporaryExposureKey> temporaryExposureKeys) { var diagnosisUid = LocalStateManager.Instance.LatestDiagnosis.DiagnosisUid; if (string.IsNullOrEmpty(diagnosisUid)) { throw new InvalidOperationException(); } try { var url = $"{apiUrlBase.TrimEnd('/')}/selfdiagnosis"; var json = JsonConvert.SerializeObject(new SelfDiagnosisSubmissionRequest { DiagnosisUid = diagnosisUid, Keys = temporaryExposureKeys }); var http = new HttpClient(); var response = await http.PutAsync(url, new StringContent(json)); response.EnsureSuccessStatusCode(); LocalStateManager.Instance.LatestDiagnosis.Shared = true; LocalStateManager.Save(); } catch { throw; } }
// this will be called when a potential exposure has been detected public async Task ExposureDetectedAsync(ExposureDetectionSummary summary, Func <Task <IEnumerable <ExposureInfo> > > getExposureInfo) { LocalStateManager.Instance.ExposureSummary = summary; var exposureInfo = await getExposureInfo(); // Add these on main thread in case the UI is visible so it can update await Device.InvokeOnMainThreadAsync(() => { foreach (var i in exposureInfo) { LocalStateManager.Instance.ExposureInformation.Add(i); } }); LocalStateManager.Save(); // If Enabled Local Notifications if (LocalStateManager.Instance.EnableNotifications) { var notification = new NotificationRequest { NotificationId = 100, Title = "Possible COVID-19 Exposure", Description = "It is possible you have been exposed to someone who was a confirmed diagnosis of COVID-19. Tap for more details." }; NotificationCenter.Current.Show(notification); } }
Task Disabled() { LocalStateManager.Instance.LastIsEnabled = false; LocalStateManager.Instance.IsWelcomed = false; LocalStateManager.Save(); return(NavigationService.NavigateAsync(nameof(HomePage))); }
// this will be called when they keys need to be collected from the server public async Task FetchExposureKeysFromServerAsync(ITemporaryExposureKeyBatches batches) { // This is "default" by default var region = LocalStateManager.Instance.Region ?? DefaultRegion; var checkForMore = true; do { try { // Find next batch number var batchNumber = LocalStateManager.Instance.ServerBatchNumber + 1; // Build the blob storage url for the given batch file we are on next var url = $"{apiUrlBlobStorageBase}/{blobStorageContainerNamePrefix}{region}/{batchNumber}.dat"; var response = await http.GetAsync(url); // If we get a 404, there are no newer batch files available to download if (response.StatusCode == System.Net.HttpStatusCode.NotFound) { checkForMore = false; break; } response.EnsureSuccessStatusCode(); // Skip batch files which are older than 14 days if (response.Content.Headers.LastModified.HasValue) { if (response.Content.Headers.LastModified < DateTimeOffset.UtcNow.AddDays(-14)) { LocalStateManager.Instance.ServerBatchNumber = batchNumber; LocalStateManager.Save(); checkForMore = true; continue; } } // Read the batch file stream using var responseStream = await response.Content.ReadAsStreamAsync(); // Parse into a Proto.File var batchFile = TemporaryExposureKeyBatch.Parser.ParseFrom(responseStream); // Submit to the batch processor await batches.AddBatchAsync(batchFile); // Update the number we are on LocalStateManager.Instance.ServerBatchNumber = batchNumber; LocalStateManager.Save(); } catch (Exception ex) { Console.WriteLine(ex); checkForMore = false; } } while (checkForMore); }
Task Disabled() { LocalStateManager.Instance.LastIsEnabled = false; LocalStateManager.Instance.IsWelcomed = false; LocalStateManager.Save(); return(GoToAsync($"//{nameof(WelcomePage)}")); }
// this will be called when the user is submitting a diagnosis and the local keys need to go to the server public async Task UploadSelfExposureKeysToServerAsync(IEnumerable <TemporaryExposureKey> temporaryExposureKeys) { var pendingDiagnosis = LocalStateManager.Instance.PendingDiagnosis; if (pendingDiagnosis == null || string.IsNullOrEmpty(pendingDiagnosis.DiagnosisUid)) { throw new InvalidOperationException(); } var selfDiag = await CreateSubmissionAsync(); var url = $"{AppSettings.Instance.ApiUrlBase.TrimEnd('/')}/selfdiagnosis"; var json = JsonConvert.SerializeObject(selfDiag); using var http = new HttpClient(); var response = await http.PutAsync(url, new StringContent(json)); response.EnsureSuccessStatusCode(); // Update pending status pendingDiagnosis.Shared = true; LocalStateManager.Save(); async Task <SelfDiagnosisSubmission> CreateSubmissionAsync() { // Create the network keys var keys = temporaryExposureKeys.Select(k => new ExposureKey { Key = Convert.ToBase64String(k.Key), RollingStart = (long)(k.RollingStart - DateTime.UnixEpoch).TotalMinutes / 10, RollingDuration = (int)(k.RollingDuration.TotalMinutes / 10), TransmissionRisk = (int)k.TransmissionRiskLevel }); // Create the submission var submission = new SelfDiagnosisSubmission(true) { AppPackageName = AppInfo.PackageName, DeviceVerificationPayload = null, Platform = DeviceInfo.Platform.ToString().ToLowerInvariant(), Regions = AppSettings.Instance.SupportedRegions, Keys = keys.ToArray(), VerificationPayload = pendingDiagnosis.DiagnosisUid, }; // See if we can add the device verification if (DependencyService.Get <IDeviceVerifier>() is IDeviceVerifier verifier) { submission.DeviceVerificationPayload = await verifier?.VerifyAsync(submission); } return(submission); } }
public async Task FetchExposureKeysFromServer(Func <IEnumerable <TemporaryExposureKey>, Task> addKeys) { var latestKeysResponseIndex = LocalStateManager.Instance.LatestKeysResponseIndex; var take = 1024; var skip = 0; var checkForMore = false; do { // Get the newest date we have keys from and request since then // or if no date stored, only return as much as the past 14 days of keys var url = $"{apiUrlBase.TrimEnd('/')}/keys?since={latestKeysResponseIndex}&skip={skip}&take={take}"; var response = await http.GetAsync(url); response.EnsureSuccessStatusCode(); var responseData = await response.Content.ReadAsStringAsync(); if (string.IsNullOrEmpty(responseData)) { break; } // Response contains the timestamp in seconds since epoch, and the list of keys var keys = JsonConvert.DeserializeObject <KeysResponse>(responseData); var numKeys = keys?.Keys?.Count() ?? 0; // See if keys were returned on this call if (numKeys > 0) { // Call the callback with the batch of keys to add await addKeys(keys.Keys); var newLatestKeysResponseIndex = keys.Latest; if (newLatestKeysResponseIndex > LocalStateManager.Instance.LatestKeysResponseIndex) { LocalStateManager.Instance.LatestKeysResponseIndex = newLatestKeysResponseIndex; LocalStateManager.Save(); } // Increment our skip starting point for the next batch skip += take; } // If we got back more or the same amount of our requested take, there may be // more left on the server to request again checkForMore = numKeys >= take; } while (checkForMore); }
public async Task ExposureDetected(ExposureDetectionSummary summary, Func <Task <IEnumerable <ExposureInfo> > > getDetailsFunc) { LocalStateManager.Instance.ExposureSummary = summary; var details = await getDetailsFunc(); LocalStateManager.Instance.ExposureInformation.AddRange(details); LocalStateManager.Save(); MessagingCenter.Instance.Send(this, "exposure_info_changed"); // TODO: Save this info and alert the user // Pop up a local notification }
// this will be called when a potential exposure has been detected public Task ExposureDetectedAsync(ExposureDetectionSummary summary, IEnumerable <ExposureInfo> exposureInfo) { LocalStateManager.Instance.ExposureSummary = summary; LocalStateManager.Instance.ExposureInformation.AddRange(exposureInfo); LocalStateManager.Save(); MessagingCenter.Instance.Send(this, "exposure_info_changed"); var notification = new NotificationRequest { NotificationId = 100, Title = "Possible COVID-19 Exposure", Description = "It is possible you have been exposed to someone who was a confirmed diagnosis of COVID-19. Tap for more details." }; NotificationCenter.Current.Show(notification); return(Task.CompletedTask); }
// this will be called when they keys need to be collected from the server public async Task FetchExposureKeyBatchFilesFromServerAsync(Func <IEnumerable <string>, Task> submitBatches, CancellationToken cancellationToken) { // This is "default" by default var rightNow = DateTimeOffset.UtcNow; try { foreach (var serverRegion in AppSettings.Instance.SupportedRegions) { // Find next directory to start checking var dirNumber = LocalStateManager.Instance.ServerBatchNumbers[serverRegion] + 1; // For all the directories while (true) { cancellationToken.ThrowIfCancellationRequested(); // Download all the files for this directory var(batchNumber, downloadedFiles) = await DownloadBatchAsync(serverRegion, dirNumber, cancellationToken); if (batchNumber == 0) { break; } // Process the current directory, if there were any files if (downloadedFiles.Count > 0) { await submitBatches(downloadedFiles); // delete all temporary files foreach (var file in downloadedFiles) { try { File.Delete(file); } catch { // no-op } } } // Update the preferences LocalStateManager.Instance.ServerBatchNumbers[serverRegion] = dirNumber; LocalStateManager.Save(); dirNumber++; } } } catch (Exception ex) { // any expections, bail out and wait for the next time // TODO: log the error on some server! Console.WriteLine(ex); } async Task <(int, List <string>)> DownloadBatchAsync(string region, ulong dirNumber, CancellationToken cancellationToken) { var downloadedFiles = new List <string>(); var batchNumber = 0; // For all the batches in a directory while (true) { // Build the blob storage url for the given batch file we are on next var url = $"{AppSettings.Instance.BlobStorageUrlBase}/{AppSettings.Instance.BlobStorageContainerNamePrefix}{region.ToLowerInvariant()}/{dirNumber}/{batchNumber + 1}.dat"; var response = await http.GetAsync(url, cancellationToken); // If we get a 404, there are no newer batch files available to download if (response.StatusCode == System.Net.HttpStatusCode.NotFound) { break; } response.EnsureSuccessStatusCode(); // Skip batch files which are older than 14 days if (response.Content.Headers.LastModified.HasValue && response.Content.Headers.LastModified < rightNow.AddDays(-14)) { // If the first one is too old, the fake download it and pretend there was only one // We can do this because each batch was created at the same time batchNumber++; break; } var tmpFile = Path.Combine(FileSystem.CacheDirectory, Guid.NewGuid().ToString() + ".zip"); // Read the batch file stream into a temporary file using var responseStream = await response.Content.ReadAsStreamAsync(); using var fileStream = File.Create(tmpFile); await responseStream.CopyToAsync(fileStream, cancellationToken); downloadedFiles.Add(tmpFile); batchNumber++; } return(batchNumber, downloadedFiles); } }