XINCGer/Unity3DTraining · error · InvalidProtocolBufferException

SkipLastField called on an end-group tag, indicating that…

Error message

SkipLastField called on an end-group tag, indicating that the corresponding start-group was missing

What it means

When the pending lastTag has wire type EndGroup, SkipLastField() throws InvalidProtocolBufferException: an end-group tag means the matching start-group was closed (or never opened), so there is no field left to skip. This indicates malformed or desynchronized protobuf wire data.

Solutions

  1. In the tag loop, check for WireFormat.WireType.EndGroup and break/return instead of calling SkipLastField().
  2. Create a fresh CodedInputStream per delimited message; never continue parsing past an end-group tag.
  3. Verify the framing code (length prefixes vs groups) matches how the sender serialized the data.
  4. Catch InvalidProtocolBufferException and reject/log the payload as corrupt rather than resynchronizing.

Example fix

// before
switch (WireFormat.GetTagWireType(tag))
{
    default: input.SkipLastField(); break; // EndGroup falls into default
}
// after
var wireType = WireFormat.GetTagWireType(tag);
if (wireType == WireFormat.WireType.EndGroup) return; // group closed
if (wireType != WireFormat.WireType.StartGroup) input.SkipLastField();
Defensive patterns

Strategy: try-catch

Validate before calling

if (WireFormat.GetTagWireType(tag) == WireFormat.WireType.EndGroup) return; // do not skip

Type guard

bool IsEndGroupTag(int tag) => WireFormat.GetTagWireType(tag) == WireFormat.WireType.EndGroup;

Try / catch

try { input.SkipLastField(); } catch (InvalidProtocolBufferException ex) { logger.LogError(ex, "Malformed wire data: stray end-group tag"); discardCurrentMessage(); }

Prevention

When it happens

Trigger: ReadTag() returned an end-group tag and the caller called SkipLastField() instead of treating it as the group terminator; parsing a stream whose group nesting is corrupted, or concatenating messages where a group end tag bleeds into the next parse session.

Common situations: Reusing a CodedInputStream across message boundaries in a multi-message stream (framing mismatch); hand-written parsers that don't break out of the loop on EndGroup wire type; truncated/corrupt network payloads.

Understand the failure class

Background: "cannot parse invalid wire-format data", "cannot unmarshal", "failed unmarshalling": protobuf unmarshal errors explained — this error's family across 10 libraries.

Related errors


AI-assisted analysis of XINCGer/Unity3DTraining@016f98412e (2026-09-12). Data as JSON: /api/errors/b98dffa75f2e884a. Report an issue: GitHub.

Appendix: source

Thrown at NetWorkAndResources/Socket_Protobuff/Assets/Plugins/Google.Protobuf/CodedInputStream.cs:408

        /// If a caller wishes to skip a group, they should skip the whole group, by calling this method after reading the
        /// start-group tag. This behavior allows callers to call this method on any field they don't understand, correctly
        /// resulting in an error if an end-group tag has not been paired with an earlier start-group tag.
        /// </remarks>
        /// <exception cref="InvalidProtocolBufferException">The last tag was an end-group tag</exception>
        /// <exception cref="InvalidOperationException">The last read operation read to the end of the logical stream</exception>
        public void SkipLastField()
        {
            if (lastTag == 0)
            {
                throw new InvalidOperationException("SkipLastField cannot be called at the end of a stream");
            }
            switch (WireFormat.GetTagWireType(lastTag))
            {
                case WireFormat.WireType.StartGroup:
                    SkipGroup(lastTag);
                    break;
                case WireFormat.WireType.EndGroup:
                    throw new InvalidProtocolBufferException("SkipLastField called on an end-group tag, indicating that the corresponding start-group was missing");
                case WireFormat.WireType.Fixed32:
                    ReadFixed32();
                    break;
                case WireFormat.WireType.Fixed64:
                    ReadFixed64();
                    break;
                case WireFormat.WireType.LengthDelimited:
                    var length = ReadLength();
                    SkipRawBytes(length);
                    break;
                case WireFormat.WireType.Varint:
                    ReadRawVarint32();
                    break;
            }
        }

        private void SkipGroup(uint startGroupTag)
        {

View on GitHub (pinned to 016f98412e)