v4 digest tree streaming

Framework part. Preparation for adb.

Bug: b/152050621
Test: atest PackageManagerShellCommandTest PackageManagerShellCommandIncrementalTest
Test: adb install --incremental megacity.apk
Change-Id: I41838c3ded5c4dc1efcc1ad91930864bd7e6d8d4
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index 8a9f1b3..4d49a64 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -105,6 +105,7 @@
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.server.LocalServices;
 import com.android.server.SystemConfig;
+import com.android.server.pm.PackageManagerShellCommandDataLoader.Metadata;
 
 import dalvik.system.DexFile;
 
@@ -118,7 +119,6 @@
 import java.io.OutputStream;
 import java.io.PrintWriter;
 import java.net.URISyntaxException;
-import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
 import java.util.Base64;
 import java.util.Collection;
@@ -3025,9 +3025,9 @@
             // 1. Single file from stdin.
             if (args.isEmpty() || STDIN_PATH.equals(args.get(0))) {
                 final String name = "base." + (isApex ? "apex" : "apk");
-                final String metadata = "-" + name;
+                final Metadata metadata = Metadata.forStdIn(name);
                 session.addFile(LOCATION_DATA_APP, name, sessionSizeBytes,
-                        metadata.getBytes(StandardCharsets.UTF_8), null);
+                        metadata.toByteArray(), null);
                 return 0;
             }
 
@@ -3056,9 +3056,10 @@
 
     private int processArgForStdin(String arg, PackageInstaller.Session session) {
         final String[] fileDesc = arg.split(":");
-        String name, metadata;
+        String name, fileId;
         long sizeBytes;
         byte[] signature = null;
+        int streamingVersion = 0;
 
         try {
             if (fileDesc.length < 2) {
@@ -3067,14 +3068,22 @@
             }
             name = fileDesc[0];
             sizeBytes = Long.parseUnsignedLong(fileDesc[1]);
-            metadata = name;
+            fileId = name;
 
             if (fileDesc.length > 2 && !TextUtils.isEmpty(fileDesc[2])) {
-                metadata = fileDesc[2];
+                fileId = fileDesc[2];
             }
             if (fileDesc.length > 3) {
                 signature = Base64.getDecoder().decode(fileDesc[3]);
             }
+            if (fileDesc.length > 4) {
+                streamingVersion = Integer.parseUnsignedInt(fileDesc[4]);
+                if (streamingVersion < 0 || streamingVersion > 1) {
+                    getErrPrintWriter().println(
+                            "Unsupported streaming version: " + streamingVersion);
+                    return 1;
+                }
+            }
         } catch (IllegalArgumentException e) {
             getErrPrintWriter().println(
                     "Unable to parse file parameters: " + arg + ", reason: " + e);
@@ -3086,9 +3095,14 @@
             return 1;
         }
 
+        final Metadata metadata;
+
         if (signature != null) {
-            // Streaming/adb mode.
-            metadata = "+" + metadata;
+            // Streaming/adb mode. Versions:
+            // 0: data only streaming, tree has to be fully available,
+            // 1: tree and data streaming.
+            metadata = (streamingVersion == 0) ? Metadata.forDataOnlyStreaming(fileId)
+                    : Metadata.forStreaming(fileId);
             try {
                 if (V4Signature.readFrom(signature) == null) {
                     getErrPrintWriter().println("V4 signature is invalid in: " + arg);
@@ -3101,11 +3115,10 @@
             }
         } else {
             // Single-shot read from stdin.
-            metadata = "-" + metadata;
+            metadata = Metadata.forStdIn(fileId);
         }
 
-        session.addFile(LOCATION_DATA_APP, name, sizeBytes,
-                metadata.getBytes(StandardCharsets.UTF_8), signature);
+        session.addFile(LOCATION_DATA_APP, name, sizeBytes, metadata.toByteArray(), signature);
         return 0;
     }
 
@@ -3115,7 +3128,7 @@
         final File file = new File(inPath);
         final String name = file.getName();
         final long size = file.length();
-        final byte[] metadata = inPath.getBytes(StandardCharsets.UTF_8);
+        final Metadata metadata = Metadata.forLocalFile(inPath);
 
         byte[] v4signatureBytes = null;
         // Try to load the v4 signature file for the APK; it might not exist.
@@ -3132,7 +3145,7 @@
             }
         }
 
