• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2022 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  *      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 package com.google.android.libraries.mobiledatadownload.file.openers;
17 
18 import android.annotation.SuppressLint;
19 import android.net.Uri;
20 import android.util.Base64;
21 import com.google.android.libraries.mobiledatadownload.file.OpenContext;
22 import com.google.android.libraries.mobiledatadownload.file.Opener;
23 import com.google.common.io.ByteStreams;
24 import com.google.errorprone.annotations.CanIgnoreReturnValue;
25 import java.io.File;
26 import java.io.FileNotFoundException;
27 import java.io.IOException;
28 import java.io.InputStream;
29 import java.io.OutputStream;
30 import java.security.MessageDigest;
31 import java.security.NoSuchAlgorithmException;
32 import javax.annotation.Nullable;
33 
34 /**
35  * Opener for loading URIs as shared libraries.
36  *
37  * <p>In many cases, the URI cannot be loaded directly and must be copied to a cache directory
38  * first. Caller is responsible for ensuring that this cache directory is cleaned periodically, but
39  * they can do so using a MobStore URI with TTL, LRU, or other garbage collection method.
40  *
41  * <p>WARNING: This opener does no validation and assumes that the data it receives is good. Note
42  * that MobStore can validate a checksum present in the URI.
43  *
44  * <p>TODO Consider requiring validation of checksum.
45  *
46  * <p>Usage: <code>
47  * storage.open(uri, SystemLibraryOpener.create().withCacheDirectory(cacheRootUri))
48  * </code>
49  */
50 public final class SystemLibraryOpener implements Opener<Void> {
51 
52   @Nullable private Uri cacheDirectory;
53 
SystemLibraryOpener()54   private SystemLibraryOpener() {}
55 
56   @CanIgnoreReturnValue
withCacheDirectory(Uri dir)57   public SystemLibraryOpener withCacheDirectory(Uri dir) {
58     this.cacheDirectory = dir;
59     return this;
60   }
61 
create()62   public static SystemLibraryOpener create() {
63     return new SystemLibraryOpener();
64   }
65 
66   @Override
67   @SuppressLint("UnsafeDynamicallyLoadedCode") // System.load is needed to load from arbitrary Uris
open(OpenContext openContext)68   public Void open(OpenContext openContext) throws IOException {
69     File file = null;
70     try {
71       // NOTE: could be backend().openFile() if we added to Backend interface.
72       file = ReadFileOpener.create().open(openContext);
73       System.load(file.getAbsolutePath());
74     } catch (IOException e) {
75       if (cacheDirectory == null) {
76         throw new IOException("Cannot directly open file and no cache directory available.");
77       }
78       Uri cachedUri =
79           cacheDirectory
80               .buildUpon()
81               .appendPath(hashedLibraryName(openContext.originalUri()))
82               .build();
83       try {
84         file = openContext.storage().open(cachedUri, ReadFileOpener.create());
85         System.load(file.getAbsolutePath());
86       } catch (FileNotFoundException e2) {
87         // NOTE: this could be extracted as CopyOpener if the need arises
88         try (InputStream from =
89                 openContext.storage().open(openContext.originalUri(), ReadStreamOpener.create());
90             OutputStream to = openContext.storage().open(cachedUri, WriteStreamOpener.create())) {
91           ByteStreams.copy(from, to);
92           to.flush();
93         }
94         file = openContext.storage().open(cachedUri, ReadFileOpener.create());
95         System.load(file.getAbsolutePath());
96       }
97     }
98     return null; // Required by Void return type.
99   }
100 
hashedLibraryName(Uri uri)101   private static String hashedLibraryName(Uri uri) {
102     try {
103       MessageDigest digest = MessageDigest.getInstance("MD5");
104       byte[] bytes = digest.digest(uri.toString().getBytes());
105       String hash = Base64.encodeToString(bytes, Base64.NO_WRAP | Base64.URL_SAFE);
106       return ".mobstore-lib." + hash + ".so";
107     } catch (NoSuchAlgorithmException e) {
108       // Unreachable.
109       throw new RuntimeException("Missing MD5 algorithm implementation", e);
110     }
111   }
112 }
113