• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2025 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 android.os.flagging;
18 
19 import android.annotation.NonNull;
20 import android.annotation.Nullable;
21 import android.internal.configinfra.aconfigd.x.Aconfigd.StorageRequestMessage;
22 import android.internal.configinfra.aconfigd.x.Aconfigd.StorageRequestMessages;
23 import android.internal.configinfra.aconfigd.x.Aconfigd.StorageReturnMessage;
24 import android.internal.configinfra.aconfigd.x.Aconfigd.StorageReturnMessages;
25 import android.net.LocalSocket;
26 import android.net.LocalSocketAddress;
27 import android.util.Slog;
28 import android.util.configinfrastructure.proto.ProtoInputStream;
29 import android.util.proto.ProtoOutputStream;
30 
31 import java.io.DataInputStream;
32 import java.io.DataOutputStream;
33 import java.io.IOException;
34 import java.io.InputStream;
35 import java.util.HashSet;
36 import java.util.Map;
37 import java.util.Set;
38 
39 /**
40  * Writes messages to aconfigd, and parses responses.
41  *
42  * <p>Uses ProtoInputStream, rather than a proto lite lib.
43  *
44  * @hide
45  */
46 public final class AconfigdProtoStreamer {
47     private static final String SYSTEM_SOCKET_ADDRESS = "aconfigd_system";
48     private static final String MAINLINE_SOCKET_ADDRESS = "aconfigd_mainline";
49 
50     private static final String TAG = "FlagManager";
51 
52     /**
53      * Create a new AconfigdProtoStreamer.
54      *
55      * @hide
56      */
AconfigdProtoStreamer()57     public AconfigdProtoStreamer() {}
58 
59     /**
60      * Send override removal requests to aconfigd.
61      *
62      * @param flags the set of flag names to remove local overrides for
63      * @param removeType the type of removal: immediately, or on reboot
64      * @hide
65      */
sendClearFlagOverrideRequests(@onNull Set<String> flags, long removeType)66     public void sendClearFlagOverrideRequests(@NonNull Set<String> flags, long removeType)
67             throws IOException {
68         ProtoOutputStream requestOutputStream = new ProtoOutputStream();
69         for (Flag flag : Flag.buildFlagsWithoutValues(flags)) {
70             long msgsToken = requestOutputStream.start(StorageRequestMessages.MSGS);
71             long msgToken =
72                     requestOutputStream.start(StorageRequestMessage.REMOVE_LOCAL_OVERRIDE_MESSAGE);
73             requestOutputStream.write(
74                     StorageRequestMessage.RemoveLocalOverrideMessage.PACKAGE_NAME,
75                     flag.packageName);
76             requestOutputStream.write(
77                     StorageRequestMessage.RemoveLocalOverrideMessage.FLAG_NAME, flag.flagName);
78             requestOutputStream.write(
79                     StorageRequestMessage.RemoveLocalOverrideMessage.REMOVE_ALL, false);
80             requestOutputStream.write(
81                     StorageRequestMessage.RemoveLocalOverrideMessage.REMOVE_OVERRIDE_TYPE,
82                     removeType);
83             requestOutputStream.end(msgToken);
84             requestOutputStream.end(msgsToken);
85         }
86 
87         sendBytesAndParseResponse(
88                 requestOutputStream.getBytes(), StorageReturnMessage.REMOVE_LOCAL_OVERRIDE_MESSAGE);
89     }
90 
91     /**
92      * Send OTA flag-staging requests to aconfigd.
93      *
94      * @param flags a map from flag names to the values that will be staged
95      * @param buildFingerprint the build fingerprint on which the values will be un-staged
96      * @hide
97      */
sendOtaFlagOverrideRequests( @onNull Map<String, Boolean> flags, @NonNull String buildFingerprint)98     public void sendOtaFlagOverrideRequests(
99             @NonNull Map<String, Boolean> flags, @NonNull String buildFingerprint)
100             throws IOException {
101         ProtoOutputStream requestOutputStream = new ProtoOutputStream();
102         long msgsToken = requestOutputStream.start(StorageRequestMessages.MSGS);
103         long msgToken = requestOutputStream.start(StorageRequestMessage.OTA_STAGING_MESSAGE);
104         requestOutputStream.write(
105                 StorageRequestMessage.OTAFlagStagingMessage.BUILD_ID, buildFingerprint);
106         for (Flag flag : Flag.buildFlags(flags)) {
107             long flagOverrideMsgToken =
108                     requestOutputStream.start(StorageRequestMessage.FLAG_OVERRIDE_MESSAGE);
109             requestOutputStream.write(
110                     StorageRequestMessage.FlagOverrideMessage.PACKAGE_NAME, flag.packageName);
111             requestOutputStream.write(
112                     StorageRequestMessage.FlagOverrideMessage.FLAG_NAME, flag.flagName);
113             requestOutputStream.write(
114                     StorageRequestMessage.FlagOverrideMessage.FLAG_VALUE, flag.value);
115             requestOutputStream.end(flagOverrideMsgToken);
116         }
117         requestOutputStream.end(msgToken);
118         requestOutputStream.end(msgsToken);
119 
120         sendBytesAndParseResponse(
121                 requestOutputStream.getBytes(), StorageReturnMessage.OTA_STAGING_MESSAGE);
122     }
123 
124     /**
125      * Send flag override requests to aconfigd, and parse the response.
126      *
127      * @hide
128      */
sendFlagOverrideRequests(@onNull Map<String, Boolean> flags, long overrideType)129     public void sendFlagOverrideRequests(@NonNull Map<String, Boolean> flags, long overrideType)
130             throws IOException {
131         ProtoOutputStream requestOutputStream = new ProtoOutputStream();
132         for (Flag flag : Flag.buildFlags(flags)) {
133             long msgsToken = requestOutputStream.start(StorageRequestMessages.MSGS);
134             long msgToken = requestOutputStream.start(StorageRequestMessage.FLAG_OVERRIDE_MESSAGE);
135             requestOutputStream.write(
136                     StorageRequestMessage.FlagOverrideMessage.PACKAGE_NAME, flag.packageName);
137             requestOutputStream.write(
138                     StorageRequestMessage.FlagOverrideMessage.FLAG_NAME, flag.flagName);
139             requestOutputStream.write(
140                     StorageRequestMessage.FlagOverrideMessage.FLAG_VALUE, flag.value);
141             requestOutputStream.write(
142                     StorageRequestMessage.FlagOverrideMessage.OVERRIDE_TYPE, overrideType);
143             requestOutputStream.end(msgToken);
144             requestOutputStream.end(msgsToken);
145         }
146 
147         sendBytesAndParseResponse(
148                 requestOutputStream.getBytes(), StorageReturnMessage.FLAG_OVERRIDE_MESSAGE);
149     }
150 
sendBytesAndParseResponse(byte[] requestBytes, long responseMessageToken)151     private void sendBytesAndParseResponse(byte[] requestBytes, long responseMessageToken)
152             throws IOException {
153         try {
154             LocalSocket systemSocket = new LocalSocket();
155             LocalSocketAddress systemAddress =
156                     new LocalSocketAddress(
157                             SYSTEM_SOCKET_ADDRESS, LocalSocketAddress.Namespace.RESERVED);
158             if (!systemSocket.isConnected()) {
159                 systemSocket.connect(systemAddress);
160             }
161 
162             InputStream inputStream = sendBytesOverSocket(requestBytes, systemSocket);
163             parseAconfigdResponse(inputStream, responseMessageToken);
164 
165             systemSocket.shutdownInput();
166             systemSocket.shutdownOutput();
167             systemSocket.close();
168 
169         } catch (IOException systemException) {
170             Slog.i(
171                     TAG,
172                     "failed to send request to system socket; trying mainline socket",
173                     systemException);
174 
175             LocalSocket mainlineSocket = new LocalSocket();
176             LocalSocketAddress mainlineAddress =
177                     new LocalSocketAddress(
178                             MAINLINE_SOCKET_ADDRESS, LocalSocketAddress.Namespace.RESERVED);
179             if (!mainlineSocket.isConnected()) {
180                 mainlineSocket.connect(mainlineAddress);
181             }
182 
183             InputStream inputStream = sendBytesOverSocket(requestBytes, mainlineSocket);
184             parseAconfigdResponse(inputStream, responseMessageToken);
185 
186             mainlineSocket.shutdownInput();
187             mainlineSocket.shutdownOutput();
188             mainlineSocket.close();
189         }
190     }
191 
sendBytesOverSocket(byte[] requestBytes, LocalSocket socket)192     private InputStream sendBytesOverSocket(byte[] requestBytes, LocalSocket socket)
193             throws IOException {
194         InputStream responseInputStream = null;
195 
196         DataInputStream inputStream = new DataInputStream(socket.getInputStream());
197         DataOutputStream outputStream = new DataOutputStream(socket.getOutputStream());
198         outputStream.writeInt(requestBytes.length);
199         outputStream.write(requestBytes);
200         responseInputStream = socket.getInputStream();
201 
202         return responseInputStream;
203     }
204 
parseAconfigdResponse(InputStream inputStream, long responseMessageToken)205     private void parseAconfigdResponse(InputStream inputStream, long responseMessageToken)
206             throws IOException {
207         ProtoInputStream proto = new ProtoInputStream(inputStream);
208         while (true) {
209             long currentToken = proto.nextField();
210 
211             if (currentToken == ProtoInputStream.NO_MORE_FIELDS) {
212                 return;
213             }
214 
215             if (currentToken != ((int) StorageReturnMessages.MSGS)) {
216                 continue;
217             }
218 
219             long msgsToken = proto.start(StorageReturnMessages.MSGS);
220             long nextToken = proto.nextField();
221 
222             if (nextToken == ((int) responseMessageToken)) {
223                 long msgToken = proto.start(responseMessageToken);
224                 proto.end(msgToken);
225             } else if (nextToken == ((int) StorageReturnMessage.ERROR_MESSAGE)) {
226                 String errmsg = proto.readString(StorageReturnMessage.ERROR_MESSAGE);
227                 throw new IOException("override request failed: " + errmsg);
228             } else if (nextToken == ProtoInputStream.NO_MORE_FIELDS) {
229                 // Do nothing.
230             } else {
231                 throw new IOException(
232                         "invalid message type, expecting only return message"
233                                 + " or error message");
234             }
235 
236             proto.end(msgsToken);
237         }
238     }
239 
240     private static class Flag {
241         public final String packageName;
242         public final String flagName;
243         public final String value;
244 
Flag( @onNull String packageName, @NonNull String flagName, @Nullable Boolean value)245         public Flag(
246                 @NonNull String packageName, @NonNull String flagName, @Nullable Boolean value) {
247             this.packageName = packageName;
248             this.flagName = flagName;
249 
250             if (value != null) {
251                 this.value = Boolean.toString(value);
252             } else {
253                 this.value = null;
254             }
255         }
256 
buildFlags(@onNull Map<String, Boolean> flags)257         public static Set<Flag> buildFlags(@NonNull Map<String, Boolean> flags) {
258             HashSet<Flag> flagSet = new HashSet();
259             for (Map.Entry<String, Boolean> flagAndValue : flags.entrySet()) {
260                 String packageName = "";
261                 String flagName = flagAndValue.getKey();
262 
263                 int periodIndex = flagName.lastIndexOf(".");
264                 if (periodIndex != -1) {
265                     packageName = flagName.substring(0, flagName.lastIndexOf("."));
266                     flagName = flagName.substring(flagName.lastIndexOf(".") + 1);
267                 }
268 
269                 flagSet.add(new Flag(packageName, flagName, flagAndValue.getValue()));
270             }
271             return flagSet;
272         }
273 
buildFlagsWithoutValues(@onNull Set<String> flags)274         public static Set<Flag> buildFlagsWithoutValues(@NonNull Set<String> flags) {
275             HashSet<Flag> flagSet = new HashSet();
276             for (String qualifiedFlagName : flags) {
277                 String packageName = "";
278                 String flagName = qualifiedFlagName;
279                 int periodIndex = qualifiedFlagName.lastIndexOf(".");
280                 if (periodIndex != -1) {
281                     packageName =
282                             qualifiedFlagName.substring(0, qualifiedFlagName.lastIndexOf("."));
283                     flagName = qualifiedFlagName.substring(qualifiedFlagName.lastIndexOf(".") + 1);
284                 }
285 
286                 flagSet.add(new Flag(packageName, flagName, null));
287             }
288             return flagSet;
289         }
290     }
291 }
292