• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright (C) 2020 The Android Open Source Project
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//      http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15import {RecordConfig} from '../controller/record_config_types';
16
17export const BUCKET_NAME = 'perfetto-ui-data';
18import {v4 as uuidv4} from 'uuid';
19import {State} from './state';
20
21export async function saveTrace(trace: File|ArrayBuffer): Promise<string> {
22  // TODO(hjd): This should probably also be a hash but that requires
23  // trace processor support.
24  const name = uuidv4();
25  const url = 'https://www.googleapis.com/upload/storage/v1/b/' +
26      `${BUCKET_NAME}/o?uploadType=media` +
27      `&name=${name}&predefinedAcl=publicRead`;
28  const response = await fetch(url, {
29    method: 'post',
30    headers: {'Content-Type': 'application/octet-stream;'},
31    body: trace,
32  });
33  await response.json();
34  return `https://storage.googleapis.com/${BUCKET_NAME}/${name}`;
35}
36
37// Bigint's are not serializable using JSON.stringify, so we use a special
38// object when serialising
39export type SerializedBigint = {
40  __kind: 'bigint',
41  value: string
42};
43
44// Check if a value looks like a serialized bigint
45export function isSerializedBigint(value: unknown): value is SerializedBigint {
46  if (value === null) {
47    return false;
48  }
49  if (typeof value !== 'object') {
50    return false;
51  }
52  if ('__kind' in value && 'value' in value) {
53    return value.__kind === 'bigint' && typeof value.value === 'string';
54  }
55  return false;
56}
57
58export function serializeStateObject(object: unknown): string {
59  const json = JSON.stringify(object, (key, value) => {
60    if (typeof value === 'bigint') {
61      return {
62        __kind: 'bigint',
63        value: value.toString(),
64      };
65    }
66    return key === 'nonSerializableState' ? undefined : value;
67  });
68  return json;
69}
70
71export function deserializeStateObject(json: string): any {
72  const object = JSON.parse(json, (_key, value) => {
73    if (isSerializedBigint(value)) {
74      return BigInt(value.value);
75    }
76    return value;
77  });
78  return object;
79}
80
81export async function saveState(stateOrConfig: State|
82                                RecordConfig): Promise<string> {
83  const text = serializeStateObject(stateOrConfig);
84  const hash = await toSha256(text);
85  const url = 'https://www.googleapis.com/upload/storage/v1/b/' +
86      `${BUCKET_NAME}/o?uploadType=media` +
87      `&name=${hash}&predefinedAcl=publicRead`;
88  const response = await fetch(url, {
89    method: 'post',
90    headers: {
91      'Content-Type': 'application/json; charset=utf-8',
92    },
93    body: text,
94  });
95  await response.json();
96  return hash;
97}
98
99// This has a bug:
100// x.toString(16) doesn't zero pad so if the digest is:
101// [23, 7, 42, ...]
102// You get:
103// ['17', '7', '2a', ...] = 1772a...
104// Rather than:
105// ['17', '07', '2a', ...] = 17072a...
106// As you ought to (and as the hexdigest is computed by e.g. Python).
107// Unfortunately there are a lot of old permalinks out there so we
108// still need this broken implementation to check their hashes.
109export async function buggyToSha256(str: string): Promise<string> {
110  const buffer = new TextEncoder().encode(str);
111  const digest = await crypto.subtle.digest('SHA-256', buffer);
112  return Array.from(new Uint8Array(digest)).map((x) => x.toString(16)).join('');
113}
114
115export async function toSha256(str: string): Promise<string> {
116  const buffer = new TextEncoder().encode(str);
117  const digest = await crypto.subtle.digest('SHA-256', buffer);
118  return Array.from(new Uint8Array(digest))
119      .map((x) => x.toString(16).padStart(2, '0'))
120      .join('');
121}
122