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 17import { 18 TamperedMessageType, 19 TamperedProtoField, 20} from 'parsers/tampered_message_type'; 21import {FakeProto} from './fake_proto_builder'; 22 23export class FakeProtoTransformer { 24 constructor(private readonly rootMessageType: TamperedMessageType) {} 25 26 transform(proto: FakeProto): FakeProto { 27 return this.transformMessageRec(proto, this.rootMessageType); 28 } 29 30 private transformFieldRec( 31 proto: FakeProto, 32 field: TamperedProtoField, 33 ): FakeProto { 34 // Leaf (primitive type) 35 if (this.shouldCheckIfPrimitiveLeaf(proto, field)) { 36 switch (field.type) { 37 case 'double': 38 return Number(proto ?? 0); 39 case 'float': 40 return Number(proto ?? 0); 41 case 'int32': 42 return Number(proto ?? 0); 43 case 'uint32': 44 return Number(proto ?? 0); 45 case 'sint32': 46 return Number(proto ?? 0); 47 case 'fixed32': 48 return Number(proto ?? 0); 49 case 'sfixed32': 50 return Number(proto ?? 0); 51 case 'int64': 52 return BigInt(proto ?? 0); 53 case 'uint64': 54 return BigInt(proto ?? 0); 55 case 'sint64': 56 return BigInt(proto ?? 0); 57 case 'fixed64': 58 return BigInt(proto ?? 0); 59 case 'sfixed64': 60 return BigInt(proto ?? 0); 61 case 'string': 62 return proto; 63 case 'bool': 64 return Boolean(proto); 65 case 'bytes': 66 return proto; 67 default: 68 // do nothing 69 } 70 } 71 72 // Leaf (enum) 73 const enumId = this.tryGetEnumId(proto); 74 if (field.tamperedEnumType && enumId !== undefined) { 75 return Number(enumId); 76 } 77 78 // Leaf (default value) 79 if (proto === null || proto === undefined) { 80 return this.getDefaultValue(proto, field); 81 } 82 83 if (!field.tamperedMessageType) { 84 return proto; 85 } 86 87 // Field is message -> continue recursion 88 return this.transformMessageRec(proto, field.tamperedMessageType); 89 } 90 91 private transformMessageRec( 92 proto: FakeProto, 93 messageType: TamperedMessageType, 94 ): FakeProto { 95 for (const childName in messageType.fields) { 96 if ( 97 !Object.prototype.hasOwnProperty.call(messageType.fields, childName) 98 ) { 99 continue; 100 } 101 const childField = messageType.fields[childName]; 102 103 if (Array.isArray(proto[childName])) { 104 for (let i = 0; i < proto[childName].length; ++i) { 105 proto[childName][i] = this.transformFieldRec( 106 proto[childName][i], 107 childField, 108 ); 109 } 110 } else { 111 proto[childName] = this.transformFieldRec(proto[childName], childField); 112 } 113 } 114 115 return proto; 116 } 117 118 private shouldCheckIfPrimitiveLeaf( 119 proto: FakeProto, 120 field: TamperedProtoField, 121 ): boolean { 122 return !field.repeated && proto !== null && proto !== undefined; 123 } 124 125 private getDefaultValue(proto: FakeProto, field: TamperedProtoField) { 126 return field.repeated ? [] : proto; 127 } 128 129 private tryGetEnumId(proto: FakeProto): number | undefined { 130 if (proto === null || proto === undefined) { 131 return 0; 132 } 133 134 switch (typeof proto) { 135 case 'number': 136 return proto; 137 case 'bigint': 138 return Number(proto); 139 default: 140 return undefined; 141 } 142 } 143} 144