{"record":{"id":"c01f895189584589","repo":"microg/GmsCore","slug":"key-data-missing","errorCode":null,"errorMessage":"key data missing","messagePattern":"key data 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":247,"sourceCode":"                else -> emptyList()\n            }\n\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)","sourceCodeStart":229,"sourceCodeEnd":265,"githubUrl":"https://github.com/microg/GmsCore/blob/157c9d86ac46c195a86c2f15ab55c84036223f95/play-services-nearby/core/src/main/kotlin/org/microg/gms/nearby/exposurenotification/ExposureNotificationServiceImpl.kt#L229-L265","documentation":"TemporaryExposureKeyProto.toKey() converts a protobuf-encoded exposure key into the API TemporaryExposureKey object. The key_data field is mandatory; when the proto was received without key data, the elvis operator throws IllegalArgumentException 'key data missing' instead of building a key with null/empty key material.","triggerScenarios":"A key file (ZIP of exported exposure keys) contains a TemporaryExposureKey proto entry with key_data unset or empty; a malformed export is downloaded and parsed, then toKey() is invoked on each entry.","commonSituations":"Corrupt or truncated exposure-key export files; keys downloaded over a flaky connection and partially written; manually constructed or test protos missing key_data; mismatched export format versions.","solutions":["Validate each proto entry's key_data is non-null/32 bytes before calling toKey","Re-download the exposure key export file and verify its signature/checksum","Skip or log entries with missing key_data instead of propagating the exception","Verify the export was fully downloaded (file size / completion) before parsing"],"exampleFix":"// before\nprivate fun TemporaryExposureKeyProto.toKey() = Builder().setKeyData(key_data?.toByteArray() ?: throw IllegalArgumentException(\"key data missing\"))...\n// after\nfun TemporaryExposureKeyProto.toKeyOrNull(): TemporaryExposureKey? {\n    val data = key_data?.toByteArray() ?: return null\n    return TemporaryExposureKey.TemporaryExposureKeyBuilder().setKeyData(data)\n        .setRollingStartIntervalNumber(rolling_start_interval_number ?: return null)\n        .setRollingPeriod(rolling_period ?: return null)\n        .setTransmissionRiskLevel(transmission_risk_level ?: 0).build()\n}","handlingStrategy":"validation","validationCode":"fun TemporaryExposureKeyProto.hasRequiredFields(): Boolean =\n    key_data != null && key_data.size() > 0 &&\n    rolling_start_interval_number != null &&\n    rolling_period != null","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    use(key)\n} catch (e: IllegalArgumentException) {\n    Log.w(TAG, \"Skipping malformed exposure key: ${e.message}\")\n}","preventionTips":["Validate export ZIP entries before converting (required fields present, key_data 16/32 bytes)","Verify export file signature and checksum before parsing","Skip-and-log malformed entries instead of failing the whole batch","Ensure the diagnosis server emits spec-compliant ExposureKey exports"],"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"}