From baa2bf54c2d8d493444fd3d58bd8a89927300a50 Mon Sep 17 00:00:00 2001 From: Jon Skeet Date: Thu, 14 Aug 2008 20:33:27 +0100 Subject: First part of dotnet library committer: Jon Skeet --- csharp/ProtocolBuffers/ByteString.cs | 96 +++ csharp/ProtocolBuffers/CodedInputStream.cs | 31 + csharp/ProtocolBuffers/CodedOutputStream.cs | 708 +++++++++++++++++++++ csharp/ProtocolBuffers/Descriptors.cs | 39 ++ csharp/ProtocolBuffers/ExtensionRegistry.cs | 5 + csharp/ProtocolBuffers/IBuilder.cs | 227 +++++++ csharp/ProtocolBuffers/IMessage.cs | 162 +++++ .../InvalidProtocolBufferException.cs | 7 + csharp/ProtocolBuffers/Properties/AssemblyInfo.cs | 36 ++ csharp/ProtocolBuffers/ProtocolBuffers.csproj | 60 ++ .../UninitializedMessageException.cs | 6 + csharp/ProtocolBuffers/UnknownFieldSet.cs | 13 + csharp/ProtocolBuffers/WireFormat.cs | 28 + 13 files changed, 1418 insertions(+) create mode 100644 csharp/ProtocolBuffers/ByteString.cs create mode 100644 csharp/ProtocolBuffers/CodedInputStream.cs create mode 100644 csharp/ProtocolBuffers/CodedOutputStream.cs create mode 100644 csharp/ProtocolBuffers/Descriptors.cs create mode 100644 csharp/ProtocolBuffers/ExtensionRegistry.cs create mode 100644 csharp/ProtocolBuffers/IBuilder.cs create mode 100644 csharp/ProtocolBuffers/IMessage.cs create mode 100644 csharp/ProtocolBuffers/InvalidProtocolBufferException.cs create mode 100644 csharp/ProtocolBuffers/Properties/AssemblyInfo.cs create mode 100644 csharp/ProtocolBuffers/ProtocolBuffers.csproj create mode 100644 csharp/ProtocolBuffers/UninitializedMessageException.cs create mode 100644 csharp/ProtocolBuffers/UnknownFieldSet.cs create mode 100644 csharp/ProtocolBuffers/WireFormat.cs (limited to 'csharp/ProtocolBuffers') diff --git a/csharp/ProtocolBuffers/ByteString.cs b/csharp/ProtocolBuffers/ByteString.cs new file mode 100644 index 00000000..1ce2453e --- /dev/null +++ b/csharp/ProtocolBuffers/ByteString.cs @@ -0,0 +1,96 @@ +using System.Text; +using System; + +namespace Google.ProtocolBuffers { + /// + /// Immutable array of bytes. + /// TODO(jonskeet): Implement the common collection interfaces? + /// + public sealed class ByteString { + + private static readonly ByteString empty = new ByteString(new byte[0]); + + private byte[] bytes; + + /// + /// Constructs a new ByteString from the given byte array. The array is + /// *not* copied, and must not be modified after this constructor is called. + /// + private ByteString(byte[] bytes) { + this.bytes = bytes; + } + + /// + /// Returns an empty ByteString. + /// + public static ByteString Empty { + get { return empty; } + } + + /// + /// Returns the length of this ByteString in bytes. + /// + public int Length { + get { return bytes.Length; } + } + + public bool IsEmpty { + get { return Length == 0; } + } + + public byte[] ToByteArray() { + return (byte[])bytes.Clone(); + } + + /// + /// Constructs a ByteString from the given array. The contents + /// are copied, so further modifications to the array will not + /// be reflected in the returned ByteString. + /// + public static ByteString CopyFrom(byte[] bytes) { + return new ByteString((byte[]) bytes.Clone()); + } + + /// + /// Constructs a ByteString from a portion of a byte array. + /// + public static ByteString CopyFrom(byte[] bytes, int offset, int count) { + byte[] portion = new byte[count]; + Array.Copy(bytes, offset, portion, 0, count); + return new ByteString(portion); + } + + /// + /// Creates a new ByteString by encoding the specified text with + /// the given encoding. + /// + public static ByteString CopyFrom(string text, Encoding encoding) { + return new ByteString(encoding.GetBytes(text)); + } + + /// + /// Creates a new ByteString by encoding the specified text in UTF-8. + /// + public static ByteString CopyFromUtf8(string text) { + return CopyFrom(text, Encoding.UTF8); + } + + /// + /// Retuns the byte at the given index. + /// + public byte this[int index] { + get { return bytes[index]; } + } + + public string ToString(Encoding encoding) { + return encoding.GetString(bytes); + } + + public string ToStringUtf8() { + return ToString(Encoding.UTF8); + } + + // TODO(jonskeet): CopyTo, Equals, GetHashCode if they turn out to be required + // TODO(jonskeet): Input/Output stuff + } +} diff --git a/csharp/ProtocolBuffers/CodedInputStream.cs b/csharp/ProtocolBuffers/CodedInputStream.cs new file mode 100644 index 00000000..66dae52c --- /dev/null +++ b/csharp/ProtocolBuffers/CodedInputStream.cs @@ -0,0 +1,31 @@ + +namespace Google.ProtocolBuffers { + public sealed class CodedInputStream { + + /// + /// Decode a 32-bit value with ZigZag encoding. + /// + /// + /// ZigZag encodes signed integers into values that can be efficiently + /// encoded with varint. (Otherwise, negative values must be + /// sign-extended to 64 bits to be varint encoded, thus always taking + /// 10 bytes on the wire.) + /// + public static int DecodeZigZag32(uint n) { + return (int)(n >> 1) ^ -(int)(n & 1); + } + + /// + /// Decode a 32-bit value with ZigZag encoding. + /// + /// + /// ZigZag encodes signed integers into values that can be efficiently + /// encoded with varint. (Otherwise, negative values must be + /// sign-extended to 64 bits to be varint encoded, thus always taking + /// 10 bytes on the wire.) + /// + public static long DecodeZigZag64(ulong n) { + return (long)(n >> 1) ^ -(long)(n & 1); + } + } +} diff --git a/csharp/ProtocolBuffers/CodedOutputStream.cs b/csharp/ProtocolBuffers/CodedOutputStream.cs new file mode 100644 index 00000000..51d5d657 --- /dev/null +++ b/csharp/ProtocolBuffers/CodedOutputStream.cs @@ -0,0 +1,708 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. +// http://code.google.com/p/protobuf/ +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +using System; +using System.IO; +using System.Text; + +namespace Google.ProtocolBuffers { + + /// + /// Encodes and writes protocol message fields. + /// + /// + /// This class contains two kinds of methods: methods that write specific + /// protocol message constructs and field types (e.g. WriteTag and + /// WriteInt32) and methods that write low-level values (e.g. + /// WriteRawVarint32 and WriteRawBytes). If you are writing encoded protocol + /// messages, you should use the former methods, but if you are writing some + /// other format of your own design, use the latter. The names of the former + /// methods are taken from the protocol buffer type names, not .NET types. + /// (Hence WriteFloat instead of WriteSingle, and WriteBool instead of WriteBoolean.) + /// + public sealed class CodedOutputStream { + /// + /// The buffer size used by CreateInstance(Stream). + /// + public static readonly int DefaultBufferSize = 4096; + + private readonly byte[] buffer; + private readonly int limit; + private int position; + private readonly Stream output; + + #region Construction + private CodedOutputStream(byte[] buffer, int offset, int length) { + this.output = null; + this.buffer = buffer; + this.position = offset; + this.limit = offset + length; + } + + private CodedOutputStream(Stream output, byte[] buffer) { + this.output = output; + this.buffer = buffer; + this.position = 0; + this.limit = buffer.Length; + } + + /// + /// Creates a new CodedOutputStream which write to the given stream. + /// + public static CodedOutputStream CreateInstance(Stream output) { + return CreateInstance(output, DefaultBufferSize); + } + + /// + /// Creates a new CodedOutputStream which write to the given stream and uses + /// the specified buffer size. + /// + public static CodedOutputStream CreateInstance(Stream output, int bufferSize) { + return new CodedOutputStream(output, new byte[bufferSize]); + } + + /// + /// Creates a new CodedOutputStream that writes directly to the given + /// byte array. If more bytes are written than fit in the array, + /// OutOfSpaceException will be thrown. + /// + public static CodedOutputStream CreateInstance(byte[] flatArray) { + return CreateInstance(flatArray, 0, flatArray.Length); + } + + /// + /// Creates a new CodedOutputStream that writes directly to the given + /// byte array slice. If more bytes are written than fit in the array, + /// OutOfSpaceException will be thrown. + /// + public static CodedOutputStream CreateInstance(byte[] flatArray, int offset, int length) { + return new CodedOutputStream(flatArray, offset, length); + } + #endregion + + #region Writing of tags etc + /// + /// Writes a double field value, including tag, to the stream. + /// + public void WriteDouble(int fieldNumber, double value) { + WriteTag(fieldNumber, WireFormat.WireType.Fixed64); + WriteRawLittleEndian64(BitConverter.DoubleToInt64Bits(value)); + } + + /// + /// Writes a float field value, including tag, to the stream. + /// + public void WriteFloat(int fieldNumber, float value) { + WriteTag(fieldNumber, WireFormat.WireType.Fixed32); + // FIXME: How do we convert a single to 32 bits? (Without unsafe code) + //WriteRawLittleEndian32(BitConverter.SingleT(value)); + } + + /// + /// Writes a uint64 field value, including tag, to the stream. + /// + public void WriteUInt64(int fieldNumber, ulong value) { + WriteTag(fieldNumber, WireFormat.WireType.Varint); + WriteRawVarint64(value); + } + + /// + /// Writes an int64 field value, including tag, to the stream. + /// + public void WriteInt64(int fieldNumber, long value) { + WriteTag(fieldNumber, WireFormat.WireType.Varint); + WriteRawVarint64((ulong)value); + } + + /// + /// Writes an int32 field value, including tag, to the stream. + /// + public void WriteInt32(int fieldNumber, int value) { + WriteTag(fieldNumber, WireFormat.WireType.Varint); + if (value >= 0) { + WriteRawVarint32((uint)value); + } else { + // Must sign-extend. + WriteRawVarint64((ulong)value); + } + } + + /// + /// Writes a fixed64 field value, including tag, to the stream. + /// + public void WriteFixed64(int fieldNumber, long value) { + WriteTag(fieldNumber, WireFormat.WireType.Fixed64); + WriteRawLittleEndian64(value); + } + + /// + /// Writes a fixed32 field value, including tag, to the stream. + /// + public void WriteFixed32(int fieldNumber, int value) { + WriteTag(fieldNumber, WireFormat.WireType.Fixed32); + WriteRawLittleEndian32(value); + } + + /// + /// Writes a bool field value, including tag, to the stream. + /// + public void WriteBool(int fieldNumber, bool value) { + WriteTag(fieldNumber, WireFormat.WireType.Varint); + WriteRawByte(value ? (byte)1 : (byte)0); + } + + /// + /// Writes a string field value, including tag, to the stream. + /// + public void WriteString(int fieldNumber, string value) { + WriteTag(fieldNumber, WireFormat.WireType.LengthDelimited); + // TODO(jonskeet): Optimise this if possible + // Unfortunately there does not appear to be any way to tell Java to encode + // UTF-8 directly into our buffer, so we have to let it create its own byte + // array and then copy. In .NET we can do the same thing very easily, + // so we don't need to worry about only writing one buffer at a time. + // We can optimise later. + byte[] bytes = Encoding.UTF8.GetBytes(value); + WriteRawVarint32((uint)bytes.Length); + WriteRawBytes(bytes); + } + + /// + /// Writes a group field value, including tag, to the stream. + /// + public void WriteGroup(int fieldNumber, IMessage value) { + WriteTag(fieldNumber, WireFormat.WireType.StartGroup); + value.WriteTo(this); + WriteTag(fieldNumber, WireFormat.WireType.EndGroup); + } + + public void WriteUnknownGroup(int fieldNumber, UnknownFieldSet value) { + WriteTag(fieldNumber, WireFormat.WireType.StartGroup); + value.WriteTo(this); + WriteTag(fieldNumber, WireFormat.WireType.EndGroup); + } + + public void WriteMessage(int fieldNumber, IMessage value) { + WriteTag(fieldNumber, WireFormat.WireType.LengthDelimited); + WriteRawVarint32((uint)value.SerializedSize); + value.WriteTo(this); + } + + public void WriteBytes(int fieldNumber, ByteString value) { + // TODO(jonskeet): Optimise this! (No need to copy the bytes twice.) + byte[] bytes = value.ToByteArray(); + WriteRawVarint32((uint)bytes.Length); + WriteRawBytes(bytes); + } + + public void WriteUInt32(int fieldNumber, uint value) { + WriteTag(fieldNumber, WireFormat.WireType.Varint); + WriteRawVarint32(value); + } + + public void WriteEnum(int fieldNumber, int value) { + WriteTag(fieldNumber, WireFormat.WireType.Varint); + WriteRawVarint32((uint)value); + } + + public void WriteSFixed32(int fieldNumber, int value) { + WriteTag(fieldNumber, WireFormat.WireType.Fixed32); + WriteRawVarint32((uint)value); + } + + public void WriteSFixed64(int fieldNumber, long value) { + WriteTag(fieldNumber, WireFormat.WireType.Fixed64); + WriteRawVarint64((ulong)value); + } + + public void WriteSInt32(int fieldNumber, int value) { + WriteTag(fieldNumber, WireFormat.WireType.Varint); + WriteRawVarint32(EncodeZigZag32(value)); + } + + public void WriteSInt64(int fieldNumber, long value) { + WriteTag(fieldNumber, WireFormat.WireType.Varint); + WriteRawVarint64(EncodeZigZag64(value)); + } + + public void WriteMessageSetExtension(int fieldNumber, IMessage value) { + WriteTag(WireFormat.MessageSetField.Item, WireFormat.WireType.StartGroup); + WriteUInt32(WireFormat.MessageSetField.TypeID, (uint)fieldNumber); + WriteMessage(WireFormat.MessageSetField.Message, value); + WriteTag(WireFormat.MessageSetField.Item, WireFormat.WireType.EndGroup); + } + + public void WriteRawMessageSetExtension(int fieldNumber, ByteString value) { + WriteTag(WireFormat.MessageSetField.Item, WireFormat.WireType.StartGroup); + WriteUInt32(WireFormat.MessageSetField.TypeID, (uint)fieldNumber); + WriteBytes(WireFormat.MessageSetField.Message, value); + WriteTag(WireFormat.MessageSetField.Item, WireFormat.WireType.EndGroup); + } + + public void WriteField(Descriptors.FieldDescriptor.Type fieldType, int fieldNumber, object value) { + switch (fieldType) { + case Descriptors.FieldDescriptor.Type.Double: WriteDouble(fieldNumber, (double)value); break; + case Descriptors.FieldDescriptor.Type.Float: WriteFloat(fieldNumber, (float)value); break; + case Descriptors.FieldDescriptor.Type.Int64: WriteInt64(fieldNumber, (long)value); break; + case Descriptors.FieldDescriptor.Type.UInt64: WriteUInt64(fieldNumber, (ulong)value); break; + case Descriptors.FieldDescriptor.Type.Int32: WriteInt32(fieldNumber, (int)value); break; + case Descriptors.FieldDescriptor.Type.Fixed64: WriteFixed64(fieldNumber, (long)value); break; + case Descriptors.FieldDescriptor.Type.Fixed32: WriteFixed32(fieldNumber, (int)value); break; + case Descriptors.FieldDescriptor.Type.Bool: WriteBool(fieldNumber, (bool)value); break; + case Descriptors.FieldDescriptor.Type.String: WriteString(fieldNumber, (string)value); break; + case Descriptors.FieldDescriptor.Type.Group: WriteGroup(fieldNumber, (IMessage)value); break; + case Descriptors.FieldDescriptor.Type.Message: WriteMessage(fieldNumber, (IMessage)value); break; + case Descriptors.FieldDescriptor.Type.Bytes: WriteBytes(fieldNumber, (ByteString)value); break; + case Descriptors.FieldDescriptor.Type.UInt32: WriteUInt32(fieldNumber, (uint)value); break; + case Descriptors.FieldDescriptor.Type.SFixed32: WriteSFixed32(fieldNumber, (int)value); break; + case Descriptors.FieldDescriptor.Type.SFixed64: WriteSFixed64(fieldNumber, (long)value); break; + case Descriptors.FieldDescriptor.Type.SInt32: WriteSInt32(fieldNumber, (int)value); break; + case Descriptors.FieldDescriptor.Type.SInt64: WriteSInt64(fieldNumber, (long)value); break; + case Descriptors.FieldDescriptor.Type.Enum: WriteEnum(fieldNumber, ((Descriptors.EnumValueDescriptor)value).Number); + break; + } + } + + #endregion + + #region Underlying writing primitives + /// + /// Encodes and writes a tag. + /// + public void WriteTag(int fieldNumber, WireFormat.WireType type) { + WriteRawVarint32(WireFormat.MakeTag(fieldNumber, type)); + } + + public void WriteRawVarint32(uint value) { + while (true) { + if ((value & ~0x7F) == 0) { + WriteRawByte(value); + return; + } else { + WriteRawByte((value & 0x7F) | 0x80); + value >>= 7; + } + } + } + + public void WriteRawVarint64(ulong value) { + while (true) { + if ((value & ~0x7FUL) == 0) { + WriteRawByte((uint)value); + return; + } else { + WriteRawByte(((uint)value & 0x7F) | 0x80); + value >>= 7; + } + } + } + + public void WriteRawLittleEndian32(int value) { + WriteRawByte((byte)value); + WriteRawByte((byte)(value >> 8)); + WriteRawByte((byte)(value >> 16)); + WriteRawByte((byte)(value >> 24)); + } + + public void WriteRawLittleEndian64(long value) { + WriteRawByte((byte)value); + WriteRawByte((byte)(value >> 8)); + WriteRawByte((byte)(value >> 16)); + WriteRawByte((byte)(value >> 24)); + WriteRawByte((byte)(value >> 32)); + WriteRawByte((byte)(value >> 40)); + WriteRawByte((byte)(value >> 48)); + WriteRawByte((byte)(value >> 56)); + } + + public void WriteRawByte(byte value) { + if (position == limit) { + RefreshBuffer(); + } + + buffer[position++] = value; + } + + public void WriteRawByte(uint value) { + WriteRawByte((byte)value); + } + + /// + /// Writes out an array of bytes. + /// + public void WriteRawBytes(byte[] value) { + WriteRawBytes(value, 0, value.Length); + } + + /// + /// Writes out part of an array of bytes. + /// + public void WriteRawBytes(byte[] value, int offset, int length) { + if (limit - position >= length) { + Array.Copy(value, offset, buffer, position, length); + // We have room in the current buffer. + position += length; + } else { + // Write extends past current buffer. Fill the rest of this buffer and + // flush. + int bytesWritten = limit - position; + Array.Copy(value, offset, buffer, position, bytesWritten); + offset += bytesWritten; + length -= bytesWritten; + position = limit; + RefreshBuffer(); + + // Now deal with the rest. + // Since we have an output stream, this is our buffer + // and buffer offset == 0 + if (length <= limit) { + // Fits in new buffer. + Array.Copy(value, offset, buffer, 0, length); + position = length; + } else { + // Write is very big. Let's do it all at once. + output.Write(value, offset, length); + } + } + } + #endregion + + #region Size computations + + const int LittleEndian64Size = 8; + const int LittleEndian32Size = 4; + + /// + /// Compute the number of bytes that would be needed to encode a + /// double field, including the tag. + /// + public static int ComputeDoubleSize(int fieldNumber, double value) { + return ComputeTagSize(fieldNumber) + LittleEndian64Size; + } + + /// + /// Compute the number of bytes that would be needed to encode a + /// float field, including the tag. + /// + public static int ComputeFloatSize(int fieldNumber, float value) { + return ComputeTagSize(fieldNumber) + LittleEndian32Size; + } + + /// + /// Compute the number of bytes that would be needed to encode a + /// uint64 field, including the tag. + /// + public static int ComputeUInt64Size(int fieldNumber, ulong value) { + return ComputeTagSize(fieldNumber) + ComputeRawVarint64Size(value); + } + + /// + /// Compute the number of bytes that would be needed to encode an + /// int64 field, including the tag. + /// + public static int ComputeInt64Size(int fieldNumber, long value) { + return ComputeTagSize(fieldNumber) + ComputeRawVarint64Size((ulong)value); + } + + /// + /// Compute the number of bytes that would be needed to encode an + /// int32 field, including the tag. + /// + public static int ComputeInt32Size(int fieldNumber, int value) { + if (value >= 0) { + return ComputeTagSize(fieldNumber) + ComputeRawVarint32Size((uint)value); + } else { + // Must sign-extend. + return ComputeTagSize(fieldNumber) + 10; + } + } + + /// + /// Compute the number of bytes that would be needed to encode a + /// fixed64 field, including the tag. + /// + public static int ComputeFixed64Size(int fieldNumber, long value) { + return ComputeTagSize(fieldNumber) + LittleEndian64Size; + } + + /// + /// Compute the number of bytes that would be needed to encode a + /// fixed32 field, including the tag. + /// + public static int ComputeFixed32Size(int fieldNumber, int value) { + return ComputeTagSize(fieldNumber) + LittleEndian32Size; + } + + /// + /// Compute the number of bytes that would be needed to encode a + /// bool field, including the tag. + /// + public static int ComputeBoolSize(int fieldNumber, bool value) { + return ComputeTagSize(fieldNumber) + 1; + } + + /// + /// Compute the number of bytes that would be needed to encode a + /// string field, including the tag. + /// + public static int ComputeStringSize(int fieldNumber, String value) { + int byteArraySize = Encoding.UTF8.GetByteCount(value); + return ComputeTagSize(fieldNumber) + + ComputeRawVarint32Size((uint)byteArraySize) + + byteArraySize; + } + + /// + /// Compute the number of bytes that would be needed to encode a + /// group field, including the tag. + /// + public static int ComputeGroupSize(int fieldNumber, IMessage value) { + return ComputeTagSize(fieldNumber) * 2 + value.SerializedSize; + } + + /// + /// Compute the number of bytes that would be needed to encode a + /// group field represented by an UnknownFieldSet, including the tag. + /// + public static int ComputeUnknownGroupSize(int fieldNumber, + UnknownFieldSet value) { + return ComputeTagSize(fieldNumber) * 2 + value.SerializedSize; + } + + /// + /// Compute the number of bytes that would be needed to encode an + /// embedded message field, including the tag. + /// + public static int ComputeMessageSize(int fieldNumber, IMessage value) { + int size = value.SerializedSize; + return ComputeTagSize(fieldNumber) + ComputeRawVarint32Size((uint)size) + size; + } + + /// + /// Compute the number of bytes that would be needed to encode a + /// bytes field, including the tag. + /// + public static int ComputeBytesSize(int fieldNumber, ByteString value) { + return ComputeTagSize(fieldNumber) + + ComputeRawVarint32Size((uint)value.Length) + + value.Length; + } + + /// + /// Compute the number of bytes that would be needed to encode a + /// uint32 field, including the tag. + /// + public static int ComputeUInt32Size(int fieldNumber, uint value) { + return ComputeTagSize(fieldNumber) + ComputeRawVarint32Size(value); + } + + /// + /// Compute the number of bytes that would be needed to encode a + /// enum field, including the tag. The caller is responsible for + /// converting the enum value to its numeric value. + /// + public static int ComputeEnumSize(int fieldNumber, int value) { + return ComputeTagSize(fieldNumber) + ComputeRawVarint32Size((uint)value); + } + + /// + /// Compute the number of bytes that would be needed to encode an + /// sfixed32 field, including the tag. + /// + public static int ComputeSFixed32Size(int fieldNumber, int value) { + return ComputeTagSize(fieldNumber) + LittleEndian32Size; + } + + /// + /// Compute the number of bytes that would be needed to encode an + /// sfixed64 field, including the tag. + /// + public static int ComputeSFixed64Size(int fieldNumber, long value) { + return ComputeTagSize(fieldNumber) + LittleEndian64Size; + } + + /// + /// Compute the number of bytes that would be needed to encode an + /// sint32 field, including the tag. + /// + public static int ComputeSInt32Size(int fieldNumber, int value) { + return ComputeTagSize(fieldNumber) + + ComputeRawVarint32Size(EncodeZigZag32(value)); + } + + /// + /// Compute the number of bytes that would be needed to encode an + /// sint64 field, including the tag. + /// + public static int ComputeSInt64Size(int fieldNumber, long value) { + return ComputeTagSize(fieldNumber) + + ComputeRawVarint64Size(EncodeZigZag64(value)); + } + + /* + * Compute the number of bytes that would be needed to encode a + * MessageSet extension to the stream. For historical reasons, + * the wire format differs from normal fields. + */ + /// + /// Compute the number of bytes that would be needed to encode a + /// MessageSet extension to the stream. For historical reasons, + /// the wire format differs from normal fields. + /// + public static int ComputeMessageSetExtensionSize(int fieldNumber, IMessage value) { + return ComputeTagSize(WireFormat.MessageSetField.Item) * 2 + + ComputeUInt32Size(WireFormat.MessageSetField.TypeID, (uint) fieldNumber) + + ComputeMessageSize(WireFormat.MessageSetField.Message, value); + } + + /// + /// Compute the number of bytes that would be needed to encode an + /// unparsed MessageSet extension field to the stream. For + /// historical reasons, the wire format differs from normal fields. + /// + public static int ComputeRawMessageSetExtensionSize(int fieldNumber, ByteString value) { + return ComputeTagSize(WireFormat.MessageSetField.Item) * 2 + + ComputeUInt32Size(WireFormat.MessageSetField.TypeID, (uint) fieldNumber) + + ComputeBytesSize(WireFormat.MessageSetField.Message, value); + } + + /// + /// Compute the number of bytes that would be needed to encode a varint. + /// + public static int ComputeRawVarint32Size(uint value) { + if ((value & (0xffffffff << 7)) == 0) return 1; + if ((value & (0xffffffff << 14)) == 0) return 2; + if ((value & (0xffffffff << 21)) == 0) return 3; + if ((value & (0xffffffff << 28)) == 0) return 4; + return 5; + } + + /// + /// Compute the number of bytes that would be needed to encode a varint. + /// + public static int ComputeRawVarint64Size(ulong value) { + if ((value & (0xffffffffffffffffL << 7)) == 0) return 1; + if ((value & (0xffffffffffffffffL << 14)) == 0) return 2; + if ((value & (0xffffffffffffffffL << 21)) == 0) return 3; + if ((value & (0xffffffffffffffffL << 28)) == 0) return 4; + if ((value & (0xffffffffffffffffL << 35)) == 0) return 5; + if ((value & (0xffffffffffffffffL << 42)) == 0) return 6; + if ((value & (0xffffffffffffffffL << 49)) == 0) return 7; + if ((value & (0xffffffffffffffffL << 56)) == 0) return 8; + if ((value & (0xffffffffffffffffL << 63)) == 0) return 9; + return 10; + } + + + /* + * Compute the number of bytes that would be needed to encode a + * field of arbitrary type, including tag, to the stream. + * + * @param type The field's type. + * @param number The field's number. + * @param value Object representing the field's value. Must be of the exact + * type which would be returned by + * {@link Message#getField(Descriptors.FieldDescriptor)} for + * this field. + */ + public static int ComputeFieldSize(Descriptors.FieldDescriptor.Type fieldType, int fieldNumber, Object value) { + switch (fieldType) { + case Descriptors.FieldDescriptor.Type.Double: return ComputeDoubleSize(fieldNumber, (double)value); + case Descriptors.FieldDescriptor.Type.Float: return ComputeFloatSize(fieldNumber, (float)value); + case Descriptors.FieldDescriptor.Type.Int64: return ComputeInt64Size(fieldNumber, (long)value); + case Descriptors.FieldDescriptor.Type.UInt64: return ComputeUInt64Size(fieldNumber, (ulong)value); + case Descriptors.FieldDescriptor.Type.Int32: return ComputeInt32Size(fieldNumber, (int)value); + case Descriptors.FieldDescriptor.Type.Fixed64: return ComputeFixed64Size(fieldNumber, (long)value); + case Descriptors.FieldDescriptor.Type.Fixed32: return ComputeFixed32Size(fieldNumber, (int)value); + case Descriptors.FieldDescriptor.Type.Bool: return ComputeBoolSize(fieldNumber, (bool)value); + case Descriptors.FieldDescriptor.Type.String: return ComputeStringSize(fieldNumber, (string)value); + case Descriptors.FieldDescriptor.Type.Group: return ComputeGroupSize(fieldNumber, (IMessage)value); + case Descriptors.FieldDescriptor.Type.Message: return ComputeMessageSize(fieldNumber, (IMessage)value); + case Descriptors.FieldDescriptor.Type.Bytes: return ComputeBytesSize(fieldNumber, (ByteString)value); + case Descriptors.FieldDescriptor.Type.UInt32: return ComputeUInt32Size(fieldNumber, (uint)value); + case Descriptors.FieldDescriptor.Type.SFixed32: return ComputeSFixed32Size(fieldNumber, (int)value); + case Descriptors.FieldDescriptor.Type.SFixed64: return ComputeSFixed64Size(fieldNumber, (long)value); + case Descriptors.FieldDescriptor.Type.SInt32: return ComputeSInt32Size(fieldNumber, (int)value); + case Descriptors.FieldDescriptor.Type.SInt64: return ComputeSInt64Size(fieldNumber, (long)value); + case Descriptors.FieldDescriptor.Type.Enum: return ComputeEnumSize(fieldNumber, ((Descriptors.EnumValueDescriptor)value).Number); + default: + throw new ArgumentOutOfRangeException("Invalid field type " + fieldType); + } + } + + /// + /// Compute the number of bytes that would be needed to encode a tag. + /// + public static int ComputeTagSize(int fieldNumber) { + return ComputeRawVarint32Size(WireFormat.MakeTag(fieldNumber, 0)); + } + #endregion + + /// + /// Encode a 32-bit value with ZigZag encoding. + /// + /// + /// ZigZag encodes signed integers into values that can be efficiently + /// encoded with varint. (Otherwise, negative values must be + /// sign-extended to 64 bits to be varint encoded, thus always taking + /// 10 bytes on the wire.) + /// + public static uint EncodeZigZag32(int n) { + // Note: the right-shift must be arithmetic + return (uint)((n << 1) ^ (n >> 31)); + } + + /// + /// Encode a 64-bit value with ZigZag encoding. + /// + /// + /// ZigZag encodes signed integers into values that can be efficiently + /// encoded with varint. (Otherwise, negative values must be + /// sign-extended to 64 bits to be varint encoded, thus always taking + /// 10 bytes on the wire.) + /// + public static ulong EncodeZigZag64(long n) { + return (ulong)((n << 1) ^ (n >> 63)); + } + + private void RefreshBuffer() { + if (output == null) { + // We're writing to a single buffer. + throw new OutOfSpaceException(); + } + + // Since we have an output stream, this is our buffer + // and buffer offset == 0 + output.Write(buffer, 0, position); + position = 0; + } + + /// + /// Indicates that a CodedOutputStream wrapping a flat byte array + /// ran out of space. + /// + public class OutOfSpaceException : IOException { + internal OutOfSpaceException() + : base("CodedOutputStream was writing to a flat byte array and ran out of space.") { + } + } + + public void Flush() { + if (output != null) { + RefreshBuffer(); + } + } + } +} \ No newline at end of file diff --git a/csharp/ProtocolBuffers/Descriptors.cs b/csharp/ProtocolBuffers/Descriptors.cs new file mode 100644 index 00000000..ae09ee5a --- /dev/null +++ b/csharp/ProtocolBuffers/Descriptors.cs @@ -0,0 +1,39 @@ + +using System; + +namespace Google.ProtocolBuffers { + public class Descriptors { + public class Descriptor { + } + public class FieldDescriptor { + public enum Type { + Double, + Float, + Int64, + UInt64, + Int32, + Fixed64, + Fixed32, + Bool, + String, + Group, + Message, + Bytes, + UInt32, + SFixed32, + SFixed64, + SInt32, + SInt64, + Enum + } + } + + public class EnumValueDescriptor + { + public int Number + { + get { throw new NotImplementedException(); } + } + } + } +} diff --git a/csharp/ProtocolBuffers/ExtensionRegistry.cs b/csharp/ProtocolBuffers/ExtensionRegistry.cs new file mode 100644 index 00000000..54d86ad7 --- /dev/null +++ b/csharp/ProtocolBuffers/ExtensionRegistry.cs @@ -0,0 +1,5 @@ + +namespace Google.ProtocolBuffers { + public class ExtensionRegistry { + } +} diff --git a/csharp/ProtocolBuffers/IBuilder.cs b/csharp/ProtocolBuffers/IBuilder.cs new file mode 100644 index 00000000..ebd6b408 --- /dev/null +++ b/csharp/ProtocolBuffers/IBuilder.cs @@ -0,0 +1,227 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.IO; + +namespace Google.ProtocolBuffers { + + /// + /// Interface implemented by Protocol Message builders. + /// TODO(jonskeet): Consider "SetXXX" methods returning the builder, as well as the properties. + /// + /// Type of message + public interface IBuilder where T : IMessage { + /// + /// Resets all fields to their default values. + /// + IBuilder Clear(); + + /// + /// Merge the specified other message into the message being + /// built. Merging occurs as follows. For each field: + /// For singular primitive fields, if the field is set in , + /// then 's value overwrites the value in this message. + /// For singular message fields, if the field is set in , + /// it is merged into the corresponding sub-message of this message using the same + /// merging rules. + /// For repeated fields, the elements in are concatenated + /// with the elements in this message. + /// + /// + /// + IBuilder MergeFrom(IMessage other); + + /// + /// Constructs the final message. Once this is called, this Builder instance + /// is no longer valid, and calling any other method may throw a + /// NullReferenceException. If you need to continue working with the builder + /// after calling Build, call Clone first. + /// + /// the message + /// is missing one or more required fields; use BuildPartial to bypass + /// this check + IMessage Build(); + + /// + /// Like Build(), but does not throw an exception if the message is missing + /// required fields. Instead, a partial message is returned. + /// + /// + IMessage BuildPartial(); + + /// + /// Clones this builder. + /// TODO(jonskeet): Explain depth of clone. + /// + IBuilder Clone(); + + /// + /// Returns true iff all required fields in the message and all + /// embedded messages are set. + /// + bool Initialized { get; } + + /// + /// Parses a message of this type from the input and merges it with this + /// message, as if using MergeFrom(IMessage<T>). + /// + /// + /// Warning: This does not verify that all required fields are present + /// in the input message. If you call Build() without setting all + /// required fields, it will throw an UninitializedMessageException. + /// There are a few good ways to deal with this: + /// + /// Call Initialized to verify to verify that all required fields are + /// set before building. + /// Parse the message separately using one of the static ParseFrom + /// methods, then use MergeFrom(IMessage<T>) to merge it with + /// this one. ParseFrom will throw an InvalidProtocolBufferException + /// (an IOException) if some required fields are missing. + /// Use BuildPartial to build, which ignores missing required fields. + /// + /// + IBuilder MergeFrom(CodedInputStream input); + + /// + /// Like MergeFrom(CodedInputStream), but also parses extensions. + /// The extensions that you want to be able to parse must be registered + /// in . Extensions not in the registry + /// will be treated as unknown fields. + /// + IBuilder MergeFrom(CodedInputStream input, ExtensionRegistry extensionRegistry); + + /// + /// Get the message's type's descriptor. + /// + /// + Descriptors.Descriptor DescriptorForType { get; } + + /// + /// Get's the message's type's default instance. + /// + /// + IMessage DefaultInstanceForType { get; } + + /// + /// Behaves like the equivalent property in IMessage<T>. + /// The returned map may or may not reflect future changes to the builder. + /// Either way, the returned map is unmodifiable. + /// + IDictionary AllFields { get; } + + /// + /// Create a builder for messages of the appropriate type for the given field. + /// Messages built with this can then be passed to the various mutation properties + /// and methods. + /// + /// + /// + /// + IBuilder NewBuilderForField(Descriptors.FieldDescriptor field) + where TField : IMessage; + + /// + /// + /// + bool HasField(Descriptors.FieldDescriptor field); + + /// + /// Allows getting and setting of a field. + /// + /// + /// + /// + object this[Descriptors.FieldDescriptor field] { get; set; } + + /// + /// Clears the field. This is exactly equivalent to calling the generated + /// Clear method corresponding to the field. + /// + /// + /// + IBuilder ClearField(Descriptors.FieldDescriptor field); + + /// + /// + /// + /// + /// + int GetRepeatedFieldCount(Descriptors.FieldDescriptor field); + + + /// + /// Allows getting and setting of a repeated field value. + /// + /// + object this[Descriptors.FieldDescriptor field, int index] { get; set; } + + /// + /// Appends the given value as a new element for the specified repeated field. + /// + /// the field is not a repeated field, + /// the field does not belong to this builder's type, or the value is + /// of the incorrect type + /// + IBuilder AddRepeatedField(Descriptors.FieldDescriptor field, object value); + + /// + /// + /// + UnknownFieldSet UnknownFields { get; set; } + + /// + /// Merge some unknown fields into the set for this message. + /// + IBuilder MergeUnknownFields(UnknownFieldSet unknownFields); + + #region Convenience methods + // TODO(jonskeet): Implement these as extension methods? + + /// + /// Parse as a message of this type and merge + /// it with the message being built. This is just a small wrapper around + /// MergeFrom(CodedInputStream). + /// + IBuilder MergeFrom(ByteString data); + + /// + /// Parse as a message of this type and merge + /// it with the message being built. This is just a small wrapper around + /// MergeFrom(CodedInputStream, ExtensionRegistry). + /// + IBuilder MergeFrom(ByteString data, ExtensionRegistry extensionRegistry); + + /// + /// Parse as a message of this type and merge + /// it with the message being built. This is just a small wrapper around + /// MergeFrom(CodedInputStream). + /// + IBuilder MergeFrom(byte[] data); + + /// + /// Parse as a message of this type and merge + /// it with the message being built. This is just a small wrapper around + /// MergeFrom(CodedInputStream, ExtensionRegistry). + /// + IBuilder MergeFrom(byte[] data, ExtensionRegistry extensionRegistry); + + /// + /// Parse as a message of this type and merge + /// it with the message being built. This is just a small wrapper around + /// MergeFrom(CodedInputStream). Note that this method always reads + /// the entire input (unless it throws an exception). If you want it to + /// stop earlier, you will need to wrap the input in a wrapper + /// stream which limits reading. Despite usually reading the entire + /// stream, this method never closes the stream. + /// + IBuilder MergeFrom(Stream input); + + /// + /// Parse as a message of this type and merge + /// it with the message being built. This is just a small wrapper around + /// MergeFrom(CodedInputStream, ExtensionRegistry). + /// + IBuilder MergeFrom(Stream input, ExtensionRegistry extensionRegistry); + #endregion + } +} diff --git a/csharp/ProtocolBuffers/IMessage.cs b/csharp/ProtocolBuffers/IMessage.cs new file mode 100644 index 00000000..6a41d78e --- /dev/null +++ b/csharp/ProtocolBuffers/IMessage.cs @@ -0,0 +1,162 @@ +using System; +using System.Collections.Generic; +using System.IO; + +namespace Google.ProtocolBuffers { + + /// + /// Non-generic interface. + /// TODO(jonskeet): Do we want or need this? + /// + public interface IMessage { + void WriteTo(CodedOutputStream output); + int SerializedSize { get; } + } + + /// + /// Interface implemented by all Protocol Buffers messages. + /// + public interface IMessage where T : IMessage { + /// + /// Returns the message's type's descriptor. This differs from the + /// Descriptor property of each generated message class in that this + /// method is an abstract method of IMessage whereas Descriptor is + /// a static property of a specific class. They return the same thing. + /// + Descriptors.Descriptor DescriptorForType { get; } + + /// + /// Returns an instance of this message type with all fields set to + /// their default values. This may or may not be a singleton. This differs + /// from the DefaultInstance property of each generated message class in that this + /// method is an abstract method of IMessage whereas DefaultInstance is + /// a static property of a specific class. They return the same thing. + /// + IMessage DefaultInstanceForType { get; } + + /// + /// Returns a collection of all the fields in this message which are set + /// and their corresponding values. A singular ("required" or "optional") + /// field is set iff HasField() returns true for that field. A "repeated" + /// field is set iff GetRepeatedFieldSize() is greater than zero. The + /// values are exactly what would be returned by calling + /// GetField(Descriptors.FieldDescriptor) for each field. The map + /// is guaranteed to be a sorted map, so iterating over it will return fields + /// in order by field number. + /// + IDictionary AllFields { get; } + + /// + /// Returns true if the given field is set. This is exactly equivalent + /// to calling the generated "Has" property corresponding to the field. + /// + /// the field is a repeated field, + /// or it's not a field of this type + bool HasField(Descriptors.FieldDescriptor field); + + /// + /// Obtains the value of the given field, or the default value if + /// it isn't set. For value type fields including enums, the boxed + /// value is returned. For embedded message fields, the sub-message + /// is returned. For repeated fields, an IList<T> is returned. + /// + object this[Descriptors.FieldDescriptor field] { get; } + + /// + /// Returns the number of elements of a repeated field. This is + /// exactly equivalent to calling the generated "Count" property + /// corresponding to the field. + /// + /// the field is not a repeated field, + /// or it's not a field of this type + int GetRepeatedFieldCount(Descriptors.FieldDescriptor field); + + /// + /// Gets an element of a repeated field. For value type fields + /// including enums, the boxed value is returned. For embedded + /// message fields, the sub-message is returned. + /// + /// the field is not a repeated field, + /// or it's not a field of this type + /// the index is out of + /// range for the repeated field's value + object this[Descriptors.FieldDescriptor field, int index] { get; } + + /// + /// Returns the unknown fields for this message. + /// + UnknownFieldSet UnknownFields { get; } + + /// + /// Returns true iff all required fields in the message and all embedded + /// messages are set. + /// + bool Initialized { get; } + + /// + /// Serializes the message and writes it to the given output stream. + /// This does not flush or close the stream. + /// + /// + void WriteTo(CodedOutputStream output); + + /// + /// Returns the number of bytes required to encode this message. + /// The result is only computed on the first call and memoized after that. + /// + int SerializedSize { get; } + + #region Comparison and hashing + /// + /// Compares the specified object with this message for equality. + /// Returns true iff the given object is a message of the same type + /// (as defined by DescriptorForType) and has identical values + /// for all its fields. + /// + bool Equals(object other); + + /// + /// Returns the hash code value for this message. + /// TODO(jonskeet): Specify the hash algorithm, but better than the Java one! + /// + /// + int GetHashCode(); + #endregion + + #region Convenience methods + /// + /// Converts the message to a string in protocol buffer text format. + /// This is just a trivial wrapper around TextFormat.PrintToString. + /// + string ToString(); + + /// + /// Serializes the message to a ByteString. This is a trivial wrapper + /// around WriteTo(CodedOutputStream). + /// + ByteString ToByteString(); + + /// + /// Serializes the message to a byte array. This is a trivial wrapper + /// around WriteTo(CodedOutputStream). + /// + byte[] ToByteArray(); + + /// + /// Serializes the message and writes it to the given stream. + /// This is just a wrapper around WriteTo(CodedOutputStream). This + /// does not flush or close the stream. + /// + /// + void WriteTo(Stream output); + #endregion + + #region Builders + /// + /// Constructs a new builder for a message of the same type as this message. + /// + IBuilder NewBuilderForType(); + #endregion + + } +} diff --git a/csharp/ProtocolBuffers/InvalidProtocolBufferException.cs b/csharp/ProtocolBuffers/InvalidProtocolBufferException.cs new file mode 100644 index 00000000..8b61f9f1 --- /dev/null +++ b/csharp/ProtocolBuffers/InvalidProtocolBufferException.cs @@ -0,0 +1,7 @@ +using System; +using System.IO; + +namespace Google.ProtocolBuffers { + public class InvalidProtocolBufferException : IOException { + } +} diff --git a/csharp/ProtocolBuffers/Properties/AssemblyInfo.cs b/csharp/ProtocolBuffers/Properties/AssemblyInfo.cs new file mode 100644 index 00000000..4653dbe3 --- /dev/null +++ b/csharp/ProtocolBuffers/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("ProtocolBuffers")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("ProtocolBuffers")] +[assembly: AssemblyCopyright("Copyright © 2008")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("279b643d-70e8-47ae-9eb1-500d1c48bab6")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/csharp/ProtocolBuffers/ProtocolBuffers.csproj b/csharp/ProtocolBuffers/ProtocolBuffers.csproj new file mode 100644 index 00000000..196f46d3 --- /dev/null +++ b/csharp/ProtocolBuffers/ProtocolBuffers.csproj @@ -0,0 +1,60 @@ + + + + Debug + AnyCPU + 9.0.21022 + 2.0 + {6908BDCE-D925-43F3-94AC-A531E6DF2591} + Library + Properties + Google.ProtocolBuffers + Google.ProtocolBuffers + v2.0 + 512 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/csharp/ProtocolBuffers/UninitializedMessageException.cs b/csharp/ProtocolBuffers/UninitializedMessageException.cs new file mode 100644 index 00000000..a77499af --- /dev/null +++ b/csharp/ProtocolBuffers/UninitializedMessageException.cs @@ -0,0 +1,6 @@ +using System; + +namespace Google.ProtocolBuffers { + public class UninitializedMessageException : Exception { + } +} diff --git a/csharp/ProtocolBuffers/UnknownFieldSet.cs b/csharp/ProtocolBuffers/UnknownFieldSet.cs new file mode 100644 index 00000000..a9602b3e --- /dev/null +++ b/csharp/ProtocolBuffers/UnknownFieldSet.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Google.ProtocolBuffers { + public class UnknownFieldSet { + public void WriteTo(CodedOutputStream output) { + throw new NotImplementedException(); + } + + public int SerializedSize { get { return 0; } } + } +} diff --git a/csharp/ProtocolBuffers/WireFormat.cs b/csharp/ProtocolBuffers/WireFormat.cs new file mode 100644 index 00000000..df15526a --- /dev/null +++ b/csharp/ProtocolBuffers/WireFormat.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Google.ProtocolBuffers { + public class WireFormat { + public enum WireType { + Varint = 0, + Fixed64 = 1, + LengthDelimited = 2, + StartGroup = 3, + EndGroup = 4, + Fixed32 = 5 + } + + internal class MessageSetField { + internal const int Item = 1; + internal const int TypeID = 2; + internal const int Message = 3; + } + + public static uint MakeTag(int fieldNumber, WireType type) { + + // FIXME + return 0; + } + } +} -- cgit v1.2.3