Initial private torrents support

This commit is contained in:
Igor Katson 2025-01-13 15:47:13 +00:00
parent bc5e23bf6d
commit 8efd77fce2
No known key found for this signature in database
GPG key ID: B4EC22B66D61A3F5
7 changed files with 50 additions and 16 deletions

View file

@ -218,14 +218,26 @@ impl<'de> serde::de::Deserializer<'de> for &mut BencodeDeserializer<'de> {
} }
} }
fn deserialize_bool<V>(self, _visitor: V) -> Result<V::Value, Self::Error> fn deserialize_bool<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where where
V: serde::de::Visitor<'de>, V: serde::de::Visitor<'de>,
{ {
Err( if !self.buf.starts_with(b"i") {
Error::new_from_kind(ErrorKind::NotSupported("bencode doesn't support booleans")) return Err(Error::custom_with_de(
.set_context(self), "expected bencode int to represent bool",
) self,
));
}
let value = self.parse_integer()?;
if value > 1 {
return Err(Error::custom_with_de(
format!("expected 0 or 1 for boolean, but got {value}"),
self,
));
}
visitor
.visit_bool(value == 1)
.map_err(|e: Self::Error| e.set_context(self))
} }
fn deserialize_i8<V>(self, visitor: V) -> Result<V::Value, Self::Error> fn deserialize_i8<V>(self, visitor: V) -> Result<V::Value, Self::Error>

View file

@ -218,11 +218,8 @@ impl<'ser, W: std::io::Write> Serializer for &'ser mut BencodeSerializer<W> {
type SerializeStruct = SerializeStruct<'ser, W>; type SerializeStruct = SerializeStruct<'ser, W>;
type SerializeStructVariant = Impossible<(), SerError>; type SerializeStructVariant = Impossible<(), SerError>;
fn serialize_bool(self, _: bool) -> Result<Self::Ok, Self::Error> { fn serialize_bool(self, value: bool) -> Result<Self::Ok, Self::Error> {
Err(SerError::custom_with_ser( self.write_number(if value { 1 } else { 0 })
"bencode doesn't support booleans",
self,
))
} }
fn serialize_i8(self, v: i8) -> Result<Self::Ok, Self::Error> { fn serialize_i8(self, v: i8) -> Result<Self::Ok, Self::Error> {

View file

@ -163,6 +163,7 @@ async fn create_torrent_raw<'a>(
attr: None, attr: None,
sha1: None, sha1: None,
symlink_path: None, symlink_path: None,
private: false,
}) })
} }

View file

@ -1008,6 +1008,8 @@ impl Session {
name, name,
} = add_res; } = add_res;
let private = metadata.as_ref().map_or(false, |m| m.info.private);
let make_peer_rx = || { let make_peer_rx = || {
self.make_peer_rx( self.make_peer_rx(
info_hash, info_hash,
@ -1015,6 +1017,7 @@ impl Session {
!opts.paused && !opts.list_only, !opts.paused && !opts.list_only,
opts.force_tracker_interval, opts.force_tracker_interval,
opts.initial_peers.clone().unwrap_or_default(), opts.initial_peers.clone().unwrap_or_default(),
private,
) )
.context("error creating peer stream") .context("error creating peer stream")
}; };
@ -1284,12 +1287,14 @@ impl Session {
t: &Arc<ManagedTorrent>, t: &Arc<ManagedTorrent>,
announce: bool, announce: bool,
) -> anyhow::Result<PeerStream> { ) -> anyhow::Result<PeerStream> {
let is_private = t.with_metadata(|m| m.info.private).unwrap_or(false);
self.make_peer_rx( self.make_peer_rx(
t.info_hash(), t.info_hash(),
t.shared().trackers.iter().cloned().collect(), t.shared().trackers.iter().cloned().collect(),
announce, announce,
t.shared().options.force_tracker_interval, t.shared().options.force_tracker_interval,
t.shared().options.initial_peers.clone(), t.shared().options.initial_peers.clone(),
is_private,
)? )?
.context("no peer source") .context("no peer source")
} }
@ -1298,17 +1303,26 @@ impl Session {
fn make_peer_rx( fn make_peer_rx(
self: &Arc<Self>, self: &Arc<Self>,
info_hash: Id20, info_hash: Id20,
trackers: Vec<String>, mut trackers: Vec<String>,
announce: bool, announce: bool,
force_tracker_interval: Option<Duration>, force_tracker_interval: Option<Duration>,
initial_peers: Vec<SocketAddr>, initial_peers: Vec<SocketAddr>,
is_private: bool,
) -> anyhow::Result<Option<PeerStream>> { ) -> anyhow::Result<Option<PeerStream>> {
let announce_port = if announce { self.tcp_listen_port } else { None }; let announce_port = if announce { self.tcp_listen_port } else { None };
let dht_rx = self let dht_rx = if is_private {
.dht None
.as_ref() } else {
.map(|dht| dht.get_peers(info_hash, announce_port)) self.dht
.transpose()?; .as_ref()
.map(|dht| dht.get_peers(info_hash, announce_port))
.transpose()?
};
if is_private && trackers.len() > 1 {
warn!("private trackers are not fully implemented, so using only the first tracker");
trackers.resize_with(1, Default::default);
}
let tracker_rx_stats = PeerRxTorrentInfo { let tracker_rx_stats = PeerRxTorrentInfo {
info_hash, info_hash,

View file

@ -134,6 +134,7 @@ async fn debug_server() -> anyhow::Result<()> {
Ok(()) Ok(())
} }
#[allow(dead_code)]
pub fn spawn_debug_server() -> tokio::task::JoinHandle<anyhow::Result<()>> { pub fn spawn_debug_server() -> tokio::task::JoinHandle<anyhow::Result<()>> {
tokio::spawn(debug_server()) tokio::spawn(debug_server())
} }

View file

@ -430,6 +430,7 @@ mod tests {
attr: None, attr: None,
sha1: None, sha1: None,
symlink_path: None, symlink_path: None,
private: false,
}, },
comment: None, comment: None,
created_by: None, created_by: None,

View file

@ -50,6 +50,10 @@ pub fn torrent_from_bytes<'de, BufType: Deserialize<'de> + From<&'de [u8]>>(
torrent_from_bytes_ext(buf).map(|r| r.meta) torrent_from_bytes_ext(buf).map(|r| r.meta)
} }
fn is_false(b: &bool) -> bool {
!*b
}
/// A parsed .torrent file. /// A parsed .torrent file.
#[derive(Serialize, Deserialize, Debug, Clone)] #[derive(Serialize, Deserialize, Debug, Clone)]
pub struct TorrentMetaV1<BufType> { pub struct TorrentMetaV1<BufType> {
@ -117,6 +121,9 @@ pub struct TorrentMetaV1Info<BufType> {
// Multi-file mode // Multi-file mode
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
pub files: Option<Vec<TorrentMetaV1File<BufType>>>, pub files: Option<Vec<TorrentMetaV1File<BufType>>>,
#[serde(skip_serializing_if = "is_false", default)]
pub private: bool,
} }
#[derive(Clone, Copy)] #[derive(Clone, Copy)]
@ -377,6 +384,7 @@ where
attr: self.attr.clone_to_owned(within_buffer), attr: self.attr.clone_to_owned(within_buffer),
sha1: self.sha1.clone_to_owned(within_buffer), sha1: self.sha1.clone_to_owned(within_buffer),
symlink_path: self.symlink_path.clone_to_owned(within_buffer), symlink_path: self.symlink_path.clone_to_owned(within_buffer),
private: self.private,
} }
} }
} }