OpenNHP/opennhp · error · ErrRuntimePanic

ErrRuntimePanic

ErrRuntimePanic

Error message

!!!recovered from panic: %v
%s

What it means

Device.MsgToPacket wraps any panic raised while converting a message into an NHP packet (assembler setup, crypto, packet construction) into ErrRuntimePanic. The formatted message embeds the panic value plus a debug.Stack() trace via SetExtraError, so a malformed or unexpected message cannot crash the daemon's processing goroutine.

Solutions

  1. Log and inspect the wrapped stack trace (ErrRuntimePanic extra error) to find the panicking line.
  2. Validate MsgData fields (HeaderType, Message non-empty, sizes) before calling MsgToPacket.
  3. Reproduce with the offending packet capture and fix the nil/slice bug at the panicking frame.
  4. Check peer/library version alignment if the panic correlates with a specific sender's messages.
  5. Watch for the mad.Destroy() deferred call on a nil mad when createMsgAssemblerData fails — a known nil-deref pattern in this function.

Example fix

// before
mad, err = d.createMsgAssemblerData(md)
defer mad.Destroy() // panics if mad is nil
if err != nil {
    return nil, err
}
// after
mad, err = d.createMsgAssemblerData(md)
if err != nil {
    return nil, err
}
defer mad.Destroy()
Defensive patterns

Strategy: try-catch

Validate before calling

if md == nil || len(md.Message) == 0 {
    return errors.New("MsgData has no message payload")
}

Type guard

func validMsgData(md *core.MsgData) bool { return md != nil && len(md.Message) > 0 }

Try / catch

mad, err := dev.MsgToPacket(md)
if errors.Is(err, core.ErrRuntimePanic) {
    log.Error("packet processing panic: %v", err) // includes stack via extra error
    return err
}

Prevention

When it happens

Trigger: Any panic inside MsgToPacket's processing path — nil pointer in message assembly (e.g. mad.Destroy deferred on nil mad), index/nil deref on malformed MsgData, or panics from underlying crypto routines on unexpected input.

Common situations: Feeding crafted/corrupt network messages into the device loop; passing MsgData with missing fields (empty Message, zero TransactionId handling edge cases); version skew between endpoints producing unexpected header types.

Understand the failure class

Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.

Related errors


AI-assisted analysis of OpenNHP/opennhp@6e04ca5ff0 (2026-09-07). Data as JSON: /api/errors/75becf380b9d5ac6. Report an issue: GitHub.

Appendix: source

Thrown at nhp/core/device.go:327

						timeout:       d.LocalTransactionTimeout(),
					}
					d.AddLocalTransaction(t)
					log.Debug("AddLocalTransaction:deviceType=%d,HeaderType=%d", d.deviceType, mad.HeaderType)
				}

				// send out fully encrypted packet
				mad.connData.ForwardOutboundPacket(mad.BasePacket)
			}()
		}
	}
}

// Synchronous linear processing.
func (d *Device) MsgToPacket(md *MsgData) (mad *MsgAssemblerData, err error) {
	defer func() {
		if x := recover(); x != nil {
			mad = nil
			err = fmt.Errorf("!!!recovered from panic: %v\n%s", x, string(debug.Stack()))
			ErrRuntimePanic.SetExtraError(err)
			err = ErrRuntimePanic
		}
	}()

	var buf [PacketBufferSize]byte
	md.ExternalPacket = &Packet{
		Buf:        &buf,
		Content:    buf[:],
		HeaderType: md.HeaderType,
	}
	//md.Compress = len(md.Message) > 64 // no gain for compression if size is small
	// use new transaction id if not specified
	if md.TransactionId == 0 {
		md.TransactionId = d.NextCounterIndex()
	}

	// process keepalive separately

View on GitHub (pinned to 6e04ca5ff0)