private BatchStatus UploadNextBatch(int batchSize, int keyAgeLimitInDays) { var lastSyncState = _settingsService.GetGatewayUploadState(); // Select only keys from last N days (by date of record creation) var uploadedOnAndAfterTicks = lastSyncState.CreationDateOfLastUploadedKey; var uploadedOnAndAfter = uploadedOnAndAfterTicks.HasValue ? new DateTime(uploadedOnAndAfterTicks.Value, DateTimeKind.Utc) : DateTime.UnixEpoch; int batchSizePlusOne = batchSize + 1; // if it will return n + 1 then there is at last one more records to send // Get key package - collection of the records created (uploaded by mobile app) in the db after {uploadedOn} IList <TemporaryExposureKey> keyPackage = _tempKeyRepository.GetKeysOnlyFromApiOriginCountryUploadedAfterTheDateForGatewayUploadForWhichConsentWasGiven( uploadedOnAndLater: uploadedOnAndAfter, numberOfRecordToSkip: lastSyncState.NumberOfKeysProcessedFromTheLastCreationDate, maxCount: batchSizePlusOne, new KeySource[] { KeySource.SmitteStopApiVersion3 }); // Take all record uploaded after the date. var currBatchStatus = new BatchStatus() { NextBatchExists = keyPackage.Count == batchSizePlusOne }; keyPackage = keyPackage.Take(batchSize).ToList(); currBatchStatus.KeysProcessed = keyPackage.Count; currBatchStatus.ProcessedSuccessfully = true; if (!keyPackage.Any()) { _logger.LogInformation("KeyPackage is empty. Stopping the upload process."); return(currBatchStatus); } _logger.LogInformation($"{keyPackage.Count} records picked."); var oldestKeyFromPackage = keyPackage.Last(); // be aware that it could not be present in the batch. This is the last record (have oldest CreationOn) that has been processed. // Create Batch based on package but filter in on RollingStartNumber. Batch.Size <= Package.Size // We can send data not older than N days ago (age of the key save in key.RollingStartNumber) DateTime createdAfter = DateTime.UtcNow.AddDays(-keyAgeLimitInDays); var createdAfterTimestamp = _epochConverter.ConvertToEpoch(createdAfter); var filteredKeysForBatch = keyPackage .Where(k => k.RollingStartNumber > createdAfterTimestamp) .Where(k => k.DaysSinceOnsetOfSymptoms >= KeyValidator.DaysSinceOnsetOfSymptomsValidRangeMin) .Where(k => k.DaysSinceOnsetOfSymptoms <= KeyValidator.DaysSinceOnsetOfSymptomsValidRangeMax) .ToList(); currBatchStatus.KeysToSend = filteredKeysForBatch.Count; var batch = CreateGatewayBatchFromKeys(filteredKeysForBatch); TemporaryExposureKeyGatewayBatchProtoDto protoBatch = MapBatchDtoToProtoAndSortForSigning(batch); _logger.LogInformation($"{currBatchStatus.KeysToSend} records to send after filtering by age."); if (protoBatch.Keys.Any()) { _logger.LogInformation($"Sending batch..."); currBatchStatus.ProcessedSuccessfully = TrySendKeyBatchToTheGateway(protoBatch, KeysSortOrderUsedToCreateAndVerifyUploadRequestSignature); } else { _logger.LogInformation($"Nothing to sent for this package. All picked keys are older then {keyAgeLimitInDays} days or have incorrect value of DaysSinceOnsetOfSymptoms."); } if (currBatchStatus.ProcessedSuccessfully) { currBatchStatus.KeysSent = protoBatch.Keys.Count; var currSyncState = new GatewayUploadState(); var lastSentKeyTicks = oldestKeyFromPackage.CreatedOn.Ticks; // If the date of the last sent element (recently) has changed in compare the date from the previous batch? // NO - Update the state: Do not change the date, only offset (+=) // YES - Update the state: Change date to the date from last sent element. Update the index to the offset from the first appearance of that date.) if (lastSyncState.CreationDateOfLastUploadedKey == lastSentKeyTicks) { currSyncState.CreationDateOfLastUploadedKey = lastSyncState.CreationDateOfLastUploadedKey; currSyncState.NumberOfKeysProcessedFromTheLastCreationDate = lastSyncState.NumberOfKeysProcessedFromTheLastCreationDate + currBatchStatus.KeysProcessed; } else { currSyncState.CreationDateOfLastUploadedKey = oldestKeyFromPackage.CreatedOn.Ticks; // find offset form fist element with CreationDateOfLastUploadedKey to last sent element var indexOfTheFirstKeyWithTheDate = keyPackage.Select((k, i) => new { Key = k, Index = i }) .First(o => o.Key.CreatedOn.Ticks == lastSentKeyTicks) .Index; currSyncState.NumberOfKeysProcessedFromTheLastCreationDate = currBatchStatus.KeysProcessed - indexOfTheFirstKeyWithTheDate; } // save new sync status _settingsService.SaveGatewaySyncState(currSyncState); } else { _logger.LogError($"Error sending the batch! Stopping upload process."); } return(currBatchStatus); }