diff --git a/core/src/peer_record.rs b/core/src/peer_record.rs index b9c2d350e73..5c788e96934 100644 --- a/core/src/peer_record.rs +++ b/core/src/peer_record.rs @@ -70,6 +70,24 @@ impl PeerRecord { Self::from_signed_envelope_impl(envelope, STANDARD_DOMAIN_SEP, STANDARD_PAYLOAD_TYPE) } + /// Attempt to re-construct a [`PeerRecord`] from a [`SignedEnvelope`] using either legacy or + /// standard interop format. + /// + /// Tries the legacy format first and falls back to the standard interop format when the payload + /// type does not match the legacy encoding. This preserves legacy validation semantics while + /// allowing protocol consumers to accept peer records produced during migration. + pub fn from_signed_envelope_legacy_or_interop( + envelope: SignedEnvelope, + ) -> Result { + match Self::from_signed_envelope(envelope.clone()) { + Ok(record) => Ok(record), + Err(FromEnvelopeError::BadPayload( + signed_envelope::ReadPayloadError::UnexpectedPayloadType { .. }, + )) => Self::from_signed_envelope_interop(envelope), + Err(err) => Err(err), + } + } + fn from_signed_envelope_impl( envelope: SignedEnvelope, domain: &str, @@ -250,6 +268,30 @@ mod tests { assert_eq!(reconstructed, record) } + #[test] + fn roundtrip_envelope_legacy_or_interop_legacy() { + let key = Keypair::generate_ed25519(); + + let record = PeerRecord::new(&key, vec![HOME.parse().unwrap()]).unwrap(); + + let envelope = record.to_signed_envelope(); + let reconstructed = PeerRecord::from_signed_envelope_legacy_or_interop(envelope).unwrap(); + + assert_eq!(reconstructed, record) + } + + #[test] + fn roundtrip_envelope_legacy_or_interop_interop() { + let key = Keypair::generate_ed25519(); + + let record = PeerRecord::new_interop(&key, vec![HOME.parse().unwrap()]).unwrap(); + + let envelope = record.to_signed_envelope(); + let reconstructed = PeerRecord::from_signed_envelope_legacy_or_interop(envelope).unwrap(); + + assert_eq!(reconstructed, record) + } + #[test] fn mismatched_signature_legacy() { use quick_protobuf::MessageWrite; diff --git a/protocols/identify/src/protocol.rs b/protocols/identify/src/protocol.rs index c8753d93abf..21f455a33a2 100644 --- a/protocols/identify/src/protocol.rs +++ b/protocols/identify/src/protocol.rs @@ -234,7 +234,8 @@ impl TryFrom for Info { .signedPeerRecord .and_then(|b| { let envelope = SignedEnvelope::from_protobuf_encoding(b.as_ref()).ok()?; - let peer_record = PeerRecord::from_signed_envelope(envelope).ok()?; + let peer_record = + PeerRecord::from_signed_envelope_legacy_or_interop(envelope).ok()?; (peer_record.peer_id() == identify_public_key.to_peer_id()).then_some(( peer_record.addresses().to_vec(), Some(peer_record.into_signed_envelope()), @@ -402,4 +403,28 @@ mod tests { .expect("read to succeed"); assert_eq!(message, parsed_message) } + + #[test] + fn uses_interop_signed_peer_record_addresses() { + let identity = identity::Keypair::generate_ed25519(); + let listen_addr: Multiaddr = "/ip4/192.0.2.1/tcp/1234".parse().unwrap(); + let ignored_addr: Multiaddr = "/ip4/127.0.0.1/tcp/4321".parse().unwrap(); + let signed_peer_record = + PeerRecord::new_interop(&identity, vec![listen_addr.clone()]).unwrap(); + let signed_envelope = signed_peer_record.into_signed_envelope(); + + let info = Info::try_from(proto::Identify { + agentVersion: Some("agent".into()), + listenAddrs: vec![ignored_addr.to_vec()], + observedAddr: None, + protocolVersion: Some("proto".into()), + protocols: vec![], + publicKey: Some(identity.public().encode_protobuf()), + signedPeerRecord: Some(signed_envelope.clone().into_protobuf_encoding()), + }) + .expect("interop signed peer records to be accepted"); + + assert_eq!(info.listen_addrs, vec![listen_addr]); + assert_eq!(info.signed_peer_record, Some(signed_envelope)); + } } diff --git a/protocols/rendezvous/src/codec.rs b/protocols/rendezvous/src/codec.rs index 264afa3a92e..e4f25f81d5d 100644 --- a/protocols/rendezvous/src/codec.rs +++ b/protocols/rendezvous/src/codec.rs @@ -451,9 +451,9 @@ impl TryFrom for Message { .transpose()? .ok_or(ConversionError::MissingNamespace)?, ttl, - record: PeerRecord::from_signed_envelope(SignedEnvelope::from_protobuf_encoding( - &signed_peer_record, - )?)?, + record: PeerRecord::from_signed_envelope_legacy_or_interop( + SignedEnvelope::from_protobuf_encoding(&signed_peer_record)?, + )?, }), proto::Message { type_pb: Some(proto::MessageType::REGISTER_RESPONSE), @@ -494,7 +494,7 @@ impl TryFrom for Message { .map(Namespace::new) .transpose()? .ok_or(ConversionError::MissingNamespace)?, - record: PeerRecord::from_signed_envelope( + record: PeerRecord::from_signed_envelope_legacy_or_interop( SignedEnvelope::from_protobuf_encoding( ®go .signedPeerRecord @@ -642,6 +642,9 @@ mod proto { #[cfg(test)] mod tests { + use libp2p_core::PeerRecord; + use libp2p_identity::Keypair; + use super::*; #[test] @@ -662,4 +665,59 @@ mod tests { assert_eq!(bytes.len(), 8 + 3) } + + #[test] + fn parses_interop_register_records() { + let namespace = Namespace::from_static("foo"); + let record = interop_peer_record(); + let expected_record = record.clone(); + + let message = Message::try_from(proto::Message::from(Message::Register( + NewRegistration::new(namespace.clone(), record, Some(42)), + ))) + .expect("interop register record to parse"); + + assert_eq!( + message, + Message::Register(NewRegistration::new(namespace, expected_record, Some(42))) + ); + } + + #[test] + fn parses_interop_discover_response_records() { + let namespace = Namespace::from_static("foo"); + let record = interop_peer_record(); + let expected_record = record.clone(); + let cookie = Cookie::for_namespace(namespace.clone()); + + let message = Message::try_from(proto::Message::from(Message::DiscoverResponse(Ok(( + vec![Registration { + namespace: namespace.clone(), + record, + ttl: 42, + }], + cookie.clone(), + ))))) + .expect("interop discover response record to parse"); + + assert_eq!( + message, + Message::DiscoverResponse(Ok(( + vec![Registration { + namespace, + record: expected_record, + ttl: 42, + }], + cookie, + ))) + ); + } + + fn interop_peer_record() -> PeerRecord { + PeerRecord::new_interop( + &Keypair::generate_ed25519(), + vec!["/ip4/127.0.0.1/tcp/1234".parse().unwrap()], + ) + .unwrap() + } }