2018-11-02 10:06:59 +01:00
|
|
|
// Copyright 2018 Parity Technologies (UK) Ltd.
|
|
|
|
//
|
|
|
|
// Permission is hereby granted, free of charge, to any person obtaining a
|
|
|
|
// copy of this software and associated documentation files (the "Software"),
|
|
|
|
// to deal in the Software without restriction, including without limitation
|
|
|
|
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
|
|
|
// and/or sell copies of the Software, and to permit persons to whom the
|
|
|
|
// Software is furnished to do so, subject to the following conditions:
|
|
|
|
//
|
|
|
|
// The above copyright notice and this permission notice shall be included in
|
|
|
|
// all copies or substantial portions of the Software.
|
|
|
|
//
|
|
|
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
|
|
|
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
|
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
|
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
|
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
|
|
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
|
|
|
// DEALINGS IN THE SOFTWARE.
|
|
|
|
|
2023-05-08 16:36:30 +02:00
|
|
|
use crate::protocol::{Identify, InboundPush, Info, OutboundPush, Push, UpgradeError};
|
2023-01-18 10:05:59 +11:00
|
|
|
use either::Either;
|
2022-06-07 13:42:34 +02:00
|
|
|
use futures::future::BoxFuture;
|
2018-11-02 10:06:59 +01:00
|
|
|
use futures::prelude::*;
|
2022-12-13 20:24:31 +00:00
|
|
|
use futures::stream::FuturesUnordered;
|
2021-10-30 12:41:30 +02:00
|
|
|
use futures_timer::Delay;
|
2023-01-18 13:35:07 +11:00
|
|
|
use libp2p_core::upgrade::SelectUpgrade;
|
2023-03-13 01:46:58 +11:00
|
|
|
use libp2p_core::Multiaddr;
|
|
|
|
use libp2p_identity::PeerId;
|
|
|
|
use libp2p_identity::PublicKey;
|
2022-11-17 17:19:36 +00:00
|
|
|
use libp2p_swarm::handler::{
|
|
|
|
ConnectionEvent, DialUpgradeError, FullyNegotiatedInbound, FullyNegotiatedOutbound,
|
2023-05-08 16:36:30 +02:00
|
|
|
ProtocolSupport,
|
2022-11-17 17:19:36 +00:00
|
|
|
};
|
2019-07-04 14:47:59 +02:00
|
|
|
use libp2p_swarm::{
|
2023-05-08 16:36:30 +02:00
|
|
|
ConnectionHandler, ConnectionHandlerEvent, KeepAlive, StreamProtocol, StreamUpgradeError,
|
|
|
|
SubstreamProtocol, SupportedProtocols,
|
2018-11-15 17:41:11 +01:00
|
|
|
};
|
2022-06-07 13:42:34 +02:00
|
|
|
use log::warn;
|
2019-09-02 11:16:52 +02:00
|
|
|
use smallvec::SmallVec;
|
2023-05-08 16:36:30 +02:00
|
|
|
use std::collections::HashSet;
|
|
|
|
use std::{io, task::Context, task::Poll, time::Duration};
|
2018-11-02 10:06:59 +01:00
|
|
|
|
2019-09-02 11:16:52 +02:00
|
|
|
/// Protocol handler for sending and receiving identification requests.
|
|
|
|
///
|
|
|
|
/// Outbound requests are sent periodically. The handler performs expects
|
|
|
|
/// at least one identification request to be answered by the remote before
|
|
|
|
/// permitting the underlying connection to be closed.
|
2022-10-04 01:17:31 +01:00
|
|
|
pub struct Handler {
|
2022-06-26 10:37:29 +02:00
|
|
|
remote_peer_id: PeerId,
|
2022-10-04 01:17:31 +01:00
|
|
|
inbound_identify_push: Option<BoxFuture<'static, Result<Info, UpgradeError>>>,
|
2019-09-02 11:16:52 +02:00
|
|
|
/// Pending events to yield.
|
2021-03-18 12:47:01 +01:00
|
|
|
events: SmallVec<
|
2023-01-18 13:35:07 +11:00
|
|
|
[ConnectionHandlerEvent<Either<Identify, Push<OutboundPush>>, (), Event, io::Error>; 4],
|
2021-03-18 12:47:01 +01:00
|
|
|
>,
|
2018-11-02 10:06:59 +01:00
|
|
|
|
2022-12-13 20:24:31 +00:00
|
|
|
/// Pending identification replies, awaiting being sent.
|
2023-05-10 03:51:47 +02:00
|
|
|
pending_replies: FuturesUnordered<BoxFuture<'static, Result<(), UpgradeError>>>,
|
2022-12-13 20:24:31 +00:00
|
|
|
|
2019-03-11 17:19:50 +01:00
|
|
|
/// Future that fires when we need to identify the node again.
|
2022-05-11 03:18:20 +02:00
|
|
|
trigger_next_identify: Delay,
|
2018-11-02 10:06:59 +01:00
|
|
|
|
2022-05-11 03:18:20 +02:00
|
|
|
/// The interval of `trigger_next_identify`, i.e. the recurrent delay.
|
2021-03-18 12:47:01 +01:00
|
|
|
interval: Duration,
|
2022-12-13 20:24:31 +00:00
|
|
|
|
|
|
|
/// The public key of the local peer.
|
|
|
|
public_key: PublicKey,
|
|
|
|
|
|
|
|
/// Application-specific version of the protocol family used by the peer,
|
|
|
|
/// e.g. `ipfs/1.0.0` or `polkadot/1.0.0`.
|
|
|
|
protocol_version: String,
|
|
|
|
|
|
|
|
/// Name and version of the peer, similar to the `User-Agent` header in
|
|
|
|
/// the HTTP protocol.
|
|
|
|
agent_version: String,
|
|
|
|
|
|
|
|
/// Address observed by or for the remote.
|
|
|
|
observed_addr: Multiaddr,
|
2023-05-08 16:36:30 +02:00
|
|
|
|
|
|
|
local_supported_protocols: SupportedProtocols,
|
|
|
|
remote_supported_protocols: HashSet<StreamProtocol>,
|
|
|
|
external_addresses: HashSet<Multiaddr>,
|
2018-11-02 10:06:59 +01:00
|
|
|
}
|
|
|
|
|
2022-12-13 20:24:31 +00:00
|
|
|
/// An event from `Behaviour` with the information requested by the `Handler`.
|
|
|
|
#[derive(Debug)]
|
2023-05-08 16:36:30 +02:00
|
|
|
pub enum InEvent {
|
|
|
|
AddressesChanged(HashSet<Multiaddr>),
|
|
|
|
Push,
|
2022-12-13 20:24:31 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Event produced by the `Handler`.
|
2018-11-02 10:06:59 +01:00
|
|
|
#[derive(Debug)]
|
2022-11-04 20:40:09 +11:00
|
|
|
#[allow(clippy::large_enum_variant)]
|
2022-10-04 01:17:31 +01:00
|
|
|
pub enum Event {
|
2021-03-18 12:47:01 +01:00
|
|
|
/// We obtained identification information from the remote.
|
2022-10-04 01:17:31 +01:00
|
|
|
Identified(Info),
|
2022-12-13 20:24:31 +00:00
|
|
|
/// We replied to an identification request from the remote.
|
2023-05-10 03:51:47 +02:00
|
|
|
Identification,
|
2021-04-10 19:46:57 +02:00
|
|
|
/// We actively pushed our identification information to the remote.
|
|
|
|
IdentificationPushed,
|
2022-12-13 20:24:31 +00:00
|
|
|
/// Failed to identify the remote, or to reply to an identification request.
|
2023-05-08 10:55:17 +02:00
|
|
|
IdentificationError(StreamUpgradeError<UpgradeError>),
|
2018-11-02 10:06:59 +01:00
|
|
|
}
|
|
|
|
|
2022-10-04 01:17:31 +01:00
|
|
|
impl Handler {
|
2022-12-13 20:24:31 +00:00
|
|
|
/// Creates a new `Handler`.
|
2023-05-08 16:36:30 +02:00
|
|
|
#[allow(clippy::too_many_arguments)]
|
2022-12-13 20:24:31 +00:00
|
|
|
pub fn new(
|
|
|
|
initial_delay: Duration,
|
|
|
|
interval: Duration,
|
|
|
|
remote_peer_id: PeerId,
|
|
|
|
public_key: PublicKey,
|
|
|
|
protocol_version: String,
|
|
|
|
agent_version: String,
|
|
|
|
observed_addr: Multiaddr,
|
2023-05-08 16:36:30 +02:00
|
|
|
external_addresses: HashSet<Multiaddr>,
|
2022-12-13 20:24:31 +00:00
|
|
|
) -> Self {
|
2022-10-04 01:17:31 +01:00
|
|
|
Self {
|
2022-06-26 10:37:29 +02:00
|
|
|
remote_peer_id,
|
2022-06-07 13:42:34 +02:00
|
|
|
inbound_identify_push: Default::default(),
|
2019-09-02 11:16:52 +02:00
|
|
|
events: SmallVec::new(),
|
2022-12-13 20:24:31 +00:00
|
|
|
pending_replies: FuturesUnordered::new(),
|
2022-05-11 03:18:20 +02:00
|
|
|
trigger_next_identify: Delay::new(initial_delay),
|
2021-03-18 12:47:01 +01:00
|
|
|
interval,
|
2022-12-13 20:24:31 +00:00
|
|
|
public_key,
|
|
|
|
protocol_version,
|
|
|
|
agent_version,
|
|
|
|
observed_addr,
|
2023-05-08 16:36:30 +02:00
|
|
|
local_supported_protocols: SupportedProtocols::default(),
|
|
|
|
remote_supported_protocols: HashSet::default(),
|
|
|
|
external_addresses,
|
2018-11-02 10:06:59 +01:00
|
|
|
}
|
|
|
|
}
|
2018-11-15 17:41:11 +01:00
|
|
|
|
2022-11-17 17:19:36 +00:00
|
|
|
fn on_fully_negotiated_inbound(
|
2019-09-02 11:16:52 +02:00
|
|
|
&mut self,
|
2022-11-17 17:19:36 +00:00
|
|
|
FullyNegotiatedInbound {
|
|
|
|
protocol: output, ..
|
|
|
|
}: FullyNegotiatedInbound<
|
|
|
|
<Self as ConnectionHandler>::InboundProtocol,
|
|
|
|
<Self as ConnectionHandler>::InboundOpenInfo,
|
|
|
|
>,
|
2019-09-02 11:16:52 +02:00
|
|
|
) {
|
2021-03-18 12:47:01 +01:00
|
|
|
match output {
|
2023-01-23 23:31:30 +11:00
|
|
|
future::Either::Left(substream) => {
|
2023-05-08 16:36:30 +02:00
|
|
|
let info = self.build_info();
|
|
|
|
|
2023-05-10 03:51:47 +02:00
|
|
|
self.pending_replies
|
|
|
|
.push(crate::protocol::send(substream, info).boxed());
|
2022-12-13 20:24:31 +00:00
|
|
|
}
|
2023-01-23 23:31:30 +11:00
|
|
|
future::Either::Right(fut) => {
|
2022-06-07 13:42:34 +02:00
|
|
|
if self.inbound_identify_push.replace(fut).is_some() {
|
|
|
|
warn!(
|
2022-06-26 10:37:29 +02:00
|
|
|
"New inbound identify push stream from {} while still \
|
|
|
|
upgrading previous one. Replacing previous with new.",
|
|
|
|
self.remote_peer_id,
|
2022-06-07 13:42:34 +02:00
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
2021-03-18 12:47:01 +01:00
|
|
|
}
|
2018-11-15 17:41:11 +01:00
|
|
|
}
|
|
|
|
|
2022-11-17 17:19:36 +00:00
|
|
|
fn on_fully_negotiated_outbound(
|
2018-11-02 10:06:59 +01:00
|
|
|
&mut self,
|
2022-11-17 17:19:36 +00:00
|
|
|
FullyNegotiatedOutbound {
|
|
|
|
protocol: output, ..
|
|
|
|
}: FullyNegotiatedOutbound<
|
|
|
|
<Self as ConnectionHandler>::OutboundProtocol,
|
|
|
|
<Self as ConnectionHandler>::OutboundOpenInfo,
|
|
|
|
>,
|
2018-11-02 10:06:59 +01:00
|
|
|
) {
|
2021-03-18 12:47:01 +01:00
|
|
|
match output {
|
2023-01-23 23:31:30 +11:00
|
|
|
future::Either::Left(remote_info) => {
|
2023-05-08 16:36:30 +02:00
|
|
|
self.update_supported_protocols_for_remote(&remote_info);
|
2022-10-04 01:17:31 +01:00
|
|
|
self.events
|
|
|
|
.push(ConnectionHandlerEvent::Custom(Event::Identified(
|
|
|
|
remote_info,
|
|
|
|
)));
|
2021-03-18 12:47:01 +01:00
|
|
|
}
|
2023-01-23 23:31:30 +11:00
|
|
|
future::Either::Right(()) => self
|
2022-10-04 01:17:31 +01:00
|
|
|
.events
|
|
|
|
.push(ConnectionHandlerEvent::Custom(Event::IdentificationPushed)),
|
2021-03-18 12:47:01 +01:00
|
|
|
}
|
2018-11-02 10:06:59 +01:00
|
|
|
}
|
|
|
|
|
2022-11-17 17:19:36 +00:00
|
|
|
fn on_dial_upgrade_error(
|
2019-09-02 11:16:52 +02:00
|
|
|
&mut self,
|
2022-11-17 17:19:36 +00:00
|
|
|
DialUpgradeError { error: err, .. }: DialUpgradeError<
|
|
|
|
<Self as ConnectionHandler>::OutboundOpenInfo,
|
|
|
|
<Self as ConnectionHandler>::OutboundProtocol,
|
2019-09-02 11:16:52 +02:00
|
|
|
>,
|
|
|
|
) {
|
2023-05-08 10:55:17 +02:00
|
|
|
let err = err.map_upgrade_err(|e| e.into_inner());
|
2022-10-04 01:17:31 +01:00
|
|
|
self.events
|
|
|
|
.push(ConnectionHandlerEvent::Custom(Event::IdentificationError(
|
|
|
|
err,
|
|
|
|
)));
|
2022-05-11 03:18:20 +02:00
|
|
|
self.trigger_next_identify.reset(self.interval);
|
2018-11-02 10:06:59 +01:00
|
|
|
}
|
2023-05-08 16:36:30 +02:00
|
|
|
|
|
|
|
fn build_info(&mut self) -> Info {
|
|
|
|
Info {
|
|
|
|
public_key: self.public_key.clone(),
|
|
|
|
protocol_version: self.protocol_version.clone(),
|
|
|
|
agent_version: self.agent_version.clone(),
|
|
|
|
listen_addrs: Vec::from_iter(self.external_addresses.iter().cloned()),
|
|
|
|
protocols: Vec::from_iter(self.local_supported_protocols.iter().cloned()),
|
|
|
|
observed_addr: self.observed_addr.clone(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn update_supported_protocols_for_remote(&mut self, remote_info: &Info) {
|
|
|
|
let new_remote_protocols = HashSet::from_iter(remote_info.protocols.clone());
|
|
|
|
|
|
|
|
let remote_added_protocols = new_remote_protocols
|
|
|
|
.difference(&self.remote_supported_protocols)
|
|
|
|
.cloned()
|
|
|
|
.collect::<HashSet<_>>();
|
|
|
|
let remote_removed_protocols = self
|
|
|
|
.remote_supported_protocols
|
|
|
|
.difference(&new_remote_protocols)
|
|
|
|
.cloned()
|
|
|
|
.collect::<HashSet<_>>();
|
|
|
|
|
|
|
|
if !remote_added_protocols.is_empty() {
|
|
|
|
self.events
|
|
|
|
.push(ConnectionHandlerEvent::ReportRemoteProtocols(
|
|
|
|
ProtocolSupport::Added(remote_added_protocols),
|
|
|
|
));
|
|
|
|
}
|
|
|
|
|
|
|
|
if !remote_removed_protocols.is_empty() {
|
|
|
|
self.events
|
|
|
|
.push(ConnectionHandlerEvent::ReportRemoteProtocols(
|
|
|
|
ProtocolSupport::Removed(remote_removed_protocols),
|
|
|
|
));
|
|
|
|
}
|
|
|
|
|
|
|
|
self.remote_supported_protocols = new_remote_protocols;
|
|
|
|
}
|
2022-11-17 17:19:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl ConnectionHandler for Handler {
|
2022-12-13 20:24:31 +00:00
|
|
|
type InEvent = InEvent;
|
2022-11-17 17:19:36 +00:00
|
|
|
type OutEvent = Event;
|
|
|
|
type Error = io::Error;
|
2022-12-13 20:24:31 +00:00
|
|
|
type InboundProtocol = SelectUpgrade<Identify, Push<InboundPush>>;
|
2023-01-18 13:35:07 +11:00
|
|
|
type OutboundProtocol = Either<Identify, Push<OutboundPush>>;
|
2022-11-17 17:19:36 +00:00
|
|
|
type OutboundOpenInfo = ();
|
|
|
|
type InboundOpenInfo = ();
|
|
|
|
|
|
|
|
fn listen_protocol(&self) -> SubstreamProtocol<Self::InboundProtocol, Self::InboundOpenInfo> {
|
2022-12-13 20:24:31 +00:00
|
|
|
SubstreamProtocol::new(SelectUpgrade::new(Identify, Push::inbound()), ())
|
2022-11-17 17:19:36 +00:00
|
|
|
}
|
|
|
|
|
2023-05-08 16:36:30 +02:00
|
|
|
fn on_behaviour_event(&mut self, event: Self::InEvent) {
|
|
|
|
match event {
|
|
|
|
InEvent::AddressesChanged(addresses) => {
|
|
|
|
self.external_addresses = addresses;
|
|
|
|
}
|
|
|
|
InEvent::Push => {
|
|
|
|
let info = self.build_info();
|
2022-12-13 20:24:31 +00:00
|
|
|
self.events
|
|
|
|
.push(ConnectionHandlerEvent::OutboundSubstreamRequest {
|
2023-01-18 13:35:07 +11:00
|
|
|
protocol: SubstreamProtocol::new(Either::Right(Push::outbound(info)), ()),
|
2022-12-13 20:24:31 +00:00
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
2022-11-17 17:19:36 +00:00
|
|
|
}
|
2018-11-02 10:06:59 +01:00
|
|
|
|
2019-01-30 16:37:34 +01:00
|
|
|
fn connection_keep_alive(&self) -> KeepAlive {
|
2023-05-08 11:31:25 +02:00
|
|
|
if self.inbound_identify_push.is_some() {
|
|
|
|
return KeepAlive::Yes;
|
|
|
|
}
|
|
|
|
|
|
|
|
if !self.pending_replies.is_empty() {
|
|
|
|
return KeepAlive::Yes;
|
|
|
|
}
|
|
|
|
|
|
|
|
KeepAlive::No
|
2019-01-04 12:02:39 +01:00
|
|
|
}
|
|
|
|
|
2020-07-27 20:27:33 +00:00
|
|
|
fn poll(
|
|
|
|
&mut self,
|
|
|
|
cx: &mut Context<'_>,
|
|
|
|
) -> Poll<
|
2022-10-04 01:17:31 +01:00
|
|
|
ConnectionHandlerEvent<Self::OutboundProtocol, Self::OutboundOpenInfo, Event, Self::Error>,
|
2018-11-02 10:06:59 +01:00
|
|
|
> {
|
2023-05-08 16:36:30 +02:00
|
|
|
if let Some(event) = self.events.pop() {
|
|
|
|
return Poll::Ready(event);
|
2018-11-02 10:06:59 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// Poll the future that fires when we need to identify the node again.
|
2023-05-08 16:36:30 +02:00
|
|
|
if let Poll::Ready(()) = self.trigger_next_identify.poll_unpin(cx) {
|
|
|
|
self.trigger_next_identify.reset(self.interval);
|
|
|
|
let ev = ConnectionHandlerEvent::OutboundSubstreamRequest {
|
|
|
|
protocol: SubstreamProtocol::new(Either::Left(Identify), ()),
|
|
|
|
};
|
|
|
|
return Poll::Ready(ev);
|
2022-06-07 13:42:34 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if let Some(Poll::Ready(res)) = self
|
|
|
|
.inbound_identify_push
|
|
|
|
.as_mut()
|
|
|
|
.map(|f| f.poll_unpin(cx))
|
|
|
|
{
|
|
|
|
self.inbound_identify_push.take();
|
|
|
|
|
|
|
|
if let Ok(info) = res {
|
2023-05-08 16:36:30 +02:00
|
|
|
self.update_supported_protocols_for_remote(&info);
|
2022-10-04 01:17:31 +01:00
|
|
|
return Poll::Ready(ConnectionHandlerEvent::Custom(Event::Identified(info)));
|
2018-11-02 10:06:59 +01:00
|
|
|
}
|
|
|
|
}
|
2022-06-07 13:42:34 +02:00
|
|
|
|
2022-12-13 20:24:31 +00:00
|
|
|
// Check for pending replies to send.
|
2023-05-08 16:36:30 +02:00
|
|
|
if let Poll::Ready(Some(result)) = self.pending_replies.poll_next_unpin(cx) {
|
|
|
|
let event = result
|
2023-05-10 03:51:47 +02:00
|
|
|
.map(|()| Event::Identification)
|
2023-05-08 16:36:30 +02:00
|
|
|
.unwrap_or_else(|err| Event::IdentificationError(StreamUpgradeError::Apply(err)));
|
|
|
|
|
|
|
|
return Poll::Ready(ConnectionHandlerEvent::Custom(event));
|
2022-12-13 20:24:31 +00:00
|
|
|
}
|
2023-05-08 16:36:30 +02:00
|
|
|
|
|
|
|
Poll::Pending
|
2018-11-02 10:06:59 +01:00
|
|
|
}
|
2022-11-17 17:19:36 +00:00
|
|
|
|
|
|
|
fn on_connection_event(
|
|
|
|
&mut self,
|
|
|
|
event: ConnectionEvent<
|
|
|
|
Self::InboundProtocol,
|
|
|
|
Self::OutboundProtocol,
|
|
|
|
Self::InboundOpenInfo,
|
|
|
|
Self::OutboundOpenInfo,
|
|
|
|
>,
|
|
|
|
) {
|
|
|
|
match event {
|
|
|
|
ConnectionEvent::FullyNegotiatedInbound(fully_negotiated_inbound) => {
|
|
|
|
self.on_fully_negotiated_inbound(fully_negotiated_inbound)
|
|
|
|
}
|
|
|
|
ConnectionEvent::FullyNegotiatedOutbound(fully_negotiated_outbound) => {
|
|
|
|
self.on_fully_negotiated_outbound(fully_negotiated_outbound)
|
|
|
|
}
|
|
|
|
ConnectionEvent::DialUpgradeError(dial_upgrade_error) => {
|
|
|
|
self.on_dial_upgrade_error(dial_upgrade_error)
|
|
|
|
}
|
2023-05-08 16:36:30 +02:00
|
|
|
ConnectionEvent::AddressChange(_)
|
|
|
|
| ConnectionEvent::ListenUpgradeError(_)
|
|
|
|
| ConnectionEvent::RemoteProtocolsChange(_) => {}
|
|
|
|
ConnectionEvent::LocalProtocolsChange(change) => {
|
|
|
|
self.local_supported_protocols.on_protocols_change(change);
|
|
|
|
}
|
2022-11-17 17:19:36 +00:00
|
|
|
}
|
|
|
|
}
|
2018-11-02 10:06:59 +01:00
|
|
|
}
|