Skip to content

Commit

Permalink
Merge pull request #188 from DP-3T/feature/delay-current-day-key
Browse files Browse the repository at this point in the history
Add feature to hold back same day TEKs until the next day
  • Loading branch information
ubamrein authored Jul 14, 2020
2 parents 208c708 + 91220f1 commit 0fab0fb
Show file tree
Hide file tree
Showing 5 changed files with 69 additions and 11 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
package org.dpppt.backend.sdk.data.gaen;

import java.time.Duration;
import java.time.OffsetDateTime;
import java.util.List;

import org.dpppt.backend.sdk.model.gaen.GaenKey;
Expand All @@ -24,6 +25,14 @@ public interface GAENDataService {
*/
void upsertExposees(List<GaenKey> keys);

/**
* Upserts (Update or Inserts) the given list of exposed keys, with delayed release of same day TEKs
*
* @param keys the list of exposed keys to upsert
* @param delayedReceivedAt the timestamp to use for the delayed release (if null use now rounded to next bucket)
*/
void upsertExposeesDelayed(List<GaenKey> keys, OffsetDateTime delayedReceivedAt);

/**
* Returns the maximum id of the stored exposed entries for the given batch.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,12 @@ public JDBCGAENDataServiceImpl(String dbType, DataSource dataSource, Duration re
@Override
@Transactional(readOnly = false)
public void upsertExposees(List<GaenKey> gaenKeys) {
upsertExposeesDelayed(gaenKeys, null);
}

@Override
public void upsertExposeesDelayed(List<GaenKey> gaenKeys, OffsetDateTime delayedReceivedAt) {

String sql = null;
if (dbType.equals(PGSQL)) {
sql = "insert into t_gaen_exposed (key, rolling_start_number, rolling_period, transmission_risk_level, received_at) values (:key, :rolling_start_number, :rolling_period, :transmission_risk_level, :received_at)"
Expand All @@ -57,7 +63,8 @@ public void upsertExposees(List<GaenKey> gaenKeys) {
}
var parameterList = new ArrayList<MapSqlParameterSource>();
var nowMillis = System.currentTimeMillis();
var receivedAt = (nowMillis/releaseBucketDuration.toMillis() + 1) * releaseBucketDuration.toMillis() - 1;
//if delayedReceivedAt is supplied use it
var receivedAt = delayedReceivedAt == null? (nowMillis/releaseBucketDuration.toMillis() + 1) * releaseBucketDuration.toMillis() - 1 : delayedReceivedAt.toInstant().toEpochMilli();
for (var gaenKey : gaenKeys) {
MapSqlParameterSource params = new MapSqlParameterSource();
params.addValue("key", gaenKey.getKeyData());
Expand All @@ -71,6 +78,7 @@ public void upsertExposees(List<GaenKey> gaenKeys) {
jt.batchUpdate(sql, parameterList.toArray(new MapSqlParameterSource[0]));
}


@Override
@Transactional(readOnly = true)
public int getMaxExposedIdForKeyDate(Long keyDate, Long publishedAfter, Long publishedUntil) {
Expand Down Expand Up @@ -135,5 +143,4 @@ public void cleanDB(Duration retentionPeriod) {
String sqlExposed = "delete from t_gaen_exposed where received_at < :retention_time";
jt.update(sqlExposed, params);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,8 @@ public abstract class WSBaseConfig implements SchedulingConfigurer, WebMvcConfig
String keyIdentifier;
@Value("${ws.app.gaen.algorithm:1.2.840.10045.4.3.2}")
String gaenAlgorithm;
@Value("${ws.app.gaen.delayTodaysKeys: false}")
boolean delayTodaysKeys;

@Autowired(required = false)
ValidateRequest requestValidator;
Expand Down Expand Up @@ -211,7 +213,7 @@ public GaenController gaenController() {
}
return new GaenController(gaenDataService(), fakeKeyService(), theValidator, gaenSigner(),
gaenValidationUtils(), Duration.ofMillis(releaseBucketDuration), Duration.ofMillis(requestTime),
Duration.ofMillis(exposedListCacheControl), keyVault.get("nextDayJWT").getPrivate());
Duration.ofMillis(exposedListCacheControl), keyVault.get("nextDayJWT").getPrivate(), delayTodaysKeys);
}

@Bean
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,9 +88,11 @@ public class GaenController {
private final PrivateKey secondDayKey;
private final ProtoSignature gaenSigner;

private final boolean delayTodaysKeys;

public GaenController(GAENDataService dataService, FakeKeyService fakeKeyService, ValidateRequest validateRequest,
ProtoSignature gaenSigner, ValidationUtils validationUtils, Duration releaseBucketDuration, Duration requestTime,
Duration exposedListCacheControl, PrivateKey secondDayKey) {
Duration exposedListCacheControl, PrivateKey secondDayKey, boolean delayTodaysKeys) {
this.dataService = dataService;
this.fakeKeyService = fakeKeyService;
this.releaseBucketDuration = releaseBucketDuration;
Expand All @@ -100,6 +102,7 @@ public GaenController(GAENDataService dataService, FakeKeyService fakeKeyService
this.exposedListCacheControl = exposedListCacheControl;
this.secondDayKey = secondDayKey;
this.gaenSigner = gaenSigner;
this.delayTodaysKeys = delayTodaysKeys;
}

@PostMapping(value = "/exposed")
Expand Down Expand Up @@ -129,6 +132,7 @@ public GaenController(GAENDataService dataService, FakeKeyService fakeKeyService
}

List<GaenKey> nonFakeKeys = new ArrayList<>();
List<GaenKey> nonFakeKeysDelayed = new ArrayList<>();
for (var key : gaenRequest.getGaenKeys()) {
if (!validationUtils.isValidBase64Key(key.getKeyData())) {
return () -> new ResponseEntity<>("No valid base64 key", HttpStatus.BAD_REQUEST);
Expand All @@ -140,15 +144,27 @@ public GaenController(GAENDataService dataService, FakeKeyService fakeKeyService
}

if (key.getRollingPeriod().equals(0)) {
//currently only android seems to send 0 which can never be valid, since a non used key should not be submitted
//default value according to EN is 144, so just set it to that. If we ever get 0 from iOS we should log it, since
//this should not happen
logger.error("RollingPeriod should NOT be 0, fixing it and using 144");
key.setRollingPeriod(GaenKey.GaenKeyDefaultRollingPeriod);
if (userAgent.toLowerCase().contains("ios")) {
logger.error("Received a rolling period of 0 for an iOS User-Agent");
}

if(delayTodaysKeys) {
// Additionally to delaying keys this feature also make sure rolling period is always set to 144
// to make sure iOS 13.5.x does not ignore the TEK.
key.setRollingPeriod(GaenKey.GaenKeyDefaultRollingPeriod);

var rollingStartNumberDuration = Duration.of(key.getRollingStartNumber(), GaenUnit.TenMinutes).toMillis();
var rollingStartNumberInstant = Instant.ofEpochMilli(rollingStartNumberDuration);
var rollingStartDate = LocalDate.ofInstant(rollingStartNumberInstant, ZoneOffset.UTC);
// If this is a same day TEK we are delaying its release
if(LocalDate.now(ZoneOffset.UTC).isEqual(rollingStartDate)) {
nonFakeKeysDelayed.add(key);
} else {
nonFakeKeys.add(key);
}
} else {
nonFakeKeys.add(key);
}
nonFakeKeys.add(key);
}

if (principal instanceof Jwt && ((Jwt) principal).containsClaim("fake")
Expand All @@ -158,6 +174,15 @@ public GaenController(GAENDataService dataService, FakeKeyService fakeKeyService
if (!nonFakeKeys.isEmpty()) {
dataService.upsertExposees(nonFakeKeys);
}
if (!nonFakeKeysDelayed.isEmpty()) {
// Hold back same day TEKs until 02:00 UTC of the next day (as RPIs are accepted by EN up to 2h after rolling period)
var tomorrowAt2AM = LocalDate.now(ZoneOffset.UTC)
.plusDays(1)
.atStartOfDay(ZoneOffset.UTC)
.plusHours(2)
.toOffsetDateTime();
dataService.upsertExposeesDelayed(nonFakeKeysDelayed, tomorrowAt2AM);
}

var delayedKeyDateDuration = Duration.of(gaenRequest.getDelayedKeyDate(), GaenUnit.TenMinutes);
var delayedKeyDate = LocalDate.ofInstant(Instant.ofEpochMilli(delayedKeyDateDuration.toMillis()),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@
@ActiveProfiles({"actuator-security"})
@SpringBootTest(properties = { "ws.app.jwt.publickey=classpath://generated_pub.pem",
"logging.level.org.springframework.security=DEBUG", "ws.exposedlist.releaseBucketDuration=7200000", "ws.gaen.randomkeysenabled=true",
"ws.app.gaen.delayTodaysKeys=true",
"ws.monitor.prometheus.user=prometheus",
"ws.monitor.prometheus.password=prometheus",
"management.endpoints.enabled-by-default=true",
Expand Down Expand Up @@ -124,10 +125,19 @@ private void testNKeys(int n, boolean shouldSucceed) throws Exception{
gaenKey2.setRollingPeriod(0);
gaenKey2.setFake(0);
gaenKey2.setTransmissionRiskLevel(0);
//third key should be delayed
var gaenKey3 = new GaenKey();
gaenKey3.setRollingStartNumber((int) Duration.ofMillis(LocalDate.now(ZoneOffset.UTC).atStartOfDay(ZoneOffset.UTC).toInstant().toEpochMilli())
.dividedBy(Duration.ofMinutes(10)));
gaenKey3.setKeyData(Base64.getEncoder().encodeToString("testKey32Bytes03".getBytes("UTF-8")));
gaenKey3.setRollingPeriod(120);
gaenKey3.setFake(0);
gaenKey3.setTransmissionRiskLevel(0);
List<GaenKey> exposedKeys = new ArrayList<>();
exposedKeys.add(gaenKey1);
exposedKeys.add(gaenKey2);
for (int i = 0; i < n-2; i++) {
exposedKeys.add(gaenKey3);
for (int i = 0; i < n-3; i++) {
var tmpKey = new GaenKey();
tmpKey.setRollingStartNumber(
(int) Duration.ofMillis(now).dividedBy(Duration.ofMinutes(10)));
Expand Down Expand Up @@ -172,6 +182,11 @@ private void testNKeys(int n, boolean shouldSucceed) throws Exception{

result = gaenDataService.getSortedExposedForKeyDate(LocalDate.now(ZoneOffset.UTC).minusDays(1).atStartOfDay().toInstant(ZoneOffset.UTC).toEpochMilli(),null, (now / releaseBucketDuration)*releaseBucketDuration);
assertEquals(0, result.size());

//third key should be released tomorrow
var tomorrow2AM = LocalDate.now(ZoneOffset.UTC).plusDays(1).atStartOfDay().plusHours(2).plusSeconds(1);
result = gaenDataService.getSortedExposedForKeyDate(LocalDate.now(ZoneOffset.UTC).atStartOfDay().toInstant(ZoneOffset.UTC).toEpochMilli(),null, tomorrow2AM.toInstant(ZoneOffset.UTC).toEpochMilli());
assertEquals(1, result.size());
}

@Test
Expand Down

0 comments on commit 0fab0fb

Please sign in to comment.