1 /* 2 * Copyright (C) 2023 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 com.android.federatedcompute.services.http; 18 19 import com.android.federatedcompute.internal.util.LogUtil; 20 21 import com.google.common.collect.ImmutableSet; 22 import com.google.protobuf.ByteString; 23 24 import org.json.JSONObject; 25 26 import java.io.ByteArrayInputStream; 27 import java.io.ByteArrayOutputStream; 28 import java.io.IOException; 29 import java.util.List; 30 import java.util.Map; 31 import java.util.zip.GZIPInputStream; 32 import java.util.zip.GZIPOutputStream; 33 34 /** Utility class containing http related variable e.g. headers, method. */ 35 public final class HttpClientUtil { 36 private static final String TAG = HttpClientUtil.class.getSimpleName(); 37 public static final String CONTENT_ENCODING_HDR = "Content-Encoding"; 38 39 public static final String ACCEPT_ENCODING_HDR = "Accept-Encoding"; 40 public static final String CONTENT_LENGTH_HDR = "Content-Length"; 41 public static final String GZIP_ENCODING_HDR = "gzip"; 42 public static final String CONTENT_TYPE_HDR = "Content-Type"; 43 public static final String PROTOBUF_CONTENT_TYPE = "application/x-protobuf"; 44 public static final String OCTET_STREAM = "application/octet-stream"; 45 public static final ImmutableSet<Integer> HTTP_OK_STATUS = ImmutableSet.of(200, 201); 46 47 public static final Integer HTTP_UNAUTHENTICATED_STATUS = 401; 48 49 public static final Integer HTTP_UNAUTHORIZED_STATUS = 403; 50 51 public static final ImmutableSet<Integer> HTTP_OK_OR_UNAUTHENTICATED_STATUS = 52 ImmutableSet.of(200, 201, 401); 53 54 // This key indicates the key attestation record used for authentication. 55 public static final String ODP_AUTHENTICATION_KEY = "odp-authentication-key"; 56 57 // This key indicates a UUID as a verified token for the device. 58 public static final String ODP_AUTHORIZATION_KEY = "odp-authorization-key"; 59 60 public static final String ODP_IDEMPOTENCY_KEY = "odp-idempotency-key"; 61 62 public static final String FCP_OWNER_ID_DIGEST = "fcp-owner-id-digest"; 63 64 public static final int DEFAULT_BUFFER_SIZE = 1024; 65 public static final byte[] EMPTY_BODY = new byte[0]; 66 67 /** The supported http methods. */ 68 public enum HttpMethod { 69 GET, 70 POST, 71 PUT, 72 } 73 74 public static final class FederatedComputePayloadDataContract { 75 public static final String KEY_ID = "keyId"; 76 77 public static final String ENCRYPTED_PAYLOAD = "encryptedPayload"; 78 79 public static final String ASSOCIATED_DATA_KEY = "associatedData"; 80 81 public static final byte[] ASSOCIATED_DATA = new JSONObject().toString().getBytes(); 82 } 83 84 /** Compresses the input data using Gzip. */ compressWithGzip(byte[] uncompressedData)85 public static byte[] compressWithGzip(byte[] uncompressedData) { 86 try (ByteString.Output outputStream = ByteString.newOutput(uncompressedData.length); 87 GZIPOutputStream gzipOutputStream = new GZIPOutputStream(outputStream)) { 88 gzipOutputStream.write(uncompressedData); 89 gzipOutputStream.finish(); 90 return outputStream.toByteString().toByteArray(); 91 } catch (IOException e) { 92 LogUtil.e(TAG, "Failed to compress using Gzip"); 93 throw new IllegalStateException("Failed to compress using Gzip", e); 94 } 95 } 96 97 /** Uncompresses the input data using Gzip. */ uncompressWithGzip(byte[] data)98 public static byte[] uncompressWithGzip(byte[] data) { 99 try (ByteArrayInputStream inputStream = new ByteArrayInputStream(data); 100 GZIPInputStream gzip = new GZIPInputStream(inputStream); 101 ByteArrayOutputStream result = new ByteArrayOutputStream()) { 102 int length; 103 byte[] buffer = new byte[DEFAULT_BUFFER_SIZE]; 104 while ((length = gzip.read(buffer, 0, DEFAULT_BUFFER_SIZE)) > 0) { 105 result.write(buffer, 0, length); 106 } 107 return result.toByteArray(); 108 } catch (Exception e) { 109 LogUtil.e(TAG, e, "Failed to decompress the data."); 110 throw new IllegalStateException("Failed to unscompress using Gzip", e); 111 } 112 } 113 114 /** Calculates total bytes are sent via network based on provided http request. */ getTotalSentBytes(FederatedComputeHttpRequest request)115 public static long getTotalSentBytes(FederatedComputeHttpRequest request) { 116 long totalBytes = 0; 117 totalBytes += 118 request.getHttpMethod().name().length() 119 + " ".length() 120 + request.getUri().length() 121 + " HTTP/1.1\r\n".length(); 122 for (String key : request.getExtraHeaders().keySet()) { 123 totalBytes += 124 key.length() 125 + ": ".length() 126 + request.getExtraHeaders().get(key).length() 127 + "\r\n".length(); 128 } 129 if (request.getExtraHeaders().containsKey(CONTENT_LENGTH_HDR)) { 130 totalBytes += Long.parseLong(request.getExtraHeaders().get(CONTENT_LENGTH_HDR)); 131 } 132 return totalBytes; 133 } 134 135 /** Calculates total bytes are received via network based on provided http response. */ getTotalReceivedBytes(FederatedComputeHttpResponse response)136 public static long getTotalReceivedBytes(FederatedComputeHttpResponse response) { 137 long totalBytes = 0; 138 boolean foundContentLengthHdr = false; 139 for (Map.Entry<String, List<String>> header : response.getHeaders().entrySet()) { 140 if (header.getKey() == null) { 141 continue; 142 } 143 for (String headerValue : header.getValue()) { 144 totalBytes += header.getKey().length() + ": ".length(); 145 totalBytes += headerValue == null ? 0 : headerValue.length(); 146 } 147 // Uses Content-Length header to estimate total received bytes which is the most 148 // accurate. 149 if (header.getKey().equals(CONTENT_LENGTH_HDR)) { 150 totalBytes += Long.parseLong(header.getValue().get(0)); 151 foundContentLengthHdr = true; 152 } 153 } 154 if (!foundContentLengthHdr && response.getPayload() != null) { 155 totalBytes += response.getPayload().length; 156 } 157 return totalBytes; 158 } 159 HttpClientUtil()160 private HttpClientUtil() {} 161 } 162