canopy-network/canopy · error

invalid length at offset %d

Error message

invalid length at offset %d

What it means

When a matching length-delimited (BytesType) field is found, GetRawProtoField reads the value length as a varint. ConsumeVarint returning a negative byte count means the length varint itself is malformed/truncated — the buffer ends inside the varint, so the field's length cannot be determined.

Source

Thrown at lib/codec/codec.go:82

// GetRawProtoField extracts the raw bytes for field number from a proto message
func GetRawProtoField(protoBytes []byte, fieldNumber int) ([]byte, error) {
	var offset int
	// parse the proto bytes to find a field
	for offset < len(protoBytes) {
		// decode the field tag (field number + wire type)
		fieldNum, wireType, tagLen := protowire.ConsumeTag(protoBytes[offset:])
		if tagLen < 0 {
			return nil, fmt.Errorf("invalid tag at offset %d", offset)
		}
		offset += tagLen
		// check if this is the field we're looking for
		if int(fieldNum) == fieldNumber {
			// for length-delimited fields (like messages), we need to read the length
			if wireType == protowire.BytesType {
				// read the length of the field value
				valueLen, lenBytes := protowire.ConsumeVarint(protoBytes[offset:])
				if lenBytes < 0 {
					return nil, fmt.Errorf("invalid length at offset %d", offset)
				}
				// calculate the new offset
				offset += lenBytes
				// extract the field value bytes
				if offset+int(valueLen) > len(protoBytes) {
					return nil, fmt.Errorf("field value exceeds buffer bounds")
				}
				// make buffer to return
				fieldBytes := make([]byte, valueLen)
				// copy into the buffer
				copy(fieldBytes, protoBytes[offset:offset+int(valueLen)])
				// return the value
				return fieldBytes, nil
			} else {
				// for other wire types, consume the value directly
				valueLen := protowire.ConsumeFieldValue(fieldNum, wireType, protoBytes[offset:])
				if valueLen < 0 {
					return nil, fmt.Errorf("invalid field value at offset %d", offset)

View on GitHub (pinned to ee8197d91d)

Solutions

  1. Re-read or re-fetch the complete message bytes; confirm length matches the originally marshaled size.
  2. Validate with proto.Unmarshal on the full buffer before calling GetRawProtoField to detect truncation early.
  3. Fix the code that slices/passes a partial buffer to GetRawProtoField.

Example fix

// before
chunk := buf[:len(buf)-2] // accidental truncation
raw, _ := codec.GetRawProtoField(chunk, 1)
// after
raw, err := codec.GetRawProtoField(buf, 1)
Defensive patterns

Strategy: validation

Validate before calling

var probe pb.Event
if err := proto.Unmarshal(data, &probe); err != nil {
    return fmt.Errorf("truncated/malformed proto bytes: %w", err)
}

Try / catch

raw, err := codec.GetRawProtoField(data, fieldNum)
if err != nil && strings.Contains(err.Error(), "invalid length") {
    return fmt.Errorf("truncated buffer at length prefix: %w", err)
}

Prevention

When it happens

Trigger: GetRawProtoField on a truncated proto buffer where the tag of the target field is present but the varint length prefix is cut off.

Common situations: Partial reads from storage or network producing truncated payloads; off-by-one slicing of the proto bytes; corruption during transport or persistence.

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 canopy-network/canopy@ee8197d91d (2026-09-06). Data as JSON: /api/errors/448a42c6196f691c. Report an issue: GitHub.