/*
* Copyright (c) 2013-2026, The PurpleI2P Project
*
* This file is part of Purple i2pd project and licensed under BSD3
*
* See full license text in LICENSE file at top of project tree
*/

#include <cstdlib>
#include <iostream>
#include <fstream>
#include <map>
#include <string>
#include <boost/program_options/cmdline.hpp>
#include <boost/program_options/options_description.hpp>
#include <boost/program_options/parsers.hpp>
#include <boost/program_options/variables_map.hpp>

#include "Identity.h"
#include "Config.h"
#include "version.h"
#include "Log.h"

using namespace boost::program_options;

namespace i2p {
namespace config {
	options_description m_OptionsDesc;
	variables_map m_Options;

#if defined(_WIN32)
#define path_to_file(file) "%appdata%\\i2pd\\" #file
#elif defined(MAC_OSX)
#define path_to_file(file) "~/Library/Application Support/i2pd/" #file
#elif defined(__HAIKU__)
#define path_to_file(file) "~/config/settings/i2pd/" #file
#else
#define path_to_file(file) "~/.i2pd/" #file " or /var/lib/i2pd/" #file
#endif

	void Init()
	{
		// register options once, they never change; drop stored values because
		// store() keeps the ones set by a previous Init
		m_Options.clear();
		static bool optionsRegistered = false;
		if (optionsRegistered) return;
		optionsRegistered = true;

		options_description general("General options");
		general.add_options()
			("help",                                                          "Show this message")
			("version",                                                       "Show i2pd version")
			("conf", value<std::string>()->default_value(""),                 "Path to main i2pd config file (default: try " path_to_file(i2pd.conf) ")")
			("tunconf", value<std::string>()->default_value(""),              "Path to config with tunnels list and options (default: try " path_to_file(tunnels.conf) ")")
			("tunnelsdir", value<std::string>()->default_value(""),           "Path to extra tunnels' configs folder (default: " path_to_file(tunnels.d))
			("certsdir", value<std::string>()->default_value(""),             "Path to certificates used for verifying .su3, families (default: " path_to_file(certificates))
			("pidfile", value<std::string>()->default_value(""),              "Path to pidfile (default: " path_to_file(i2pd.pid))
			("log", value<std::string>()->default_value(""),                  "Logs destination: stdout, file, syslog (stdout if not set)")
			("logfile", value<std::string>()->default_value(""),              "Path to logfile (stdout if not set, autodetect if daemon)")
			("loglevel", value<std::string>()->default_value("warn"),         "Set the minimal level of log messages (debug, info, warn, error, none)")
			("logclftime", bool_switch()->default_value(false),               "Write full CLF-formatted date and time to log (default: disabled, write only time)")
			("family", value<std::string>()->default_value(""),               "Specify a family, router belongs to")
			("datadir", value<std::string>()->default_value(""),              "Path to storage of i2pd data (RI, keys, peer profiles, ...)")
			("host", value<std::string>()->default_value(""),                 "External IP")
			("ifname", value<std::string>()->default_value(""),               "Network interface to bind to")
			("ifname4", value<std::string>()->default_value(""),              "Network interface to bind to for ipv4")
			("ifname6", value<std::string>()->default_value(""),              "Network interface to bind to for ipv6")
			("nat", bool_switch()->default_value(true),                       "Should we assume we are behind NAT? (default: enabled)")
			("port", value<uint16_t>()->default_value(0),                     "Port to listen for incoming connections (default: auto)")
			("ipv4", bool_switch()->default_value(true),                      "Enable communication through ipv4 (default: enabled)")
			("address4", value<std::string>()->default_value(""),             "Local address to bind ipv4 transport sockets to")
			("ipv6", bool_switch()->default_value(false),                     "Enable communication through ipv6 (default: disabled)")
			("address6", value<std::string>()->default_value(""),             "Local address to bind ipv6 transport sockets to")
			("reservedrange", bool_switch()->default_value(true),             "Check remote RI for being in blacklist of reserved IP ranges (default: enabled)")
			("netid", value<int>()->default_value(I2PD_NET_ID),               "Specify NetID. Main I2P is 2")
			("daemon", bool_switch()->default_value(false),                   "Router will go to background after start (default: disabled)")
			("service", bool_switch()->default_value(false),                  "Router will use system folders like '/var/lib/i2pd' (default: disabled)")
			("notransit", bool_switch()->default_value(false),                "Router will not accept transit tunnels at startup (default: disabled)")
			("floodfill", bool_switch()->default_value(false),                "Router will be floodfill (default: disabled)")
			("bandwidth", value<std::string>()->default_value(""),            "Transit traffic bandwidth limit: integer in KBps or letters: L (32), O (256), P (2048), X (>9000)")
			("share", value<int>()->default_value(100),                       "Limit of transit traffic from max bandwidth in percents. (default: 100)")
			("ntcp", bool_switch()->default_value(false),                     "Ignored. Always false")
			("ssu", bool_switch()->default_value(false),                      "Ignored. Always false")
			("ntcpproxy", value<std::string>()->default_value(""),            "Ignored")
#ifdef _WIN32
			("svcctl", value<std::string>()->default_value(""),               "Ignored")
			("insomnia", bool_switch()->default_value(false),                 "Prevent system from sleeping (default: disabled)")
			("close", value<std::string>()->default_value("ask"),             "Action on close: minimize, exit, ask")
#endif
			("stan", bool_switch()->default_value(false), 					  "Router has limited connectivity (default: false)")
		;

		options_description limits("Limits options");
		limits.add_options()
			("limits.coresize", value<uint32_t>()->default_value(0),          "Maximum size of corefile in Kb (0 - use system limit)")
#if defined(__HAIKU__)
			// Haiku's system default is 512, so we set 4096 explicitly
			("limits.openfiles", value<uint16_t>()->default_value(4096),		"Maximum number of open files (4096 by default)")
#else
			("limits.openfiles", value<uint16_t>()->default_value(0),         "Maximum number of open files (0 - use system default)")
#endif
			("limits.transittunnels", value<uint32_t>()->default_value(25000), "Maximum active transit tunnels (default:25000)")
			("limits.zombies", value<double>()->default_value(0),             "Minimum percentage of successfully created tunnels under which tunnel cleanup is paused (default [%]: 0.00)")
			("limits.ntcpsoft", value<uint16_t>()->default_value(0),          "Ignored")
			("limits.ntcphard", value<uint16_t>()->default_value(0),          "Ignored")
			("limits.ntcpthreads", value<uint16_t>()->default_value(1),       "Ignored")
		;

		options_description httpserver("HTTP Server options");
		httpserver.add_options()
			("http.enabled", value<bool>()->default_value(true),                "Enable or disable webconsole")
			("http.address", value<std::string>()->default_value("127.0.0.1"),  "Webconsole listen address")
			("http.port", value<uint16_t>()->default_value(7070),               "Webconsole listen port")
			("http.auth", value<bool>()->default_value(false),                  "Enable Basic HTTP auth for webconsole")
			("http.user", value<std::string>()->default_value("i2pd"),          "Username for basic auth")
			("http.pass", value<std::string>()->default_value(""),              "Password for basic auth (default: random, see logs)")
			("http.strictheaders", value<bool>()->default_value(true),          "Enable strict host checking on WebUI")
			("http.hostname", value<std::string>()->default_value("localhost"), "Expected hostname for WebUI")
			("http.webroot", value<std::string>()->default_value("/"),          "WebUI root path (default: / )")
			("http.lang", value<std::string>()->default_value("english"),       "WebUI language (default: english )")
			("http.showTotalTCSR", value<bool>()->default_value(false),         "Show additional value with total TCSR since router's start (default: false)")
			("http.theme", value<std::string>()->default_value("light"), 	    "Theme for http web console")
			("http.javascript", value<bool>()->default_value(true),          	"Allow pages with javascript")
		;

		options_description httpproxy("HTTP Proxy options");
		httpproxy.add_options()
			("httpproxy.enabled", value<bool>()->default_value(true),                 "Enable or disable HTTP Proxy")
			("httpproxy.address", value<std::string>()->default_value("127.0.0.1"),   "HTTP Proxy listen address")
			("httpproxy.port", value<uint16_t>()->default_value(4444),                "HTTP Proxy listen port")
			("httpproxy.keys", value<std::string>()->default_value("transient-proxy"), "File to persist HTTP Proxy keys. Transient by default")
			("httpproxy.signaturetype", value<i2p::data::SigningKeyType>()->
				default_value(i2p::data::SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519),      "Signature type for new keys. 7 (EdDSA) by default")
			("httpproxy.inbound.length", value<std::string>()->default_value("3"),    "HTTP proxy inbound tunnel length")
			("httpproxy.outbound.length", value<std::string>()->default_value("3"),   "HTTP proxy outbound tunnel length")
			("httpproxy.inbound.quantity", value<std::string>()->default_value("5"),  "HTTP proxy inbound tunnels quantity")
			("httpproxy.outbound.quantity", value<std::string>()->default_value("5"), "HTTP proxy outbound tunnels quantity")
			("httpproxy.inbound.lengthVariance", value<std::string>()->default_value("0"),  "HTTP proxy inbound tunnels length variance")
			("httpproxy.outbound.lengthVariance", value<std::string>()->default_value("0"), "HTTP proxy outbound tunnels length variance")
			("httpproxy.latency.min", value<std::string>()->default_value("0"),       "HTTP proxy min latency for tunnels")
			("httpproxy.latency.max", value<std::string>()->default_value("0"),       "HTTP proxy max latency for tunnels")
			("httpproxy.outproxy", value<std::string>()->default_value(""),           "HTTP proxy upstream out proxy url")
			("httpproxy.addresshelper", value<bool>()->default_value(true),           "Enable or disable addresshelper")
			("httpproxy.senduseragent", value<bool>()->default_value(false),          "Pass through user's User-Agent if enabled. Disabled by default")
			("httpproxy.i2cp.leaseSetType", value<std::string>()->default_value("3"), "Local destination's LeaseSet type")
#if OPENSSL_PQ
			("httpproxy.i2cp.leaseSetEncType", value<std::string>()->default_value("6,4,0"), "Local destination's LeaseSet encryption type")
#else
			("httpproxy.i2cp.leaseSetEncType", value<std::string>()->default_value("4,0"), "Local destination's LeaseSet encryption type")
#endif
			("httpproxy.i2cp.leaseSetPrivKey", value<std::string>()->default_value(""), "LeaseSet private key")
			("httpproxy.i2p.streaming.maxOutboundSpeed", value<std::string>()->default_value("1730000000"), "Max outbound speed of HTTP proxy stream in bytes/sec")
			("httpproxy.i2p.streaming.maxInboundSpeed", value<std::string>()->default_value("1730000000"), "Max inbound speed of HTTP proxy stream in bytes/sec")
			("httpproxy.i2p.streaming.profile", value<std::string>()->default_value("1"), "HTTP Proxy bandwidth usage profile. 1 - bulk(high), 2- interactive(low)")
			("httpproxy.i2p.streaming.maxWindowSize", value<std::string>()->default_value("512"), "HTTP Proxy stream max window size. 512 by default")
			("httpproxy.i2cp.closeIdleTime", value<uint64_t>()->default_value(0), "HTTP Proxy idle timeout in milliseconds after which destination stops building tunnels. Disabled by default(0)")
			("httpproxy.i2cp.newDestOnResume", value<bool>()->default_value(false), "HTTP Proxy generate a new local destination when resuming from idle. false by default")
		;

		options_description socksproxy("SOCKS Proxy options");
		socksproxy.add_options()
			("socksproxy.enabled", value<bool>()->default_value(true),                 "Enable or disable SOCKS Proxy")
			("socksproxy.address", value<std::string>()->default_value("127.0.0.1"),   "SOCKS Proxy listen address")
			("socksproxy.port", value<uint16_t>()->default_value(4447),                "SOCKS Proxy listen port")
			("socksproxy.keys", value<std::string>()->default_value("transient-proxy"), "File to persist SOCKS Proxy keys. Transient by default")
			("socksproxy.signaturetype", value<i2p::data::SigningKeyType>()->
				default_value(i2p::data::SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519),       "Signature type for new keys. 7 (EdDSA) by default")
			("socksproxy.inbound.length", value<std::string>()->default_value("3"),    "SOCKS proxy inbound tunnel length")
			("socksproxy.outbound.length", value<std::string>()->default_value("3"),   "SOCKS proxy outbound tunnel length")
			("socksproxy.inbound.quantity", value<std::string>()->default_value("5"),  "SOCKS proxy inbound tunnels quantity")
			("socksproxy.outbound.quantity", value<std::string>()->default_value("5"), "SOCKS proxy outbound tunnels quantity")
			("socksproxy.inbound.lengthVariance", value<std::string>()->default_value("0"),  "SOCKS proxy inbound tunnels length variance")
			("socksproxy.outbound.lengthVariance", value<std::string>()->default_value("0"), "SOCKS proxy outbound tunnels length variance")
			("socksproxy.latency.min", value<std::string>()->default_value("0"),       "SOCKS proxy min latency for tunnels")
			("socksproxy.latency.max", value<std::string>()->default_value("0"),       "SOCKS proxy max latency for tunnels")
			("socksproxy.outproxy.enabled", value<bool>()->default_value(false),       "Enable or disable SOCKS outproxy")
			("socksproxy.outproxy", value<std::string>()->default_value("127.0.0.1"),  "Upstream outproxy address for SOCKS Proxy")
			("socksproxy.outproxyport", value<uint16_t>()->default_value(9050),        "Upstream outproxy port for SOCKS Proxy")
			("socksproxy.i2cp.leaseSetType", value<std::string>()->default_value("3"), "Local destination's LeaseSet type")
#if OPENSSL_PQ
			("socksproxy.i2cp.leaseSetEncType", value<std::string>()->default_value("6,4,0"), "Local destination's LeaseSet encryption type")
#else
			("socksproxy.i2cp.leaseSetEncType", value<std::string>()->default_value("4,0"), "Local destination's LeaseSet encryption type")
#endif
			("socksproxy.i2cp.leaseSetPrivKey", value<std::string>()->default_value(""), "LeaseSet private key")
			("socksproxy.i2p.streaming.maxOutboundSpeed", value<std::string>()->default_value("1730000000"), "Max outbound speed of SOCKS proxy stream in bytes/sec")
			("socksproxy.i2p.streaming.maxInboundSpeed", value<std::string>()->default_value("1730000000"), "Max inbound speed of SOCKS proxy stream in bytes/sec")
			("socksproxy.i2p.streaming.profile", value<std::string>()->default_value("1"), "SOCKS Proxy bandwidth usage profile. 1 - bulk(high), 2- interactive(low)")
			("socksproxy.i2p.streaming.maxWindowSize", value<std::string>()->default_value("512"), "SOCKS Proxy stream max window size. 512 by default")
			("socksproxy.i2cp.closeIdleTime", value<uint64_t>()->default_value(0), "SOCKS Proxy idle timeout in milliseconds after which destination stops building tunnels. Disabled by default(0)")
			("socksproxy.i2cp.newDestOnResume", value<bool>()->default_value(false), "SOCKS Proxy generate a new local destination when resuming from idle. false by default")
		;

		options_description shareddest("Shared local destination options");
		shareddest.add_options()
			("shareddest.inbound.length", value<std::string>()->default_value("3"),    "Shared local destination inbound tunnel length")
			("shareddest.outbound.length", value<std::string>()->default_value("3"),   "Shared local destination outbound tunnel length")
			("shareddest.inbound.quantity", value<std::string>()->default_value("3"),  "Shared local destination inbound tunnels quantity")
			("shareddest.outbound.quantity", value<std::string>()->default_value("3"), "Shared local destination outbound tunnels quantity")
			("shareddest.i2cp.leaseSetType", value<std::string>()->default_value("3"), "Shared local destination's LeaseSet type")
#if OPENSSL_PQ
			("shareddest.i2cp.leaseSetEncType", value<std::string>()->default_value("6,4,0"), "Shared local destination's LeaseSet encryption type")
#else
			("shareddest.i2cp.leaseSetEncType", value<std::string>()->default_value("4,0"), "Shared local destination's LeaseSet encryption type")
#endif
			("shareddest.i2p.streaming.profile", value<std::string>()->default_value("2"), "Shared local destination bandwidth usage profile. 1 - bulk(high), 2- interactive(low)")
		;

		options_description sam("SAM bridge options");
		sam.add_options()
			("sam.enabled", value<bool>()->default_value(true),               "Enable or disable SAM Application bridge")
			("sam.address", value<std::string>()->default_value("127.0.0.1"), "SAM listen address")
			("sam.port", value<uint16_t>()->default_value(7656),              "SAM listen TCP port")
			("sam.portudp", value<uint16_t>()->default_value(0),              "SAM listen UDP port")
			("sam.singlethread", value<bool>()->default_value(true),          "Sessions run in the SAM bridge's thread")
		;

		options_description bob("BOB options");
		bob.add_options()
			("bob.enabled", value<bool>()->default_value(false),              "Enable or disable BOB command channel")
			("bob.address", value<std::string>()->default_value("127.0.0.1"), "BOB listen address")
			("bob.port", value<uint16_t>()->default_value(2827),              "BOB listen port")
		;

		options_description i2cp("I2CP options");
		i2cp.add_options()
			("i2cp.enabled", value<bool>()->default_value(false),              "Enable or disable I2CP")
			("i2cp.address", value<std::string>()->default_value("127.0.0.1"), "I2CP listen address")
			("i2cp.port", value<uint16_t>()->default_value(7654),              "I2CP listen port")
			("i2cp.singlethread", value<bool>()->default_value(true),          "Destinations run in the I2CP server's thread")
			("i2cp.inboundlimit", value<uint32_t>()->default_value(0),         "Client inbound limit in KBps to return in BandwidthLimitsMessage. Router's bandwidth by default")
			("i2cp.outboundlimit", value<uint32_t>()->default_value(0),        "Client outbound limit in KBps to return in BandwidthLimitsMessage. Router's bandwidth by default")
		;

		options_description i2pcontrol("I2PControl options");
		i2pcontrol.add_options()
			("i2pcontrol.enabled", value<bool>()->default_value(false),                    "Enable or disable I2P Control Protocol")
			("i2pcontrol.address", value<std::string>()->default_value("127.0.0.1"),       "I2PCP listen address")
			("i2pcontrol.port", value<uint16_t>()->default_value(7650),                    "I2PCP listen port")
			("i2pcontrol.password", value<std::string>()->default_value("itoopie"),        "I2PCP access password")
			("i2pcontrol.cert", value<std::string>()->default_value("i2pcontrol.crt.pem"), "I2PCP connection certificate")
			("i2pcontrol.key", value<std::string>()->default_value("i2pcontrol.key.pem"),  "I2PCP connection certificate key")
		;

		bool upnp_default = false;
#if (defined(USE_UPNP) && (defined(WIN32_APP) || defined(ANDROID)))
		upnp_default = true; // enable UPNP for windows GUI and android by default
#endif
		options_description upnp("UPnP options");
		upnp.add_options()
			("upnp.enabled", value<bool>()->default_value(upnp_default), "Enable or disable UPnP: automatic port forwarding")
			("upnp.name", value<std::string>()->default_value("I2Pd"),   "Name i2pd appears in UPnP forwarding list")
		;

		options_description precomputation("Precomputation options");
		precomputation.add_options()
			("precomputation.elgamal",
#if (defined(_M_AMD64) || defined(__x86_64__))
				value<bool>()->default_value(false),
#else
				value<bool>()->default_value(true),
#endif
				"Enable or disable elgamal precomputation table")
		;

		options_description reseed("Reseed options");
		reseed.add_options()
			("reseed.verify", value<bool>()->default_value(false),        "Verify .su3 signature")
			("reseed.threshold", value<uint16_t>()->default_value(25),    "Minimum number of known routers before requesting reseed")
			("reseed.floodfill", value<std::string>()->default_value(""), "Ignored. Always empty")
			("reseed.file", value<std::string>()->default_value(""),      "Path to local .su3 file or HTTPS URL to reseed from")
			("reseed.zipfile", value<std::string>()->default_value(""),   "Path to local .zip file to reseed from")
			("reseed.proxy", value<std::string>()->default_value(""),     "url for reseed proxy, supports http/socks")
			("reseed.urls", value<std::string>()->default_value(
				"https://reseed2.i2p.net/,"
				"https://reseed.diva.exchange/,"
				"https://reseed-fr.i2pd.xyz/,"
				"https://reseed.onion.im/,"
				"https://i2pseed.creativecowpat.net:8443/,"
				"https://reseed.i2pgit.org/,"
				"https://coconut.incognet.io/," // expired cert
				"https://reseed-pl.i2pd.xyz/,"
				"https://www2.mk16.de/,"
			    "https://i2p.novg.net/,"
            	"https://reseed.stormycloud.org/,"
            	"https://reseed.sahil.world/,"
            	"https://i2p.diyarciftci.xyz/,"
            	"https://bybyh.de/,"
            	"https://spiral.likogan.dev/"
			),                                                            "Reseed URLs, separated by comma")
			("reseed.yggurls", value<std::string>()->default_value(
				"http://[324:71e:281a:9ed3::ace]:7070/,"
				"http://[301:65b9:c7cd:9a36::1]:18801/,"
				"http://[320:f09a:f09f:7acd::216]/,"
				"http://[316:f9e0:f22e:a74f::216]/"
			),                                                            "Reseed URLs through the Yggdrasil, separated by comma")
			("reseed.followredirect", value<bool>()->default_value(false),         "Follow redirects when reseeding")
		;

		options_description addressbook("AddressBook options");
		addressbook.add_options()
			("addressbook.enabled", value<bool>()->default_value(true), "Enable address book lookups and subscriptions (default: enabled)")
			("addressbook.defaulturl", value<std::string>()->default_value(
				"http://shx5vqsw7usdaunyzr2qmes2fq37oumybpudrd4jjj4e4vk4uusa.b32.i2p/hosts.txt"
			),                                                                     "AddressBook subscription URL for initial setup")
			("addressbook.subscriptions", value<std::string>()->default_value(
				"http://reg.i2p/hosts.txt"
			),                                                                     "AddressBook subscriptions URLs, separated by comma")
			("addressbook.hostsfile", value<std::string>()->default_value(""),     "File to dump addresses in hosts.txt format");

		options_description trust("Trust options");
		trust.add_options()
			("trust.enabled", value<bool>()->default_value(false),     "Enable explicit trust options")
			("trust.family", value<std::string>()->default_value(""),  "Router Family to trust for first hops")
			("trust.routers", value<std::string>()->default_value(""), "Only Connect to these routers")
			("trust.hidden", value<bool>()->default_value(false),      "Should we hide our router from other routers?")
		;

		// Save deprecated websocket options for compatibility
		options_description websocket("Websocket Options");
		websocket.add_options()
			("websockets.enabled", value<bool>()->default_value(false),     "Deprecated option")
			("websockets.address", value<std::string>()->default_value(""), "Deprecated option")
			("websockets.port", value<uint16_t>()->default_value(0),        "Deprecated option")
		;

		options_description exploratory("Exploratory Options");
		exploratory.add_options()
			("exploratory.inbound.length", value<int>()->default_value(2),    "Exploratory inbound tunnel length")
			("exploratory.outbound.length", value<int>()->default_value(2),   "Exploratory outbound tunnel length")
			("exploratory.inbound.quantity", value<int>()->default_value(3),  "Exploratory inbound tunnels quantity")
			("exploratory.outbound.quantity", value<int>()->default_value(3), "Exploratory outbound tunnels quantity")
		;

		options_description ntcp2("NTCP2 Options");
		ntcp2.add_options()
			("ntcp2.enabled", value<bool>()->default_value(true),          "Enable NTCP2 (default: enabled)")
			("ntcp2.published", value<bool>()->default_value(true),        "Publish NTCP2 (default: enabled)")
			("ntcp2.port", value<uint16_t>()->default_value(0),            "Port to listen for incoming NTCP2 connections (default: auto)")
			("ntcp2.addressv6", value<std::string>()->default_value("::"), "Address to publish NTCP2 with")
			("ntcp2.proxy", value<std::string>()->default_value(""),       "Proxy URL for NTCP2 transport")
#if OPENSSL_PQ
			("ntcp2.version", value<int>()->default_value(4),              "Protocol version. 2 - standard, 3,4,5 - post quantum (default: 4)")
#else
			("ntcp2.version", value<int>()->default_value(2),              "Protocol version. 2 - standard, 3,4,5 - post quantum (default: 2)")
#endif
		;

		options_description ssu2("SSU2 Options");
		ssu2.add_options()
			("ssu2.enabled", value<bool>()->default_value(true),         "Enable SSU2 (default: enabled)")
			("ssu2.published", value<bool>()->default_value(true),        "Publish SSU2 (default: enabled)")
			("ssu2.port", value<uint16_t>()->default_value(0),            "Port to listen for incoming SSU2 packets (default: auto)")
			("ssu2.mtu4", value<uint16_t>()->default_value(0),            "MTU for ipv4 address (default: detect)")
			("ssu2.mtu6", value<uint16_t>()->default_value(0),            "MTU for ipv6 address (default: detect)")
			("ssu2.proxy", value<std::string>()->default_value(""),       "Socks5 proxy URL for SSU2 transport")
			("ssu2.firewalled4", value<bool>()->default_value(false),     "Set ipv4 network status to Firewalled even if OK (default: disabled)")
			("ssu2.firewalled6", value<bool>()->default_value(false),     "Set ipv6 network status to Firewalled even if OK (default: disabled)")
#if OPENSSL_PQ
			("ssu2.version", value<int>()->default_value(4),              "Protocol version. 2 - standard, 3,4 - post quantum (default: 4)")
#else
			("ssu2.version", value<int>()->default_value(2),              "Protocol version. 2 - standard, 3,4 - post quantum (default: 2)")
#endif
		;

		options_description nettime("Time sync options");
		nettime.add_options()
			("nettime.enabled", value<bool>()->default_value(false),       "Enable NTP time sync (default: disabled)")
			("nettime.ntpservers", value<std::string>()->default_value(
				"0.pool.ntp.org,"
				"1.pool.ntp.org,"
				"2.pool.ntp.org,"
				"3.pool.ntp.org"
			),                                                             "Comma separated list of NTP servers")
			("nettime.ntpsyncinterval", value<int>()->default_value(72),   "NTP sync interval in hours (default: 72)")
			("nettime.frompeers", value<bool>()->default_value(true),      "Sync clock from transport peers (default: enabled)")
		;

		options_description persist("Network information persisting options");
		persist.add_options()
			("persist.profiles", value<bool>()->default_value(true),       "Persist peer profiles (default: true)")
			("persist.addressbook", value<bool>()->default_value(true),    "Persist full addresses (default: true)")
			("persist.netdbinterval", value<int>()->default_value(60),     "NetDb persist interval in seconds (default: 60)")
		;

		options_description cpuext("CPU encryption extensions options. Deprecated");
		cpuext.add_options()
			("cpuext.aesni", bool_switch()->default_value(true),                     "Deprecated option")
			("cpuext.avx", bool_switch()->default_value(false),                      "Deprecated option")
			("cpuext.force", bool_switch()->default_value(false),                    "Deprecated option")
		;

		options_description meshnets("Meshnet transports options");
		meshnets.add_options()
			("meshnets.yggdrasil", bool_switch()->default_value(false),              "Support transports through the Yggdrasil (default: false)")
			("meshnets.yggaddress", value<std::string>()->default_value(""),         "Yggdrasil address to publish")
		;

#ifdef __linux__
		options_description unix_specific("UNIX-specific options");
		unix_specific.add_options()
			("unix.handle_sigtstp", bool_switch()->default_value(false),             "Handle SIGTSTP and SIGCONT signals (default: disabled)")
		;
#endif

#ifdef __OpenBSD__
		options_description openbsd_specific("OpenBSD specific options");
		openbsd_specific.add_options()
			("openbsd.pledge_file", value<std::string>()->default_value(""), "OpenBSD file with pledge rules")
			("openbsd.unveil_file", value<std::string>()->default_value(""), "OpenBSD file with unveil rules")
			("openbsd.unveil_enabled", value<bool>()->default_value(true),     "use unveil rues")
			("openbsd.pledge_enabled", value<bool>()->default_value(true),     "use pledge rules")
			;
#endif

		m_OptionsDesc
			.add(general)
			.add(limits)
			.add(httpserver)
			.add(httpproxy)
			.add(socksproxy)
			.add(shareddest)
			.add(sam)
			.add(bob)
			.add(i2cp)
			.add(i2pcontrol)
			.add(upnp)
			.add(precomputation)
			.add(reseed)
			.add(addressbook)
			.add(trust)
			.add(websocket) // deprecated
			.add(exploratory)
			.add(ntcp2)
			.add(ssu2)
			.add(nettime)
			.add(persist)
			.add(cpuext)
			.add(meshnets)
#ifdef __linux__
			.add(unix_specific)
#endif
#ifdef __OpenBSD__
			.add(openbsd_specific)
#endif
		;
	}

