Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 79de06e6 authored by Guojing Yuan's avatar Guojing Yuan
Browse files

[CDM perm sync] Send and receive messages integration

Send message flow (will remain unchanged):
1. A companion app calls startSystemDataTransfer
2. SystemDataTransferProcessor creates the permission backup file and
   send it CompanionMessageProcessor
3. CompanionMessageProcessor paginate the message and send them to
   CompanionSecureCommsManager
4. CompanionSecureCommsManager encrypts the messages and send them to the
   app.
This CL changes the logic to bind to the app if it's unbind. Previously
CompanionSecureCommsManager would throw an exception if it's unbind.

Receive message flow (newly integrated in this CL):
1. A companion app calls dispatchMessageToSystem
2. CompanionSecureCommsManager receives the message and decrypts it.
3. CompanionMessageProcessor receives the message via listener
   and may return the complete message to SystemDataTransferProcessor
   via listener.
4. SystemDataTransferProcessor processes the decrypted complete message.

Bug: 231474219

Test: manually tested the sender and receiver flows.
Change-Id: I0eb920103080fcb708614e636c6afec4fc69d92c
parent f1f44551
Loading
Loading
Loading
Loading
+12 −5
Original line number Diff line number Diff line
@@ -103,7 +103,10 @@ public class CompanionApplicationController {
        mCompanionServicesRegister.invalidate(userId);
    }

    void bindCompanionApplication(@UserIdInt int userId, @NonNull String packageName,
    /**
     * CDM binds to the companion app.
     */
    public void bindCompanionApplication(@UserIdInt int userId, @NonNull String packageName,
            boolean bindImportant) {
        if (DEBUG) {
            Log.i(TAG, "bind() u" + userId + "/" + packageName
@@ -143,7 +146,10 @@ public class CompanionApplicationController {
        }
    }

    void unbindCompanionApplication(@UserIdInt int userId, @NonNull String packageName) {
    /**
     * CDM unbinds the companion app.
     */
    public void unbindCompanionApplication(@UserIdInt int userId, @NonNull String packageName) {
        if (DEBUG) Log.i(TAG, "unbind() u" + userId + "/" + packageName);

        final List<CompanionDeviceServiceConnector> serviceConnectors;
@@ -237,9 +243,9 @@ public class CompanionApplicationController {
        primaryServiceConnector.postOnDeviceDisappeared(association);
    }

    /** Pass an encryped secure message to the companion application for transporting. */
    /** Pass an encrypted secure message to the companion application for transporting. */
    public void dispatchMessage(@UserIdInt int userId, @NonNull String packageName,
            int associationId, @NonNull byte[] message) {
            int associationId, int messageId, @NonNull byte[] message) {
        if (DEBUG) {
            Log.i(TAG, "dispatchMessage() u" + userId + "/" + packageName
                    + " associationId=" + associationId);
@@ -256,7 +262,8 @@ public class CompanionApplicationController {
            return;
        }

        primaryServiceConnector.postOnMessageDispatchedFromSystem(associationId, message);
        primaryServiceConnector.postOnMessageDispatchedFromSystem(associationId, messageId,
                message);
    }

    private void onPrimaryServiceBindingDied(@UserIdInt int userId, @NonNull String packageName) {
+9 −1
Original line number Diff line number Diff line
@@ -626,7 +626,15 @@ public class CompanionDeviceManagerService extends SystemService {
                        + " message(Base64)=" + Base64.encodeToString(message, 0));
            }

            mSecureCommsManager.receiveSecureMessage(associationId, message);
            AssociationInfo association = getAssociationWithCallerChecks(associationId);
            if (association == null) {
                throw new IllegalArgumentException("Association with ID " + associationId + " "
                        + "does not exist "
                        + "or belongs to a different package "
                        + "or belongs to a different user");
            }

            mSecureCommsManager.receiveSecureMessage(messageId, associationId, message);
        }

        @Override
+3 −8
Original line number Diff line number Diff line
@@ -98,15 +98,10 @@ class CompanionDeviceServiceConnector extends ServiceConnector.Impl<ICompanionDe
        post(companionService -> companionService.onDeviceDisappeared(associationInfo));
    }

    void postOnMessageDispatchedFromSystem(int associationId, @NonNull byte[] message) {
        // We always use messageId 0 (at least for now).
        // Unlike the message itself, the messageId is not encoded, which means that the CDM on the
        // other (receiving) end CAN NOT and MUST NOT trust this messageId.
        // If CDM needs to pass messageId around to the other side - it should embed it in the
        // message body.
    void postOnMessageDispatchedFromSystem(int associationId, int messageId,
            @NonNull byte[] message) {
        post(companionService ->
                companionService.onMessageDispatchedFromSystem(
                        /* messageId*/ 0, associationId, message));
                companionService.onMessageDispatchedFromSystem(messageId, associationId, message));
    }

    /**
+2 −1
Original line number Diff line number Diff line
@@ -107,7 +107,8 @@ class CompanionDeviceShellCommand extends ShellCommand {
                        message = sb.toString().getBytes(UTF_8);
                    }

                    mSecureCommsManager.sendSecureMessage(associationId, message);
                    mSecureCommsManager.sendSecureMessage(associationId, /* messageId */ 0,
                            message);
                    break;

                default:
+83 −20
Original line number Diff line number Diff line
@@ -16,6 +16,8 @@

package com.android.server.companion.datatransfer;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.util.Slog;
import android.util.proto.ProtoInputStream;
import android.util.proto.ProtoOutputStream;
@@ -39,6 +41,12 @@ public class CompanionMessageProcessor {

    private static final String LOG_TAG = CompanionMessageProcessor.class.getSimpleName();

    /** Listener for incoming complete messages. */
    interface Listener {
        /** When a complete message is received from the companion app. */
        void onCompleteMessageReceived(@NonNull CompanionMessageInfo message);
    }

    // Rough size for each CompanionMessage, each message can exceed 50K for a little, but not
    // too much. Hard limit is 100K, WCS data processing limit. Closer to 100K, less stable at
    // the WCS data processing layer. Refer to
@@ -48,6 +56,9 @@ public class CompanionMessageProcessor {

    private final CompanionSecureCommunicationsManager mSecureCommsManager;

    @Nullable
    private Listener mListener;

    // Association id -> (parent id -> received messages)
    private final Map<Integer, Map<Integer, List<CompanionMessageInfo>>> mAssociationsMessagesMap =
            new HashMap<>();
@@ -56,6 +67,11 @@ public class CompanionMessageProcessor {

    public CompanionMessageProcessor(CompanionSecureCommunicationsManager secureCommsManager) {
        mSecureCommsManager = secureCommsManager;
        mSecureCommsManager.setListener(this::onDecryptedMessageReceived);
    }

    public void setListener(@NonNull Listener listener) {
        mListener = listener;
    }

    /**
@@ -72,7 +88,8 @@ public class CompanionMessageProcessor {

        for (int i = 0; i < totalMessageCount; i++) {
            ProtoOutputStream proto = new ProtoOutputStream();
            proto.write(CompanionMessage.ID, parentMessageId + i + 1);
            int messageId = parentMessageId + i + 1;
            proto.write(CompanionMessage.ID, messageId);

            long paginationInfoToken = proto.start(CompanionMessage.PAGINATION_INFO);
            proto.write(CompanionMessage.PaginationInfo.PARENT_ID, parentMessageId);
@@ -87,29 +104,68 @@ public class CompanionMessageProcessor {

            Slog.i(LOG_TAG, "Sending " + currentData.length + " bytes to " + packageName);

            mSecureCommsManager.sendSecureMessage(associationId, proto.getBytes());
            mSecureCommsManager.sendSecureMessage(associationId, messageId, proto.getBytes());
        }
    }

    /**
     * Process message and store it. If all the messages with the same parent id have been received,
     * return the message with combined message data. Otherwise, return null if there's still data
     * parts missing.
     * Process the message and store it. If all the messages with the same parent id have been
     * received, return the message with combined message data. Otherwise, return null if there's
     * still data parts missing.
     */
    public CompanionMessageInfo processMessage(int messageId, int associationId, byte[] message) {
    public CompanionMessageInfo onDecryptedMessageReceived(int messageId, int associationId,
            byte[] message) {
        ProtoInputStream proto = new ProtoInputStream(message);
        try {
            int id = proto.readInt(CompanionMessage.ID);
            if (id == messageId) {
            int id = 0;
            int parentId = 0;
            int page = 0;
            int total = 0;
            int type = CompanionMessage.UNKNOWN;
            byte[] data = null;

            // Read proto data
            while (proto.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
                switch (proto.getFieldNumber()) {
                    case (int) CompanionMessage.ID:
                        id = proto.readInt(CompanionMessage.ID);
                        break;
                    case (int) CompanionMessage.PAGINATION_INFO:
                        long paginationToken = proto.start(CompanionMessage.PAGINATION_INFO);
                int parentId = proto.readInt(CompanionMessage.PaginationInfo.PARENT_ID);
                int page = proto.readInt(CompanionMessage.PaginationInfo.PAGE);
                int total = proto.readInt(CompanionMessage.PaginationInfo.TOTAL);
                        while (proto.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
                            switch (proto.getFieldNumber()) {
                                case (int) CompanionMessage.PaginationInfo.PARENT_ID:
                                    parentId = proto.readInt(
                                            CompanionMessage.PaginationInfo.PARENT_ID);
                                    break;
                                case (int) CompanionMessage.PaginationInfo.PAGE:
                                    page = proto.readInt(CompanionMessage.PaginationInfo.PAGE);
                                    break;
                                case (int) CompanionMessage.PaginationInfo.TOTAL:
                                    total = proto.readInt(CompanionMessage.PaginationInfo.TOTAL);
                                    break;
                                default:
                                    Slog.e(LOG_TAG, "Unexpected field id "
                                            + proto.getFieldNumber() + " for PaginationInfo.");
                                    break;
                            }
                        }
                        proto.end(paginationToken);
                int type = proto.readInt(CompanionMessage.TYPE);
                byte[] data = proto.readBytes(CompanionMessage.DATA);
                        break;
                    case (int) CompanionMessage.TYPE:
                        type = proto.readInt(CompanionMessage.TYPE);
                        break;
                    case (int) CompanionMessage.DATA:
                        data = proto.readBytes(CompanionMessage.DATA);
                        break;
                    default:
                        Slog.e(LOG_TAG, "Unexpected field id " + proto.getFieldNumber()
                                + " for CompanionMessage.");
                        break;
                }
            }

            if (id == messageId) {
                CompanionMessageInfo messageInfo = new CompanionMessageInfo(id, page, total, type,
                        data);
                // Add the message into mAssociationsMessagesMap
@@ -122,28 +178,35 @@ public class CompanionMessageProcessor {
                mAssociationsMessagesMap.put(associationId, associationMessages);
                // Check if all the messages with the same parentId are received.
                if (childMessages.size() == total) {
                    Slog.i(LOG_TAG, "All [" + total + "] messages are received. Processing.");

                    childMessages.sort(Comparator.comparing(CompanionMessageInfo::getPage));
                    ByteArrayOutputStream stream = new ByteArrayOutputStream();
                    for (int i = 0; i < childMessages.size(); i++) {
                        stream.write(childMessages.get(i).getData());
                    }
                    mAssociationsMessagesMap.remove(parentId);
                    return new CompanionMessageInfo(parentId, 0, total, type, stream.toByteArray());
                    mListener.onCompleteMessageReceived(
                            new CompanionMessageInfo(parentId, 0, total, type,
                                    stream.toByteArray()));
                } else {
                    Slog.i(LOG_TAG, "[" + childMessages.size() + "/" + total
                            + "] messages are received for parentId [" + parentId + "]");
                }
            } else {
                Slog.e(LOG_TAG, "Message id mismatch.");
                return null;
            }
        } catch (IOException e) {
            Slog.e(LOG_TAG, "Can't read proto message id: " + messageId + ", message: "
                    + new String(message) + ".");
            Slog.e(LOG_TAG, "Can't read proto from the message.");
            return null;
        }
        return null;
    }

    /**
     * Find the next parent id. The parent and child ids are incremental.
     * Find the next parent id from [1, Integer.MAX_VALUE].
     * The parent and child ids are incremental.
     */
    private int findNextParentId(int associationId, int totalMessageCount) {
        int nextParentId = mNextParentId.getOrDefault(associationId, 1);
Loading