// Copyright Marcus Del Favero 2025
// Licensed under the GNU AGPLv3 with an exception, see `README.md` for details
mod publishing;
mod publication;
pub(super) mod status;

pub(super) use publishing::{PublishingServer, PublishingArgs, SubscribeError};
pub(super) use publication::{PublicationServer, InitError};

use std::fmt::{Debug, Formatter, Result as FmtResult};

use serde::{Serialize, Deserialize};
use zeroize::ZeroizeOnDrop;

use crate::protocol::discovery::{DiscoveryServer, GameId};

#[derive(Debug, Serialize, Deserialize)]
enum SubscribeMessage {
	Init(Vec<DiscoveryServer>),
	Add(DiscoveryServer),
	Remove(GameId),

	/**
	 * This variant isn't strictly needed, since `Add` can instead be used, but
	 * instead sending this message reduces bandwidth by only sending the updated
	 * player and spectator counts.
	 *
	 * I created this variant because it's likely that the most common updates to
	 * game servers will be the changing client count, not the addition and
	 * removal.
	 */
	UpdateClientCount(GameId, usize, usize),
}

/**
 * The key used for authenticating Publish Subscribe requests.
 *
 * Right now my code supports at most one active publish-subscribe connection
 * because implementing support for more active connections is unnecessary work.
 * One problem with this is the possibility that a malicious publication server
 * makes a Publish Subscribe request to the publishing server and that request
 * succeeds because the publication server hasn't yet responded. If that does
 * happen, the intended publication server's request will be rejected, and so
 * users won't be able to view the published servers through online discovery.
 *
 * One solution for this is to check that the source IP address of any Publish
 * Subscribe requests matches the destination IP address of any awaited Publish
 * Init Requests, but this has two problems. First, a malicious node can still
 * exist sharing the same public IP address (like if behind a NAT) so it doesn't
 * completely remove the problem. Second, it might be desirable for a different
 * IP address that is controlled by the same server admin to make the Publish
 * Subscribe request for whatever reason.
 *
 * The solution is to this is to send a 256-bit key in the Publish Init request
 * and to accept the first Publish Subscribe request that includes that same
 * key.
 */
#[derive(Clone, Eq, ZeroizeOnDrop, Serialize, Deserialize)]
pub struct Key([u8; 32]);

impl Key {
	pub(in crate::net) fn try_new() -> Result<Key, String> {
		let mut key = [0; 32];
		getrandom::fill(&mut key).map_err(|err| format!("failed generating random bytes: {err}"))?;
		Ok(Key(key))
	}
}

/**
 * Forcing equality to take constant time to prevent any hypothetical timing
 * attack.
 *
 * In practice such a timing attack would be very very difficult to pull off
 * because the time variation in equality would be only a few nanoseconds, which
 * I doubt can be detected over the network (20 μs over the network is possible,
 * https://security.stackexchange.com/questions/111040/should-i-worry-about-remote-timing-attacks-on-string-comparison,
 * three orders of magnitude more precise isn't likely).
 *
 * Also the connection window for SERP Publish Subscribe to be made by an
 * interceptor isn't that long, and the key is reset when Publish Init is
 * reattempted, so performing such an attack becomes even more difficult than
 * what is already an impossible task.
 *
 * Even if the attacker does succeed in guessing the key, they won't get that
 * much out of it. All they would have is the ability to prevent the correct
 * publication server from receiving updates, and receive published game
 * servers, which already are public!
 */
impl PartialEq for Key {
	fn eq(&self, other: &Key) -> bool {
		constant_time_eq::constant_time_eq_32(&self.0, &other.0)
	}
}

/**
 * Logging the key isn't likely to be a big deal because the key is only used
 * temporarily, but it's generally good practice not to log this stuff.
 *
 * A very artificial scenario in which logging the key would be problematic is
 * if for some reason a publication server sends its logs to another server over
 * an insecure connection, and a malicious node on that path intercepts the key
 * and performs the Publish Subscribe request which might reach the publishing
 * server before the actual request.
 */
impl Debug for Key {
	fn fmt(&self, fmt: &mut Formatter<'_>) -> FmtResult {
		write!(fmt, "Key(...)")
	}
}