-        session.addFile(LOCATION_DATA_APP, name, size, metadata, v4signatureBytes);
+        session.addFile(LOCATION_DATA_APP, name, size, metadata.toByteArray(), v4signatureBytes);
     }
 
     private int doWriteSplits(int sessionId, ArrayList<String> splitPaths, long sessionSizeBytes,
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommandDataLoader.java b/services/core/java/com/android/server/pm/PackageManagerShellCommandDataLoader.java
index 6d83d70..2aa6e573 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommandDataLoader.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommandDataLoader.java
@@ -24,7 +24,6 @@
 import android.os.ParcelFileDescriptor;
 import android.os.ShellCommand;
 import android.service.dataloader.DataLoaderService;
-import android.text.TextUtils;
 import android.util.Slog;
 import android.util.SparseArray;
 
@@ -114,6 +113,74 @@
         }
     }
 
+    static class Metadata {
+        /**
+         * Full files read from stdin.
+         */
+        static final byte STDIN = 0;
+        /**
+         * Full files read from local file.
+         */
+        static final byte LOCAL_FILE = 1;
+        /**
+         * Signature tree read from stdin, data streamed.
+         */
+        static final byte DATA_ONLY_STREAMING = 2;
+        /**
+         * Everything streamed.
+         */
+        static final byte STREAMING = 3;
+
+        private final byte mMode;
+        private final String mData;
+
+        static Metadata forStdIn(String fileId) {
+            return new Metadata(STDIN, fileId);
+        }
+
+        static Metadata forLocalFile(String filePath) {
+            return new Metadata(LOCAL_FILE, filePath);
+        }
+
+        static Metadata forDataOnlyStreaming(String fileId) {
+            return new Metadata(DATA_ONLY_STREAMING, fileId);
+        }
+
+        static Metadata forStreaming(String fileId) {
+            return new Metadata(STREAMING, fileId);
+        }
+
+        private Metadata(byte mode, String data) {
+            this.mMode = mode;
+            this.mData = (data == null) ? "" : data;
+        }
+
+        static Metadata fromByteArray(byte[] bytes) throws IOException {
+            if (bytes == null || bytes.length == 0) {
+                return null;
+            }
+            byte mode = bytes[0];
+            String data = new String(bytes, 1, bytes.length - 1, StandardCharsets.UTF_8);
+            return new Metadata(mode, data);
+        }
+
+        byte[] toByteArray() {
+            byte[] dataBytes = this.mData.getBytes(StandardCharsets.UTF_8);
+            byte[] result = new byte[1 + dataBytes.length];
+            result[0] = this.mMode;
+            System.arraycopy(dataBytes, 0, result, 1, dataBytes.length);
+            return result;
+        }
+
+        byte getMode() {
+            return this.mMode;
+        }
+
+        String getData() {
+            return this.mData;
+        }
+    }
+
     private static class DataLoader implements DataLoaderService.DataLoader {
         private DataLoaderParams mParams = null;
         private FileSystemConnector mConnector = null;
@@ -136,19 +203,31 @@
             }
             try {
                 for (InstallationFile file : addedFiles) {
-                    String filePath = new String(file.getMetadata(), StandardCharsets.UTF_8);
-                    if (TextUtils.isEmpty(filePath) || filePath.startsWith(STDIN_PATH)) {
-                        final ParcelFileDescriptor inFd = getStdInPFD(shellCommand);
-                        mConnector.writeData(file.getName(), 0, file.getLengthBytes(), inFd);
-                    } else {
-                        ParcelFileDescriptor incomingFd = null;
-                        try {
-                            incomingFd = getLocalFile(shellCommand, filePath);
-                            mConnector.writeData(file.getName(), 0, incomingFd.getStatSize(),
-                                    incomingFd);
-                        } finally {
-                            IoUtils.closeQuietly(incomingFd);
+                    Metadata metadata = Metadata.fromByteArray(file.getMetadata());
+                    if (metadata == null) {
+                        Slog.e(TAG, "Invalid metadata for file: " + file.getName());
+                        return false;
+                    }
+                    switch (metadata.getMode()) {
+                        case Metadata.STDIN: {
+                            final ParcelFileDescriptor inFd = getStdInPFD(shellCommand);
+                            mConnector.writeData(file.getName(), 0, file.getLengthBytes(), inFd);
+                            break;
                         }
+                        case Metadata.LOCAL_FILE: {
+                            ParcelFileDescriptor incomingFd = null;
+                            try {
+                                incomingFd = getLocalFile(shellCommand, metadata.getData());
+                                mConnector.writeData(file.getName(), 0, incomingFd.getStatSize(),
+                                        incomingFd);
+                            } finally {
+                                IoUtils.closeQuietly(incomingFd);
+                            }
+                            break;
+                        }
+                        default:
+                            Slog.e(TAG, "Unsupported metadata mode: " + metadata.getMode());
+                            return false;
                     }
                 }
                 return true;
diff --git a/services/core/jni/com_android_server_pm_PackageManagerShellCommandDataLoader.cpp b/services/core/jni/com_android_server_pm_PackageManagerShellCommandDataLoader.cpp
index 725036c..e9a5e58 100644
--- a/services/core/jni/com_android_server_pm_PackageManagerShellCommandDataLoader.cpp
+++ b/services/core/jni/com_android_server_pm_PackageManagerShellCommandDataLoader.cpp
@@ -227,56 +227,40 @@
     return result;
 }
 
+enum MetadataMode : int8_t {
+    STDIN = 0,
+    LOCAL_FILE = 1,
+    DATA_ONLY_STREAMING = 2,
+    STREAMING = 3,
+};
+
 struct InputDesc {
     unique_fd fd;
     IncFsSize size;
     IncFsBlockKind kind = INCFS_BLOCK_KIND_DATA;
     bool waitOnEof = false;
     bool streaming = false;
+    MetadataMode mode = STDIN;
 };
 using InputDescs = std::vector<InputDesc>;
 
-static inline InputDescs openInputs(JNIEnv* env, const JniIds& jni, jobject shellCommand,
-                                    IncFsSize size, IncFsSpan metadata) {
+template <class T>
+std::optional<T> read(IncFsSpan& data) {
+    if (data.size < (int32_t)sizeof(T)) {
+        return {};
+    }
+    T res;
+    memcpy(&res, data.data, sizeof(res));
+    data.data += sizeof(res);
+    data.size -= sizeof(res);
+    return res;
+}
+
+static inline InputDescs openLocalFile(JNIEnv* env, const JniIds& jni, jobject shellCommand,
+                                       IncFsSize size, const std::string& filePath) {
     InputDescs result;
     result.reserve(2);
 
-    if (metadata.size == 0 || *metadata.data == '-') {
-        // stdin
-        auto fd = convertPfdToFdAndDup(
-                env, jni,
-                env->CallStaticObjectMethod(jni.packageManagerShellCommandDataLoader,
-                                            jni.pmscdGetStdInPFD, shellCommand));
-        if (fd.ok()) {
-            result.push_back(InputDesc{
-                    .fd = std::move(fd),
-                    .size = size,
-                    .waitOnEof = true,
-            });
-        }
-        return result;
-    }
-    if (*metadata.data == '+') {
-        // verity tree from stdin, rest is streaming
-        auto fd = convertPfdToFdAndDup(
-                env, jni,
-                env->CallStaticObjectMethod(jni.packageManagerShellCommandDataLoader,
-                                            jni.pmscdGetStdInPFD, shellCommand));
-        if (fd.ok()) {
-            auto treeSize = verityTreeSizeForFile(size);
-            result.push_back(InputDesc{
-                    .fd = std::move(fd),
-                    .size = treeSize,
-                    .kind = INCFS_BLOCK_KIND_HASH,
-                    .waitOnEof = true,
-                    .streaming = true,
-            });
-        }
-        return result;
-    }
-
-    // local file and possibly signature
-    const std::string filePath(metadata.data, metadata.size);
     const std::string idsigPath = filePath + ".idsig";
 
     auto idsigFd = convertPfdToFdAndDup(
@@ -314,6 +298,59 @@
     return result;
 }
 
+static inline InputDescs openInputs(JNIEnv* env, const JniIds& jni, jobject shellCommand,
+                                    IncFsSize size, IncFsSpan metadata) {
+    auto mode = read<int8_t>(metadata).value_or(STDIN);
+    if (mode == LOCAL_FILE) {
+        // local file and possibly signature
+        return openLocalFile(env, jni, shellCommand, size,
+                             std::string(metadata.data, metadata.size));
+    }
+
+    auto fd = convertPfdToFdAndDup(
+            env, jni,
+            env->CallStaticObjectMethod(jni.packageManagerShellCommandDataLoader,
+                                        jni.pmscdGetStdInPFD, shellCommand));
+    if (!fd.ok()) {
+        return {};
+    }
+
+    InputDescs result;
+    switch (mode) {
+        case STDIN: {
+            result.push_back(InputDesc{
+                    .fd = std::move(fd),
+                    .size = size,
+                    .waitOnEof = true,
+            });
+            break;
+        }
+        case DATA_ONLY_STREAMING: {
+            // verity tree from stdin, rest is streaming
+            auto treeSize = verityTreeSizeForFile(size);
+            result.push_back(InputDesc{
+                    .fd = std::move(fd),
+                    .size = treeSize,
+                    .kind = INCFS_BLOCK_KIND_HASH,
+                    .waitOnEof = true,
+                    .streaming = true,
+                    .mode = DATA_ONLY_STREAMING,
+            });
+            break;
+        }
+        case STREAMING: {
+            result.push_back(InputDesc{
+                    .fd = std::move(fd),
+                    .size = 0,
+                    .streaming = true,
+                    .mode = STREAMING,
+            });
+            break;
+        }
+    }
+    return result;
+}
+
 static inline JNIEnv* GetJNIEnvironment(JavaVM* vm) {
     JNIEnv* env;
     if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
@@ -390,6 +427,7 @@
         blocks.reserve(BLOCKS_COUNT);
 
         unique_fd streamingFd;
+        MetadataMode streamingMode;
         for (auto&& file : addedFiles) {
             auto inputs = openInputs(env, jni, shellCommand, file.size, file.metadata);
             if (inputs.empty()) {
@@ -411,6 +449,7 @@
             for (auto&& input : inputs) {
                 if (input.streaming && !streamingFd.ok()) {
                     streamingFd.reset(dup(input.fd));
+                    streamingMode = input.mode;
                 }
                 if (!copyToIncFs(incfsFd, input.size, input.kind, input.fd, input.waitOnEof,
                                  &buffer, &blocks)) {
@@ -425,7 +464,7 @@
 
         if (streamingFd.ok()) {
             ALOGE("onPrepareImage: done, proceeding to streaming.");
-            return initStreaming(std::move(streamingFd));
+            return initStreaming(std::move(streamingFd), streamingMode);
         }
 
         ALOGE("onPrepareImage: done.");
@@ -564,7 +603,7 @@
     }
 
     // Streaming.
-    bool initStreaming(unique_fd inout) {
+    bool initStreaming(unique_fd inout, MetadataMode mode) {
         mEventFd.reset(eventfd(0, EFD_CLOEXEC));
         if (mEventFd < 0) {
             ALOGE("Failed to create eventfd.");
@@ -591,8 +630,8 @@
             }
         }
 
-        mReceiverThread =
-                std::thread([this, io = std::move(inout)]() mutable { receiver(std::move(io)); });
+        mReceiverThread = std::thread(
+                [this, io = std::move(inout), mode]() mutable { receiver(std::move(io), mode); });
         ALOGI("Started streaming...");
         return true;
     }
@@ -624,7 +663,7 @@
         }
     }
 
-    void receiver(unique_fd inout) {
+    void receiver(unique_fd inout, MetadataMode mode) {
         std::vector<uint8_t> data;
         std::vector<IncFsDataBlock> instructions;
         std::unordered_map<FileIdx, unique_fd> writeFds;
@@ -667,7 +706,7 @@
                     break;
                 }
                 const FileIdx fileIdx = header.fileIdx;
-                const android::dataloader::FileId fileId = convertFileIndexToFileId(fileIdx);
+                const android::dataloader::FileId fileId = convertFileIndexToFileId(mode, fileIdx);
                 if (!android::incfs::isValidFileId(fileId)) {
                     ALOGE("Unknown data destination for file ID %d. "
                           "Ignore.",
@@ -679,7 +718,7 @@
                 if (writeFd < 0) {
                     writeFd.reset(this->mIfs->openWrite(fileId));
                     if (writeFd < 0) {
-                        ALOGE("Failed to open file %d for writing (%d). Aboring.", header.fileIdx,
+                        ALOGE("Failed to open file %d for writing (%d). Aborting.", header.fileIdx,
                               -writeFd);
                         break;
                     }
@@ -716,9 +755,11 @@
     }
 
     FileIdx convertFileIdToFileIndex(android::dataloader::FileId fileId) {
-        // FileId is a string in format '+FileIdx\0'.
+        // FileId has format '\2FileIdx'.
         const char* meta = (const char*)&fileId;
-        if (*meta != '+') {
+
+        int8_t mode = *meta;
+        if (mode != DATA_ONLY_STREAMING && mode != STREAMING) {
             return -1;
         }
 
@@ -732,10 +773,10 @@
         return FileIdx(fileIdx);
     }
 
-    android::dataloader::FileId convertFileIndexToFileId(FileIdx fileIdx) {
+    android::dataloader::FileId convertFileIndexToFileId(MetadataMode mode, FileIdx fileIdx) {
         IncFsFileId fileId = {};
         char* meta = (char*)&fileId;
-        *meta = '+';
+        *meta = mode;
         if (auto [p, ec] = std::to_chars(meta + 1, meta + sizeof(fileId), fileIdx);
             ec != std::errc()) {
             return {};