1 | #region Copyright notice and license
|
---|
2 | // Protocol Buffers - Google's data interchange format
|
---|
3 | // Copyright 2008 Google Inc. All rights reserved.
|
---|
4 | // http://github.com/jskeet/dotnet-protobufs/
|
---|
5 | // Original C++/Java/Python code:
|
---|
6 | // http://code.google.com/p/protobuf/
|
---|
7 | //
|
---|
8 | // Redistribution and use in source and binary forms, with or without
|
---|
9 | // modification, are permitted provided that the following conditions are
|
---|
10 | // met:
|
---|
11 | //
|
---|
12 | // * Redistributions of source code must retain the above copyright
|
---|
13 | // notice, this list of conditions and the following disclaimer.
|
---|
14 | // * Redistributions in binary form must reproduce the above
|
---|
15 | // copyright notice, this list of conditions and the following disclaimer
|
---|
16 | // in the documentation and/or other materials provided with the
|
---|
17 | // distribution.
|
---|
18 | // * Neither the name of Google Inc. nor the names of its
|
---|
19 | // contributors may be used to endorse or promote products derived from
|
---|
20 | // this software without specific prior written permission.
|
---|
21 | //
|
---|
22 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
---|
23 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
---|
24 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
---|
25 | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
---|
26 | // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
---|
27 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
---|
28 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
---|
29 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
---|
30 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
---|
31 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
---|
32 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
---|
33 | #endregion
|
---|
34 |
|
---|
35 | using System;
|
---|
36 | using System.Collections;
|
---|
37 | using System.Collections.Generic;
|
---|
38 | using System.IO;
|
---|
39 | using Google.ProtocolBuffers.Descriptors;
|
---|
40 |
|
---|
41 | namespace Google.ProtocolBuffers.ProtoMunge
|
---|
42 | {
|
---|
43 | /// <summary>
|
---|
44 | /// Utility console application which takes a message descriptor and a corresponding message,
|
---|
45 | /// and produces a new message with similar but random data. The data is the same length
|
---|
46 | /// as the original, but with random values within appropriate bands. (For instance, a compressed
|
---|
47 | /// integer in the range 0-127 will end up as another integer in the same range, to keep the length
|
---|
48 | /// the same.)
|
---|
49 | /// TODO(jonskeet): Potentially refactor to use an instance instead, making it simpler to
|
---|
50 | /// be thread-safe for external use.
|
---|
51 | /// </summary>
|
---|
52 | public sealed class Program {
|
---|
53 |
|
---|
54 | static readonly Random rng = new Random();
|
---|
55 |
|
---|
56 | static int Main(string[] args) {
|
---|
57 | if (args.Length != 3) {
|
---|
58 | Console.Error.WriteLine("Usage: ProtoMunge <descriptor type name> <input data> <output file>");
|
---|
59 | Console.Error.WriteLine("The descriptor type name is the fully-qualified message name, including assembly.");
|
---|
60 | Console.Error.WriteLine("(At a future date it may be possible to do this without building the .NET assembly at all.)");
|
---|
61 | return 1;
|
---|
62 | }
|
---|
63 | IMessage defaultMessage;
|
---|
64 | try {
|
---|
65 | defaultMessage = MessageUtil.GetDefaultMessage(args[0]);
|
---|
66 | } catch (ArgumentException e) {
|
---|
67 | Console.Error.WriteLine(e.Message);
|
---|
68 | return 1;
|
---|
69 | }
|
---|
70 | try {
|
---|
71 | IBuilder builder = defaultMessage.WeakCreateBuilderForType();
|
---|
72 | byte[] inputData = File.ReadAllBytes(args[1]);
|
---|
73 | builder.WeakMergeFrom(ByteString.CopyFrom(inputData));
|
---|
74 | IMessage original = builder.WeakBuild();
|
---|
75 | IMessage munged = Munge(original);
|
---|
76 | if (original.SerializedSize != munged.SerializedSize) {
|
---|
77 | throw new Exception("Serialized sizes don't match");
|
---|
78 | }
|
---|
79 | File.WriteAllBytes(args[2], munged.ToByteArray());
|
---|
80 | return 0;
|
---|
81 | } catch (Exception e) {
|
---|
82 | Console.Error.WriteLine("Error: {0}", e.Message);
|
---|
83 | Console.Error.WriteLine();
|
---|
84 | Console.Error.WriteLine("Detailed exception information: {0}", e);
|
---|
85 | return 1;
|
---|
86 | }
|
---|
87 | }
|
---|
88 |
|
---|
89 | /// <summary>
|
---|
90 | /// Munges a message recursively.
|
---|
91 | /// </summary>
|
---|
92 | /// <returns>A new message of the same type as the original message,
|
---|
93 | /// but munged so that all the data is desensitised.</returns>
|
---|
94 | private static IMessage Munge(IMessage message) {
|
---|
95 | IBuilder builder = message.WeakCreateBuilderForType();
|
---|
96 | foreach (var pair in message.AllFields) {
|
---|
97 | if (pair.Key.IsRepeated) {
|
---|
98 | foreach (object singleValue in (IEnumerable)pair.Value) {
|
---|
99 | builder.WeakAddRepeatedField(pair.Key, CheckedMungeValue(pair.Key, singleValue));
|
---|
100 | }
|
---|
101 | } else {
|
---|
102 | builder[pair.Key] = CheckedMungeValue(pair.Key, pair.Value);
|
---|
103 | }
|
---|
104 | }
|
---|
105 | IMessage munged = builder.WeakBuild();
|
---|
106 | if (message.SerializedSize != munged.SerializedSize) {
|
---|
107 | Console.WriteLine("Sub message sizes: {0}/{1}", message.SerializedSize, munged.SerializedSize);
|
---|
108 | }
|
---|
109 | return munged;
|
---|
110 | }
|
---|
111 |
|
---|
112 | /// <summary>
|
---|
113 | /// Munges a single value and checks that the length ends up the same as it was before.
|
---|
114 | /// </summary>
|
---|
115 | private static object CheckedMungeValue(FieldDescriptor fieldDescriptor, object value) {
|
---|
116 | int currentSize = CodedOutputStream.ComputeFieldSize(fieldDescriptor.FieldType, fieldDescriptor.FieldNumber, value);
|
---|
117 | object mungedValue = MungeValue(fieldDescriptor, value);
|
---|
118 | int mungedSize = CodedOutputStream.ComputeFieldSize(fieldDescriptor.FieldType, fieldDescriptor.FieldNumber, mungedValue);
|
---|
119 | // Exceptions log more easily than assertions
|
---|
120 | if (currentSize != mungedSize) {
|
---|
121 | throw new Exception("Munged value had wrong size. Field type: " + fieldDescriptor.FieldType
|
---|
122 | + "; old value: " + value + "; new value: " + mungedValue);
|
---|
123 | }
|
---|
124 | return mungedValue;
|
---|
125 | }
|
---|
126 |
|
---|
127 | /// <summary>
|
---|
128 | /// Munges a single value of the specified field descriptor. (i.e. if the field is
|
---|
129 | /// actually a repeated int, this method receives a single int value to munge, and
|
---|
130 | /// is called multiple times).
|
---|
131 | /// </summary>
|
---|
132 | private static object MungeValue(FieldDescriptor fieldDescriptor, object value) {
|
---|
133 | switch (fieldDescriptor.FieldType) {
|
---|
134 | case FieldType.SInt64:
|
---|
135 | case FieldType.Int64:
|
---|
136 | return (long) MungeVarint64((ulong) (long)value);
|
---|
137 | case FieldType.UInt64:
|
---|
138 | return MungeVarint64((ulong)value);
|
---|
139 | case FieldType.SInt32:
|
---|
140 | return (int)MungeVarint32((uint)(int)value);
|
---|
141 | case FieldType.Int32:
|
---|
142 | return MungeInt32((int) value);
|
---|
143 | case FieldType.UInt32:
|
---|
144 | return MungeVarint32((uint)value);
|
---|
145 | case FieldType.Double:
|
---|
146 | return rng.NextDouble();
|
---|
147 | case FieldType.Float:
|
---|
148 | return (float)rng.NextDouble();
|
---|
149 | case FieldType.Fixed64: {
|
---|
150 | byte[] data = new byte[8];
|
---|
151 | rng.NextBytes(data);
|
---|
152 | return BitConverter.ToUInt64(data, 0);
|
---|
153 | }
|
---|
154 | case FieldType.Fixed32: {
|
---|
155 | byte[] data = new byte[4];
|
---|
156 | rng.NextBytes(data);
|
---|
157 | return BitConverter.ToUInt32(data, 0);
|
---|
158 | }
|
---|
159 | case FieldType.Bool:
|
---|
160 | return rng.Next(2) == 1;
|
---|
161 | case FieldType.String:
|
---|
162 | return MungeString((string)value);
|
---|
163 | case FieldType.Group:
|
---|
164 | case FieldType.Message:
|
---|
165 | return Munge((IMessage)value);
|
---|
166 | case FieldType.Bytes:
|
---|
167 | return MungeByteString((ByteString)value);
|
---|
168 | case FieldType.SFixed64: {
|
---|
169 | byte[] data = new byte[8];
|
---|
170 | rng.NextBytes(data);
|
---|
171 | return BitConverter.ToInt64(data, 0);
|
---|
172 | }
|
---|
173 | case FieldType.SFixed32: {
|
---|
174 | byte[] data = new byte[4];
|
---|
175 | rng.NextBytes(data);
|
---|
176 | return BitConverter.ToInt32(data, 0);
|
---|
177 | }
|
---|
178 | case FieldType.Enum:
|
---|
179 | return MungeEnum(fieldDescriptor, (EnumValueDescriptor) value);
|
---|
180 | default:
|
---|
181 | // TODO(jonskeet): Different exception?
|
---|
182 | throw new ArgumentException("Invalid field descriptor");
|
---|
183 | }
|
---|
184 | }
|
---|
185 |
|
---|
186 | private static object MungeString(string original) {
|
---|
187 | foreach (char c in original) {
|
---|
188 | if (c > 127) {
|
---|
189 | throw new ArgumentException("Can't handle non-ascii yet");
|
---|
190 | }
|
---|
191 | }
|
---|
192 | char[] chars = new char[original.Length];
|
---|
193 | // Convert to pure ASCII - no control characters.
|
---|
194 | for (int i = 0; i < chars.Length; i++) {
|
---|
195 | chars[i] = (char) rng.Next(32, 127);
|
---|
196 | }
|
---|
197 | return new string(chars);
|
---|
198 | }
|
---|
199 |
|
---|
200 | /// <summary>
|
---|
201 | /// Int32 fields are slightly strange - we need to keep the sign the same way it is:
|
---|
202 | /// negative numbers can munge to any other negative number (it'll always take
|
---|
203 | /// 10 bytes) but positive numbers have to stay positive, so we can't use the
|
---|
204 | /// full range of 32 bits.
|
---|
205 | /// </summary>
|
---|
206 | private static int MungeInt32(int value) {
|
---|
207 | if (value < 0) {
|
---|
208 | return rng.Next(int.MinValue, 0);
|
---|
209 | }
|
---|
210 | int length = CodedOutputStream.ComputeRawVarint32Size((uint) value);
|
---|
211 | uint min = length == 1 ? 0 : 1U << ((length - 1) * 7);
|
---|
212 | uint max = length == 5 ? int.MaxValue : (1U << (length * 7)) - 1;
|
---|
213 | return (int) NextRandomUInt64(min, max);
|
---|
214 | }
|
---|
215 |
|
---|
216 | private static uint MungeVarint32(uint original) {
|
---|
217 | int length = CodedOutputStream.ComputeRawVarint32Size(original);
|
---|
218 | uint min = length == 1 ? 0 : 1U << ((length - 1) * 7);
|
---|
219 | uint max = length == 5 ? uint.MaxValue : (1U << (length * 7)) - 1;
|
---|
220 | return (uint)NextRandomUInt64(min, max);
|
---|
221 | }
|
---|
222 |
|
---|
223 | private static ulong MungeVarint64(ulong original) {
|
---|
224 | int length = CodedOutputStream.ComputeRawVarint64Size(original);
|
---|
225 | ulong min = length == 1 ? 0 : 1UL << ((length - 1) * 7);
|
---|
226 | ulong max = length == 10 ? ulong.MaxValue : (1UL<< (length * 7)) - 1;
|
---|
227 | return NextRandomUInt64(min, max);
|
---|
228 | }
|
---|
229 |
|
---|
230 | /// <summary>
|
---|
231 | /// Returns a random number in the range [min, max] (both inclusive).
|
---|
232 | /// </summary>
|
---|
233 | private static ulong NextRandomUInt64(ulong min, ulong max) {
|
---|
234 | if (min > max) {
|
---|
235 | throw new ArgumentException("min must be <= max; min=" + min + "; max = " + max);
|
---|
236 | }
|
---|
237 | ulong range = max - min;
|
---|
238 | // This isn't actually terribly good at very large ranges - but it doesn't really matter for the sake
|
---|
239 | // of this program.
|
---|
240 | return min + (ulong)(range * rng.NextDouble());
|
---|
241 | }
|
---|
242 |
|
---|
243 | private static object MungeEnum(FieldDescriptor fieldDescriptor, EnumValueDescriptor original) {
|
---|
244 | // Find all the values which get encoded to the same size as the current value, and pick one at random
|
---|
245 | int originalSize = CodedOutputStream.ComputeRawVarint32Size((uint)original.Number);
|
---|
246 | List<EnumValueDescriptor> sameSizeValues = new List<EnumValueDescriptor> ();
|
---|
247 | foreach (EnumValueDescriptor candidate in fieldDescriptor.EnumType.Values) {
|
---|
248 | if (CodedOutputStream.ComputeRawVarint32Size((uint)candidate.Number) == originalSize) {
|
---|
249 | sameSizeValues.Add(candidate);
|
---|
250 | }
|
---|
251 | }
|
---|
252 | return sameSizeValues[rng.Next(sameSizeValues.Count)];
|
---|
253 | }
|
---|
254 |
|
---|
255 | private static object MungeByteString(ByteString byteString) {
|
---|
256 | byte[] data = new byte[byteString.Length];
|
---|
257 | rng.NextBytes(data);
|
---|
258 | return ByteString.CopyFrom(data);
|
---|
259 | }
|
---|
260 | }
|
---|
261 | } |
---|