{"record":{"id":"e737c6b73683e915","repo":"microg/GmsCore","slug":"rolling-start-interval-number-missing","errorCode":null,"errorMessage":"rolling start interval number missing","messagePattern":"rolling start interval number missing","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"play-services-nearby/core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/ExposureNotificationServiceImpl.kt","lineNumber":249,"sourceCode":"\n            ExposureDatabase.with(context) { database ->\n                database.noteAppAction(packageName, \"getTemporaryExposureKeyHistory\", JSONObject().apply {\n                    put(\"result\", status.statusCode)\n                    put(\"response_keys_size\", response.size)\n                }.toString())\n            }\n            try {\n                params.callback.onResult(status, response)\n            } catch (e: Exception) {\n                Log.w(TAG, \"Callback failed\", e)\n            }\n        }\n    }\n\n    private fun TemporaryExposureKeyProto.toKey(): TemporaryExposureKey = TemporaryExposureKey.TemporaryExposureKeyBuilder()\n            .setKeyData(key_data?.toByteArray() ?: throw IllegalArgumentException(\"key data missing\"))\n            .setRollingStartIntervalNumber(rolling_start_interval_number\n                    ?: throw IllegalArgumentException(\"rolling start interval number missing\"))\n            .setRollingPeriod(rolling_period ?: throw IllegalArgumentException(\"rolling period missing\"))\n            .setTransmissionRiskLevel(transmission_risk_level ?: 0)\n            .build()\n\n    private fun InputStream.copyToFile(outputFile: File) {\n        outputFile.outputStream().use { output ->\n            copyTo(output)\n            output.flush()\n        }\n    }\n\n    private fun MessageDigest.digest(file: File): ByteArray = file.inputStream().use { input ->\n        val buf = ByteArray(4096)\n        var bytes = input.read(buf)\n        while (bytes != -1) {\n            update(buf, 0, bytes)\n            bytes = input.read(buf)\n        }","sourceCodeStart":231,"sourceCodeEnd":267,"githubUrl":"https://github.com/microg/GmsCore/blob/157c9d86ac46c195a86c2f15ab55c84036223f95/play-services-nearby/core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/ExposureNotificationServiceImpl.kt#L231-L267","documentation":"TemporaryExposureKeyProto.toKey() requires rolling_start_interval_number (the 10-minute interval at which the key becomes valid). If the received proto lacks this field, the elvis operator throws IllegalArgumentException 'rolling start interval number missing'. Without it the key's validity window cannot be computed, so the conversion fails.","triggerScenarios":"Parsing an exposure-key export (TEK) proto whose rolling_start_interval_number field is unset; converting diagnosis keys fetched from a server that omitted the field.","commonSituations":"Malformed or spec-noncompliant diagnosis-server exports; partially written key files; test fixtures missing fields; custom key submission clients that build incomplete protos.","solutions":["Reject or repair export entries lacking rolling_start_interval_number before conversion","Re-download the export and verify checksum/signature","Default to a sane value only if your use case allows, otherwise skip the entry","Ensure the diagnosis server publishes spec-compliant exports (all required TEK fields present)"],"exampleFix":"// before\n.setRollingStartIntervalNumber(rolling_start_interval_number ?: throw IllegalArgumentException(\"rolling start interval number missing\"))\n// after\n.setRollingStartIntervalNumber(rolling_start_interval_number ?: Int.MIN_VALUE.also { Log.w(TAG, \"missing rolling start interval, skipping\") }.let { return null })","handlingStrategy":"validation","validationCode":"fun TemporaryExposureKeyProto.hasValidRollingStart(): Boolean =\n    rolling_start_interval_number != null && rolling_start_interval_number > 0","typeGuard":"fun TemporaryExposureKeyProto?.isConvertibleToKey(): Boolean =\n    this != null && key_data?.size() ?: 0 > 0 && rolling_start_interval_number != null && rolling_period != null","tryCatchPattern":"try {\n    val key = proto.toKey()\n} catch (e: IllegalArgumentException) {\n    if (e.message?.contains(\"rolling start interval\") == true) skipEntry(proto)\n    else throw e\n}","preventionTips":["Check rolling_start_interval_number presence before conversion","Validate export files' integrity (signature/checksum) after download","Keep diagnosis-server exports spec-compliant (all TEK fields set)","Skip malformed entries with logging rather than aborting batch import"],"tags":["nearby","exposure-notification","protobuf","validation"],"backgroundTag":"empty-required-field","analyzedSha":"157c9d86ac46c195a86c2f15ab55c84036223f95","analyzedAt":"2026-09-06T17:27:33.892Z","contentChangedAt":"2026-09-06T17:27:33.892Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}