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