1 /* <lambda>null2 * Copyright (C) 2022 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.libraries.pcc.chronicle.api.integration 18 19 import com.android.libraries.pcc.chronicle.analysis.ChronicleContext 20 import com.android.libraries.pcc.chronicle.analysis.PolicyEngine 21 import com.android.libraries.pcc.chronicle.api.Chronicle 22 import com.android.libraries.pcc.chronicle.api.Connection 23 import com.android.libraries.pcc.chronicle.api.ConnectionRequest 24 import com.android.libraries.pcc.chronicle.api.ConnectionResult 25 import com.android.libraries.pcc.chronicle.api.ReadConnection 26 import com.android.libraries.pcc.chronicle.api.WriteConnection 27 import com.android.libraries.pcc.chronicle.api.error.ChronicleError 28 import com.android.libraries.pcc.chronicle.api.error.ConnectionNotDeclared 29 import com.android.libraries.pcc.chronicle.api.error.ConnectionProviderNotFound 30 import com.android.libraries.pcc.chronicle.api.error.Disabled 31 import com.android.libraries.pcc.chronicle.api.error.PolicyNotFound 32 import com.android.libraries.pcc.chronicle.api.error.PolicyViolation 33 import com.android.libraries.pcc.chronicle.api.flags.FlagsReader 34 import com.android.libraries.pcc.chronicle.api.isReadConnection 35 import com.android.libraries.pcc.chronicle.api.isWriteConnection 36 import com.android.libraries.pcc.chronicle.api.policy.Policy 37 import com.android.libraries.pcc.chronicle.api.policy.PolicyConformanceCheck 38 import com.android.libraries.pcc.chronicle.api.policy.builder.PolicyCheck 39 import com.android.libraries.pcc.chronicle.api.policy.builder.PolicyCheckResult 40 import com.android.libraries.pcc.chronicle.util.Logcat 41 import com.android.libraries.pcc.chronicle.util.TypedMap 42 import kotlin.reflect.KClass 43 import kotlinx.atomicfu.atomic 44 import kotlinx.atomicfu.update 45 46 /** Default implementation of [Chronicle]. */ 47 class DefaultChronicle( 48 chronicleContext: ChronicleContext, 49 private val policyEngine: PolicyEngine, 50 private val config: Config, 51 private val flags: FlagsReader, 52 ) : Chronicle { 53 private val context = atomic(chronicleContext) 54 55 init { 56 config.policyConformanceCheck.checkPoliciesConform(chronicleContext.policySet.toSet()) 57 58 chronicleContext.connectionProviders.forEach { 59 // Log if not null 60 it::class.qualifiedName?.let { qualifiedName -> 61 logger.d("ConnectionProvider: %s", qualifiedName) 62 } 63 } 64 65 // Verify that all provided write connections match all associated policies, and that they all 66 // are mentioned in at least one policy. 67 val writeConnectionCheckResult = policyEngine.checkWriteConnections(context.value) 68 if (writeConnectionCheckResult is PolicyCheckResult.Fail) { 69 handlePolicyCheckFail(writeConnectionCheckResult)?.let { throw it } 70 } 71 } 72 73 @Suppress("UNCHECKED_CAST") // Checks happen, just not in a way that is compiler-verifiable. 74 override fun getAvailableConnectionTypes(dataTypeClass: KClass<*>): Chronicle.ConnectionTypes { 75 val context = context.value 76 77 val dtd = 78 context.dataTypeDescriptorSet.findDataTypeDescriptor(dataTypeClass) 79 ?: return Chronicle.ConnectionTypes.EMPTY 80 81 return context.connectionProviders 82 .filter { it.dataType.descriptor == dtd } 83 .fold(Chronicle.ConnectionTypes.EMPTY) { acc, provider -> 84 val readConnections = mutableSetOf<Class<out ReadConnection>>() 85 val writeConnections = mutableSetOf<Class<out WriteConnection>>() 86 provider.dataType.connectionTypes.forEach { connectionClass -> 87 if (connectionClass.isReadConnection) { 88 readConnections.add(connectionClass as Class<out ReadConnection>) 89 } 90 if (connectionClass.isWriteConnection) { 91 writeConnections.add(connectionClass as Class<out WriteConnection>) 92 } 93 } 94 95 Chronicle.ConnectionTypes( 96 readConnections = acc.readConnections + readConnections, 97 writeConnections = acc.writeConnections + writeConnections 98 ) 99 } 100 } 101 102 override fun <T : Connection> getConnection(request: ConnectionRequest<T>): ConnectionResult<T> = 103 logger.timeVerbose("ChronicleImpl.getConnection($request)") { getConnectionInner(request) } 104 105 private fun <T : Connection> getConnectionInner( 106 request: ConnectionRequest<T> 107 ): ConnectionResult<T> { 108 if (flags.config.value.failNewConnections) { 109 return ConnectionResult.Failure(Disabled("Chronicle disabled via flags.")) 110 } 111 112 if (!request.requester.requiredConnectionTypes.contains(request.connectionType)) { 113 logger.d( 114 "Connection is not declared as required in `ProcessorNode` of a request: %s", 115 request 116 ) 117 return ConnectionResult.Failure(ConnectionNotDeclared(request)) 118 } 119 120 val currentContext = context.value 121 val connectionProvider = 122 currentContext.findConnectionProvider(request.connectionType) 123 ?: return ConnectionResult.Failure(ConnectionProviderNotFound(request)) 124 125 val policy = request.policy 126 if (policy != null && policy !in currentContext.policySet) { 127 handlePolicyCheckException(PolicyNotFound(policy))?.let { 128 return ConnectionResult.Failure(it) 129 } 130 } 131 if (policy == null && request.connectionType.isReadConnection) { 132 handlePolicyCheckFail( 133 PolicyCheckResult.Fail( 134 listOf( 135 PolicyCheck("ConnectionRequest.policy must be non-null for ReadConnection requests") 136 ) 137 ) 138 ) 139 ?.let { 140 return ConnectionResult.Failure(it) 141 } 142 } 143 144 // Add to the graph and check the policy within an atomic update to the graph. 145 context.update { existing -> 146 val updated = existing.withNode(request.requester) 147 148 if (policy != null && request.connectionType.isReadConnection) { 149 val checkResult = policyEngine.checkPolicy(policy, request, updated) 150 if (checkResult is PolicyCheckResult.Fail) { 151 handlePolicyCheckFail(checkResult)?.let { 152 return ConnectionResult.Failure(it) 153 } 154 } 155 } 156 157 updated 158 } 159 160 return ConnectionResult.Success(connectionProvider.getTypedConnection(request)) 161 } 162 163 private fun handlePolicyCheckFail(failure: PolicyCheckResult.Fail): ChronicleError? = 164 handlePolicyCheckException(PolicyViolation(failure.message)) 165 166 private fun handlePolicyCheckException(error: ChronicleError): ChronicleError? { 167 return when (config.policyMode) { 168 Config.PolicyMode.STRICT -> error 169 Config.PolicyMode.LOG -> { 170 error.message?.let { logger.e("Policy violation detected: %s", it) } 171 null 172 } 173 } 174 } 175 176 /** 177 * Allows [Chronicle] to use a new `connectionContext` by updating the [ChronicleContext]. 178 * Subsequent [Policy] checking will use the updated context when checking the `allowedContext`. 179 */ 180 fun updateConnectionContext(updatedContext: TypedMap) { 181 context.update { existing -> existing.withConnectionContext(updatedContext) } 182 } 183 184 /** A set of configuration values that can be cased to [DefaultChronicle] at construction time. */ 185 data class Config( 186 /** Configure the behavior for policy check failures. */ 187 val policyMode: PolicyMode, 188 189 /** 190 * Set of rules to apply to guarantee that all [Policies][Policy] provided to Chronicle meet a 191 * standard of quality. 192 */ 193 val policyConformanceCheck: PolicyConformanceCheck 194 ) { 195 enum class PolicyMode { 196 /** Policy failure will be logged, but the connection request will succeed. */ 197 LOG, 198 199 /** Policy failure will result in failure result being returned. */ 200 STRICT 201 } 202 } 203 204 companion object { 205 private val logger = Logcat.default 206 } 207 } 208