1/*
2 * Copyright (C) 2024 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
17import {assertDefined} from 'common/assert_utils';
18import {
19  TamperedMessageType,
20  TamperedProtoField,
21} from 'parsers/tampered_message_type';
22import {AddOperation} from 'trace/tree_node/operations/add_operation';
23import {PropertyTreeNode} from 'trace/tree_node/property_tree_node';
24import {DEFAULT_PROPERTY_TREE_NODE_FACTORY} from 'trace/tree_node/property_tree_node_factory';
25
26export class AddDefaults extends AddOperation<PropertyTreeNode> {
27  private readonly protoType: TamperedMessageType;
28  constructor(
29    protoField: TamperedProtoField,
30    private readonly propertyAllowlist?: string[],
31    private readonly propertyDenylist?: string[],
32  ) {
33    super();
34    this.protoType = assertDefined(protoField.tamperedMessageType);
35  }
36
37  override makeProperties(value: PropertyTreeNode): PropertyTreeNode[] {
38    const defaultPropertyNodes: PropertyTreeNode[] = [];
39    for (const fieldName in this.protoType.fields) {
40      if (
41        this.propertyAllowlist &&
42        !this.propertyAllowlist.includes(fieldName)
43      ) {
44        continue;
45      }
46
47      if (this.propertyDenylist && this.propertyDenylist.includes(fieldName)) {
48        continue;
49      }
50
51      if (
52        !Object.prototype.hasOwnProperty.call(this.protoType.fields, fieldName)
53      ) {
54        continue;
55      }
56
57      const field = this.protoType.fields[fieldName];
58      let existingNode = value.getChildByName(fieldName);
59      let defaultValue: any = field.repeated ? [] : field.defaultValue;
60
61      if (!field.repeated && defaultValue === null) {
62        switch (field.type) {
63          case 'double':
64            defaultValue = 0;
65            break;
66          case 'float':
67            defaultValue = 0;
68            break;
69          case 'int32':
70            defaultValue = 0;
71            break;
72          case 'uint32':
73            defaultValue = 0;
74            break;
75          case 'sint32':
76            defaultValue = 0;
77            break;
78          case 'fixed32':
79            defaultValue = 0;
80            break;
81          case 'sfixed32':
82            defaultValue = 0;
83            break;
84          case 'int64':
85            defaultValue = BigInt(0);
86            break;
87          case 'uint64':
88            defaultValue = BigInt(0);
89            break;
90          case 'sint64':
91            defaultValue = BigInt(0);
92            break;
93          case 'fixed64':
94            defaultValue = BigInt(0);
95            break;
96          case 'sfixed64':
97            defaultValue = BigInt(0);
98            break;
99          case 'bool':
100            defaultValue = Boolean(defaultValue);
101            break;
102          default:
103          //do nothing
104        }
105      }
106
107      if (
108        !existingNode ||
109        existingNode.getValue() === defaultValue ||
110        (existingNode.getValue() === undefined &&
111          existingNode.getAllChildren().length === 0)
112      ) {
113        existingNode = DEFAULT_PROPERTY_TREE_NODE_FACTORY.makeDefaultProperty(
114          value.id,
115          fieldName,
116          defaultValue,
117        );
118        defaultPropertyNodes.push(existingNode);
119        continue;
120      }
121
122      if (field.tamperedMessageType) {
123        const operation = new AddDefaults(field);
124        if (field.repeated) {
125          existingNode
126            .getAllChildren()
127            .forEach((child) => operation.apply(child));
128        } else {
129          operation.apply(existingNode);
130        }
131      }
132    }
133
134    return defaultPropertyNodes;
135  }
136}
137