81

(this is a re-post of a question that I saw in my RSS, but which was deleted by the OP. I've re-added it because I've seen this question asked several times in different places; wiki for "good form")

Suddenly, I receive a ProtoException when deserializing and the message is: unknown wire-type 6

  • What is a wire-type?
  • What are the different wire-type values and their description?
  • I suspect a field is causing the problem, how to debug this?
Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900
  • 10
    A Google library's Exception message sent me here. That's pretty funny... – Zach Smith Nov 27 '17 at 16:37
  • 1
    @Zach was it actually the google library? I know I link from protobuf-net, but that isn't google. It's an independent implementation of a google protocol. – Marc Gravell Nov 27 '17 at 19:13
  • Easy thing to check before you look any further: Are both ends using **matching classes**? Mine were not, in my case because because *I had accidentally made the wrong call to the server*! – Timo Aug 07 '18 at 13:34

8 Answers8

68

First thing to check:

IS THE INPUT DATA PROTOBUF DATA? If you try and parse another format (json, xml, csv, binary-formatter), or simply broken data (an "internal server error" html placeholder text page, for example), then it won't work.


What is a wire-type?

It is a 3-bit flag that tells it (in broad terms; it is only 3 bits after all) what the next data looks like.

Each field in protocol buffers is prefixed by a header that tells it which field (number) it represents, and what type of data is coming next; this "what type of data" is essential to support the case where unanticipated data is in the stream (for example, you've added fields to the data-type at one end), as it lets the serializer know how to read past that data (or store it for round-trip if required).

What are the different wire-type values and their description?

  • 0: variant-length integer (up to 64 bits) - base-128 encoded with the MSB indicating continuation (used as the default for integer types, including enums)
  • 1: 64-bit - 8 bytes of data (used for double, or electively for long/ulong)
  • 2: length-prefixed - first read an integer using variant-length encoding; this tells you how many bytes of data follow (used for strings, byte[], "packed" arrays, and as the default for child objects properties / lists)
  • 3: "start group" - an alternative mechanism for encoding child objects that uses start/end tags - largely deprecated by Google, it is more expensive to skip an entire child-object field since you can't just "seek" past an unexpected object
  • 4: "end group" - twinned with 3
  • 5: 32-bit - 4 bytes of data (used for float, or electively for int/uint and other small integer types)

I suspect a field is causing the problem, how to debug this?

Are you serializing to a file? The most likely cause (in my experience) is that you have overwritten an existing file, but have not truncated it; i.e. it was 200 bytes; you've re-written it, but with only 182 bytes. There are now 18 bytes of garbage on the end of your stream that is tripping it up. Files must be truncated when re-writing protocol buffers. You can do this with FileMode:

using(var file = new FileStream(path, FileMode.Truncate)) {
    // write
}

or alternatively by SetLength after writing your data:

file.SetLength(file.Position);

Other possible cause

You are (accidentally) deserializing a stream into a different type than what was serialized. It's worth double-checking both sides of the conversation to ensure this is not happening.

Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900
  • See the docs here: http://code.google.com/apis/protocolbuffers/docs/encoding.html#structure – Nigel Touch Feb 16 '12 at 19:00
  • @Nigel I'm very familiar with the spec; the problem described here, though, relates to accidental corruption by not trimming a file. – Marc Gravell Feb 16 '12 at 19:14
  • 21
    +1 Nigel, leading protobuf-net's creator to the protobuf specs :), like a boss! I got the exception but it wasn't file related. It was because I had updated one protobuffed object on the client and forgot to publish to the web server. (Not sure if this is worth adding to possible causes) – Joe Apr 26 '12 at 03:44
  • @Joe that shouldn't cause this error; a field that is unexpected is not a problem - the field-header (in fact, the wire-type specifically) contains enough information to skip an unknown field without needing to understand it. – Marc Gravell Apr 26 '12 at 05:44
  • ah, in this particular case, my update meant i had to move one property up the hierarchy and delete it in several child classes. so the new client was passing a serialized object with a new unknown field and a removed expected field. – Joe Apr 26 '12 at 06:04
  • @Joe that still shouldn't ever produce an unknown wire-type; the wire-type is still valid and present in that case - it might cause **other** errors. It probably isn't an important point, of course. – Marc Gravell Apr 26 '12 at 06:21
  • 2
    @MarcGravell just a follow up on your comments. I received exactly this exception when sending side was using a class `class Foo { DateTime OccurredOn { get; set; } }` and receiving side had `class Foo { long OccurredOn { get; set; } }`. Notice the difference between object's types: `DateTime` vs `long`. Not sure if you want to address this, but this supports @Joe findings. I use protobuf-net 2.0.0.668. I fixed it by using same type on both ends (which make sense), but the exception in this case was not related to a file. – oleksii Jun 25 '14 at 13:19
  • @oleksii yeah, that would indeed be an invalid wire type; fair enough. – Marc Gravell Jun 25 '14 at 13:23
  • I’m getting this when deserializing `public List fUnits;` where Unit has subclasses defined with ProtoInclude. If I comment out this field, all is good. It’s not the likely error, and it doesn’t seem to be any of the other things mentioned here. – David Dunham Jul 03 '14 at 21:30
  • @DavidDunham I'd love to help, but that isn't much to go on; is there perhaps a reproducible example I can look at? – Marc Gravell Jul 03 '14 at 22:28
  • @MarcGravell Thanks both for making this and your willingness to help. Since this seems to be entirely different (and so I can format a little better), I made a new question: http://stackoverflow.com/questions/24611339/using-protobuf-net-i-get-an-exception-about-an-unknown-wire-type-with-listsubc – David Dunham Jul 07 '14 at 13:09
  • @MarcGravell I have an old Silverlight client generating some output with ProtoBuf 2.x and am trying to read it back in on a DotNetCore platform with ProtoBuf 3.x. Now the Silverlight app has no proto attributes on the fields (was that allowed back then?), and I'm using [ProtoContract(ImplicitFields = ImplicitFields.AllPublic)] in DotNetCore in absense of adding these attributes by hand. I get the error unknown type trying to deserialize. Differences between proto 2 and 3 here or just doesn't know order to map fields? Thanks. – richardb May 12 '21 at 13:48
  • @richardb I'd really need more specifics to comment; the "no attributes" scenarios supported are very limited, and mostly cover tuple-like types; however, honestly: my advice is "add the attributes". Both protobuf-net v2 and v3 (not to be confused with proto2 and proto3 which are orthogonal) know how to handle fields. Happy to offer guidance here, but being able to see some context would *really help* – Marc Gravell May 12 '21 at 13:51
  • @MarcGravell Changing the Silverlight app is beyond what we can do, and the use-case here is data is being backed up to a file with serialization. These files then loaded in years later. Bit of a legacy that has been inherited. I'll try adding the attributes specifically but code works nicely on newly created serialized data, just not this legacy file from our Silverlight app (which we are retiring). Appreciate the super fast response BTW. – richardb May 12 '21 at 14:25
  • @richardb I didn't say anything about changing the silverlight app; just: it is really hard for me to give a confident answer without more context. I was only talking about adding attributes in the .NET Core version. I would *not* go anywhere near `ImplicitFields` here - it is very unlikely that this matches the defined order. Absolute worst case, it is pretty easy to reverse engineer the layout even if we didn't have a single line of the silverlight code - but I'd need to see payload data. This might be more of an email conversation, honestly. – Marc Gravell May 12 '21 at 14:31
  • @MarcGravell Its ok, I got you. Have added attributes specifically and it is working. Thanks. – richardb May 12 '21 at 14:38
48

Since the stack trace references this StackOverflow question, I thought I'd point out that you can also receive this exception if you (accidentally) deserialize a stream into a different type than what was serialized. So it's worth double-checking both sides of the conversation to ensure this is not happening.

Kirk Woll
  • 76,112
  • 22
  • 180
  • 195
  • 10
    Fair point - and probably - no, definitely - the first thing to check. – Marc Gravell Jun 15 '13 at 22:39
  • 3
    Yeah, that's what I just accidentally did -- changed the type of an existing field. Well, this here is quite an obvious mistake, but still it took me some minutes to realize what I did wrong (required inspecting some diffs of proto files and stuff). Is there a way to have a full log of deserialization? It could quickly give me an idea of where to look for the broken field. – Michael Antipin Dec 23 '13 at 09:37
  • In my case the different type just the same type but not `nullable` – Ahmad Apr 28 '22 at 08:44
11

This can also be caused by an attempt to write more than one protobuf message to a single stream. The solution is to use SerializeWithLengthPrefix and DeserializeWithLengthPrefix.


Why this happens:

The protobuf specification supports a fairly small number of wire-types (the binary storage formats) and data-types (the .NET etc data-types). Additionally, this is not 1:1, nor is is 1:many or many:1 - a single wire-type can be used for multiple data-types, and a single data-type can be encoded via any of multiple wire-types. As a consequence, you cannot fully understand a protobuf fragment unless you already know the scema, so you know how to interpret each value. When you are, say, reading an Int32 data-type, the supported wire-types might be "varint", "fixed32" and "fixed64", where-as when reading a String data-type, the only supported wire-type is "string".

If there is no compatible map between the data-type and wire-type, then the data cannot be read, and this error is raised.

Now let's look at why this occurs in the scenario here:

[ProtoContract]
public class Data1
{
    [ProtoMember(1, IsRequired=true)]
    public int A { get; set; }
}

[ProtoContract]
public class Data2
{
    [ProtoMember(1, IsRequired = true)]
    public string B { get; set; }
}

class Program
{
    static void Main(string[] args)
    {
        var d1 = new Data1 { A = 1};
        var d2 = new Data2 { B = "Hello" };
        var ms = new MemoryStream();
        Serializer.Serialize(ms, d1); 
        Serializer.Serialize(ms, d2);
        ms.Position = 0;
        var d3 = Serializer.Deserialize<Data1>(ms); // This will fail
        var d4 = Serializer.Deserialize<Data2>(ms);
        Console.WriteLine("{0} {1}", d3, d4);
    }
}

In the above, two messages are written directly after each-other. The complication is: protobuf is an appendable format, with append meaning "merge". A protobuf message does not know its own length, so the default way of reading a message is: read until EOF. However, here we have appended two different types. If we read this back, it does not know when we have finished reading the first message, so it keeps reading. When it gets to data from the second message, we find ourselves reading a "string" wire-type, but we are still trying to populate a Data1 instance, for which member 1 is an Int32. There is no map between "string" and Int32, so it explodes.

The *WithLengthPrefix methods allow the serializer to know where each message finishes; so, if we serialize a Data1 and Data2 using the *WithLengthPrefix, then deserialize a Data1 and a Data2 using the *WithLengthPrefix methods, then it correctly splits the incoming data between the two instances, only reading the right value into the right object.

Additionally, when storing heterogeneous data like this, you might want to additionally assign (via *WithLengthPrefix) a different field-number to each class; this provides greater visibility of which type is being deserialized. There is also a method in Serializer.NonGeneric which can then be used to deserialize the data without needing to know in advance what we are deserializing:

// Data1 is "1", Data2 is "2"
Serializer.SerializeWithLengthPrefix(ms, d1, PrefixStyle.Base128, 1);
Serializer.SerializeWithLengthPrefix(ms, d2, PrefixStyle.Base128, 2);
ms.Position = 0;

var lookup = new Dictionary<int,Type> { {1, typeof(Data1)}, {2,typeof(Data2)}};
object obj;
while (Serializer.NonGeneric.TryDeserializeWithLengthPrefix(ms,
    PrefixStyle.Base128, fieldNum => lookup[fieldNum], out obj))
{
    Console.WriteLine(obj); // writes Data1 on the first iteration,
                            // and Data2 on the second iteration
}
Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900
Chriseyre2000
  • 2,053
  • 1
  • 15
  • 28
  • 2
    Hmmmm.... no, that shouldn't cause this error. The protobuf spec is *designed* to be appendable, so writing a second object would result in a perfectly valid and legal protobuf stream, so **should not** cause this error (which refers to a corrupt stream). The significance of `*WithLengthPrefix` is that *without* it, you have absolutely no way of knowing where the first message ends and the second message begins. If you have an example that shows multiple messages being written causing this error, I would very much like to see it. – Marc Gravell Sep 13 '12 at 11:20
  • I was serializing two different objects in sequence to the same stream using two calls to Serialize (it was a key-value pair). Deserializing causes the unknown wire-type error. I had reused the same ids in both objects (each starts from 1). Putting two of the same obect in the same stream leaves the stream at the end after the first deserialize (returning you the data of the second object) the second read gives you a default object. – Chriseyre2000 Sep 13 '12 at 12:01
  • I'll need to repro it, but I strongly suspect that is a different-but-similar message, in particular that the discovered wire-type is not valid for the expected data. Which would make since, because you are essentially merging a string and an int. I'll check later, though. – Marc Gravell Sep 16 '12 at 17:18
  • Thanks - That was a simplified version of what I was doing (The full version writes a list of key-value pairs to a file). It works fine with the WithLengthPrefix version. – Chriseyre2000 Sep 16 '12 at 17:30
  • 1
    I apologise; it does indeed raise the same message, but the context is different. If you don't mind, I'll add some more context to your answer. – Marc Gravell Sep 17 '12 at 09:21
  • I suspect this context is what Joe was seeing in the comments on the other answer. Is it possible for this to output something else like "Wrong type on the wire for field x"? to differentiate the contexts? – James Sep 18 '12 at 09:45
  • I ran into this when only part of my stream of data was a PB message. The rest were bytes of a "container" in which the PB message is encapsulated. When I tried Deserializing from the stream (that contained bytes after the end of the PB message) I got this exception. Using the WithLenghtPrefix sovled the issue – TimothyP Mar 27 '13 at 11:14
  • @MarcGravell: The two versions of the Serializer.DeserializeWithLengthPrefix() method both require a generic parameter. Contrary to Serializer.Deserialize() there is no implementation that accepts the type as a function parameter. This is useful when we only know the type at runtime! Is there any chance you might add Serializer.DeserializeWithLengthPrefix(Type type, Stream source, PrefixStyle style) ? – Philip Atz Feb 19 '21 at 10:30
  • 1
    @Philip try on Serializer.NonGeneric – Marc Gravell Feb 19 '21 at 11:32
5

Previous answers already explain the problem better than I can. I just want to add an even simpler way to reproduce the exception.

This error will also occur simply if the type of a serialized ProtoMember is different from the expected type during deserialization.

For instance if the client sends the following message:

public class DummyRequest
{
    [ProtoMember(1)]
    public int Foo{ get; set; }
}

But what the server deserializes the message into is the following class:

public class DummyRequest
{
    [ProtoMember(1)]
    public string Foo{ get; set; }
}

Then this will result in the for this case slightly misleading error message

ProtoBuf.ProtoException: Invalid wire-type; this usually means you have over-written a file without truncating or setting the length

It will even occur if the property name changed. Let's say the client sent the following instead:

public class DummyRequest
{
    [ProtoMember(1)]
    public int Bar{ get; set; }
}

This will still cause the server to deserialize the int Bar to string Foo which causes the same ProtoBuf.ProtoException.

I hope this helps somebody debugging their application.

Tobias
  • 4,999
  • 7
  • 34
  • 40
1

Also check the obvious that all your subclasses have [ProtoContract] attribute. Sometimes you can miss it when you have rich DTO.

Tomasito
  • 1,864
  • 1
  • 20
  • 43
  • 1
    That would usually present a different error message, though (about an unexpected type) – Marc Gravell Jun 16 '14 at 11:01
  • @MarcGravell Once I have `SerializationException: ProtoBufServiceClient: Error deserializing: Invalid wire-type;` and this page address but sometime, when I don't put attribute I have: `SerializationException: ProtoBufServiceClient: Error deserializing: No serializer defined for type: Foo ---> System.InvalidOperationException: No serializer defined for type: Foo.` In first case I have: `class A { public List bars {get;set;}}`, in second it's single field of type `Foo`. I'll inspect the problem in simple console app. – Tomasito Jun 16 '14 at 12:23
1

I've seen this issue when using the improper Encoding type to convert the bytes in and out of strings.

Need to use Encoding.Default and not Encoding.UTF8.

using (var ms = new MemoryStream())
{
    Serializer.Serialize(ms, obj);
    var bytes = ms.ToArray();
    str = Encoding.Default.GetString(bytes);
}
Micah
  • 10,295
  • 13
  • 66
  • 95
  • 3
    `Need to use Encoding.Default` ... no, just no; you **never** - never - want to use `Encoding.Default` *for anything*. `Encoding` is simply incorrect here; if you need binary as text, use base-64 or similar; **not** an encoding. And encoding is for storing text as binary, *not* the other way around. – Marc Gravell Aug 06 '18 at 16:00
  • i would like all people reading this to know that @MarcGravell is orders of magnitude better than i, please accept his answer, thanks for not downvoting mine. even though its worked for us in production for years in our code that serializes objects out to redis. hopefully that doesnt make mark double shudder. he deserves better. he's earned it. thanks, community – Micah Aug 07 '18 at 18:09
  • did you know redis can talk binary? You can just give redis your byte[] – Marc Gravell Aug 07 '18 at 19:56
  • I had same issue with using Encoding.UTF8. Solved it by converting from and to Base64String. – Jins Peter Sep 21 '20 at 17:51
1

If you are using SerializeWithLengthPrefix, please mind that casting instance to object type breaks the deserialization code and causes ProtoBuf.ProtoException : Invalid wire-type.

using (var ms = new MemoryStream())
{
    var msg = new Message();
    Serializer.SerializeWithLengthPrefix(ms, (object)msg, PrefixStyle.Base128); // Casting msg to object breaks the deserialization code.
    ms.Position = 0;
    Serializer.DeserializeWithLengthPrefix<Message>(ms, PrefixStyle.Base128)
}
Chris Xue
  • 2,317
  • 1
  • 25
  • 21
  • well, I guess that's true... but... why would you do that? btw, there's a non-generic API in `Serializer.NonGeneric` that will work fine with `object` – Marc Gravell Sep 29 '17 at 13:52
1

This happened in my case because I had something like this:

var ms = new MemoryStream();
Serializer.Serialize(ms, batch);

_queue.Add(Convert.ToBase64String(ms.ToArray()));

So basically I was putting a base64 into a queue and then, on the consumer side I had:

var stream = new MemoryStream(Encoding.UTF8.GetBytes(myQueueItem));
var batch = Serializer.Deserialize<List<EventData>>(stream);

So though the type of each myQueueItem was correct, I forgot that I converted a string. The solution was to convert it once more:

var bytes = Convert.FromBase64String(myQueueItem);
var stream = new MemoryStream(bytes);
var batch = Serializer.Deserialize<List<EventData>>(stream);
kamil-mrzyglod
  • 4,948
  • 1
  • 20
  • 29