• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2021 Google LLC
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  *   https://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 package com.google.android.enterprise.connectedapps.internal;
17 
18 import android.os.Bundle;
19 import android.os.Parcel;
20 import com.google.android.enterprise.connectedapps.CrossProfileSender;
21 import java.nio.ByteBuffer;
22 import java.util.Arrays;
23 import java.util.HashMap;
24 import java.util.Map;
25 
26 /**
27  * Build up parcels over multiple calls and prepare responses.
28  *
29  * <p>This is the counterpart to {@link BundleCallSender}. Calls by the {@link BundleCallSender}
30  * should be relayed to an instance of this class.
31  */
32 public final class BundleCallReceiver {
33 
34   static final byte STATUS_COMPLETE = 0;
35   static final byte STATUS_INCOMPLETE = 1;
36   static final byte STATUS_INCLUDES_BUNDLES = 2;
37 
38   private final Map<Long, byte[]> preparedCalls = new HashMap<>();
39   private final Map<Long, Integer> preparedCallParts = new HashMap<>();
40   private final Map<Long, byte[]> preparedResponses = new HashMap<>();
41   private final Map<Long, Bundle> preparedCallBundles = new HashMap<>();
42   private final Map<Long, Bundle> preparedResponseBundles = new HashMap<>();
43 
44   /**
45    * Prepare a response to be returned by calls to {@link #getPreparedResponse(long, int)}.
46    *
47    * <p>The {@code byte[]} returned will begin with a 0 if all can be contained in a single call and
48    * 1 if further calls to {@link #getPreparedResponse(long, int)} are required. If the first byte
49    * is a 1, then the following 4 bytes will be an {@link Integer} representing the total number of
50    * bytes in the response.
51    *
52    * <p>The {@code byte[]} returned will consist only of a 2 if a bundle needs to be fetched using
53    * {@link #getPreparedResponseBundle(long, int)}.
54    */
prepareResponse(long callId, Bundle responseBundle)55   public byte[] prepareResponse(long callId, Bundle responseBundle) {
56     Parcel responseParcel = Parcel.obtain();
57     responseBundle.writeToParcel(responseParcel, /* flags= */ 0);
58 
59     byte[] responseBytes;
60     try {
61       responseBytes = responseParcel.marshall();
62     } catch (Exception | AssertionError e) {
63       prepareResponseBundle(callId, /* bundleId= */ 0, responseBundle);
64       return new byte[] {STATUS_INCLUDES_BUNDLES};
65     } finally {
66       responseParcel.recycle();
67     }
68 
69     if (responseBytes.length <= CrossProfileSender.MAX_BYTES_PER_BLOCK) {
70       return ByteUtilities.joinByteArrays(new byte[] {STATUS_COMPLETE}, responseBytes);
71     }
72     // Record the bytes to be sent and send the first block
73     preparedResponses.put(callId, responseBytes);
74     byte[] response = new byte[CrossProfileSender.MAX_BYTES_PER_BLOCK + 5];
75     response[0] = STATUS_INCOMPLETE;
76     byte[] sizeBytes = ByteBuffer.allocate(4).putInt(responseBytes.length).array();
77     System.arraycopy(sizeBytes, /* srcPos= */ 0, response, /* destPos= */ 1, /* length= */ 4);
78     System.arraycopy(
79         responseBytes,
80         /* srcPos= */ 0,
81         response,
82         /* destPos= */ 5,
83         /* length= */ CrossProfileSender.MAX_BYTES_PER_BLOCK);
84     return response;
85   }
86 
prepareBundle(long callId, int bundleId, Bundle bundle)87   public void prepareBundle(long callId, int bundleId, Bundle bundle) {
88     if (preparedCallBundles.containsKey(callId)) {
89       throw new IllegalStateException("Already prepared bundle for call " + callId);
90     }
91 
92     preparedCallBundles.put(callId, bundle);
93   }
94 
prepareResponseBundle(long callId, int bundleId, Bundle bundle)95   private void prepareResponseBundle(long callId, int bundleId, Bundle bundle) {
96     if (preparedResponseBundles.containsKey(callId)) {
97       throw new IllegalStateException("Already prepared bundle for response " + callId);
98     }
99 
100     preparedResponseBundles.put(callId, bundle);
101   }
102 
getPreparedResponseBundle(long callId, int bundleId)103   public Bundle getPreparedResponseBundle(long callId, int bundleId) {
104     return preparedResponseBundles.remove(callId);
105   }
106 
107   /**
108    * Prepare a call, storing one block of bytes for a call which will be completed with a call to
109    * {@link #getPreparedCall(long, int, byte[])}.
110    */
prepareCall(long callId, int blockId, int numBytes, byte[] paramBytes)111   public void prepareCall(long callId, int blockId, int numBytes, byte[] paramBytes) {
112     if (!preparedCalls.containsKey(callId)) {
113       preparedCalls.put(callId, new byte[numBytes]);
114       preparedCallParts.put(callId, 0);
115     }
116     System.arraycopy(
117         paramBytes,
118         /* srcPos= */ 0,
119         preparedCalls.get(callId),
120         /* destPos= */ blockId * CrossProfileSender.MAX_BYTES_PER_BLOCK,
121         /* length= */ CrossProfileSender.MAX_BYTES_PER_BLOCK);
122     preparedCallParts.put(
123         callId,
124         preparedCallParts.get(callId)
125             + 1
126             + blockId); // +1 to have a difference when preparing the 0th block
127   }
128 
129   /**
130    * Fetch the full {@link Bundle} using bytes previously stored by calls to {@link
131    * #prepareCall(long, int, int, byte[])}.
132    *
133    * <p>If this is the only block, then the {@code paramBytes} will be unmarshalled directly into a
134    * {@link Bundle}.
135    *
136    * @throws IllegalStateException If this is not the only block, and any previous blocks are
137    *     missing.
138    */
getPreparedCall(long callId, int blockId, byte[] paramBytes)139   public Bundle getPreparedCall(long callId, int blockId, byte[] paramBytes) {
140     if (bytesRefersToBundle(paramBytes)) {
141       return preparedCallBundles.remove(callId);
142     }
143 
144     if (blockId > 0) {
145       int expectedBlocks = 0;
146       for (int i = 0; i < blockId; i++) {
147         expectedBlocks += 1 + i;
148       }
149       if (!preparedCallParts.containsKey(callId)
150           || expectedBlocks != preparedCallParts.get(callId)) {
151         throw new IllegalStateException("Call " + callId + " not prepared");
152       }
153       byte[] fullParamBytes = preparedCalls.get(callId);
154       System.arraycopy(
155           paramBytes,
156           /* srcPos= */ 0,
157           fullParamBytes,
158           /* destPos= */ blockId * CrossProfileSender.MAX_BYTES_PER_BLOCK,
159           /* length= */ paramBytes.length);
160       paramBytes = fullParamBytes;
161       preparedCalls.remove(callId);
162       preparedCallParts.remove(callId);
163     }
164 
165     Parcel parcel = Parcel.obtain();
166     parcel.unmarshall(paramBytes, 0, paramBytes.length);
167     parcel.setDataPosition(0);
168     Bundle bundle = new Bundle(Bundler.class.getClassLoader());
169     bundle.readFromParcel(parcel);
170     parcel.recycle();
171     return bundle;
172   }
173 
bytesRefersToBundle(byte[] bytes)174   static boolean bytesRefersToBundle(byte[] bytes) {
175     return bytes[0] == STATUS_INCLUDES_BUNDLES;
176   }
177 
178   /**
179    * Get a block from a response previously prepared with {@link #prepareResponse(long, Bundle)}.
180    *
181    * <p>If this is the final block, then the prepared blocks will be dropped, and future calls to
182    * this method will fail.
183    */
getPreparedResponse(long callId, int blockId)184   public byte[] getPreparedResponse(long callId, int blockId) {
185     byte[] preparedBytes = preparedResponses.get(callId);
186     byte[] response =
187         Arrays.copyOfRange(
188             preparedBytes,
189             /* from= */ blockId * CrossProfileSender.MAX_BYTES_PER_BLOCK,
190             /* to= */ Math.min(
191                 preparedBytes.length, (blockId + 1) * CrossProfileSender.MAX_BYTES_PER_BLOCK));
192     int numberOfBlocks =
193         (int) Math.ceil(preparedBytes.length * 1.0 / CrossProfileSender.MAX_BYTES_PER_BLOCK);
194     if (blockId == numberOfBlocks - 1) {
195       preparedResponses.remove(callId);
196     }
197     return response;
198   }
199 }
200