	void ParseCmdline(int argc, char* argv[], bool ignoreUnknown)
	{
		try
		{
			auto style = boost::program_options::command_line_style::unix_style
			           | boost::program_options::command_line_style::allow_long_disguise;
			style &=   ~ boost::program_options::command_line_style::allow_guessing;
			if (ignoreUnknown)
				store(command_line_parser(argc, argv).options(m_OptionsDesc).style (style).allow_unregistered().run(), m_Options);
			else
				store(parse_command_line(argc, argv, m_OptionsDesc, style), m_Options);
		}
		catch (boost::program_options::error& e)
		{
			ThrowFatal ("Error while parsing arguments: ", e.what());
			std::cerr << "args: " << e.what() << std::endl;
			exit(EXIT_FAILURE);
		}

		if (!ignoreUnknown && (m_Options.count("help") || m_Options.count("h")))
		{
			std::cout << "i2pd version " << I2PD_VERSION << " (" << I2P_VERSION << ")" << std::endl;
			std::cout << m_OptionsDesc;
			exit(EXIT_SUCCESS);
		}
		else if (m_Options.count("version"))
		{
			std::cout << "i2pd version " << I2PD_VERSION << " (" << I2P_VERSION << ")" << std::endl;
			std::cout << "Boost version "
					  << BOOST_VERSION / 100000     << "."  // maj. version
					  << BOOST_VERSION / 100 % 1000 << "."  // min. version
					  << BOOST_VERSION % 100                // patch version
					  << std::endl;
#if defined(OPENSSL_VERSION_TEXT)
			std::cout << OPENSSL_VERSION_TEXT << std::endl;
#endif
#if defined(LIBRESSL_VERSION_TEXT)
			std::cout << LIBRESSL_VERSION_TEXT << std::endl;
#endif

			exit(EXIT_SUCCESS);
		}
	}

	void ParseConfig(const std::string& path)
	{
		if (path == "") return;

		std::ifstream config(path, std::ios::in);

		if (!config.is_open())
		{
			ThrowFatal ("Missing or unreadable config file: ", path);
			std::cerr << "missing/unreadable config file: " << path << std::endl;
			exit(EXIT_FAILURE);
		}

		try
		{
			store(boost::program_options::parse_config_file(config, m_OptionsDesc), m_Options);
		}
		catch (boost::program_options::error& e)
		{
			ThrowFatal ("Error while parsing config file: ", e.what());
			std::cerr << e.what() << std::endl;
			exit(EXIT_FAILURE);
		};
	}

	void Finalize()
	{
		notify(m_Options);
	}

	bool IsDefault(const char *name)
	{
		if (!m_Options.count(name))
			throw "try to check non-existent option";

		if (m_Options[name].defaulted())
			return true;
		return false;
	}

	bool GetOptionAsAny(const char *name, boost::any& value)
	{
		if (!m_Options.count(name))
			return false;
		value = m_Options[name];
		return true;
	}

	bool GetOptionAsAny(const std::string& name, boost::any& value)
	{
		return GetOptionAsAny (name.c_str (), value);
	}

} // namespace config
} // namespace i2p
