gnunet-svn
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[taler-depolymerization] branch master updated (06cbb24 -> 3d6220f)


From: gnunet
Subject: [taler-depolymerization] branch master updated (06cbb24 -> 3d6220f)
Date: Tue, 08 Mar 2022 16:48:13 +0100

This is an automated email from the git hooks/post-receive script.

antoine pushed a change to branch master
in repository depolymerization.

    from 06cbb24  Network based currency names
     new 7fce22c  RFC 8905 compatible currency names
     new 643cd6c  Better config parsing
     new 3d6220f  wire-gateway: support auth

The 3 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 README.md                                    |  14 ++-
 btc-wire/src/bin/btc-wire-utils.rs           |   7 +-
 btc-wire/src/btc_config.rs                   |   4 +-
 btc-wire/src/lib.rs                          |  31 +++---
 btc-wire/src/main.rs                         |   8 +-
 common/src/config.rs                         | 153 ++++++++++++++++-----------
 common/src/currency.rs                       |  12 +--
 docs/{taler-btc.conf => taler-btc-full.conf} |   5 +-
 docs/{taler-btc.conf => taler-btc-min.conf}  |  12 +--
 docs/{taler-eth.conf => taler-eth-full.conf} |   5 +-
 docs/taler-eth-min.conf                      |  11 ++
 eth-wire/src/bin/eth-wire-utils.rs           |   6 +-
 eth-wire/src/lib.rs                          |  29 +++--
 eth-wire/src/main.rs                         |   9 +-
 instrumentation/src/main.rs                  |   2 +-
 makefile                                     |   1 +
 test/common.sh                               |   2 +-
 test/conf/taler_btc.conf                     |   3 +-
 test/conf/taler_btc_auth.conf                |  19 ++++
 test/conf/taler_btc_bump.conf                |   5 +-
 test/conf/taler_btc_lifetime.conf            |   5 +-
 test/conf/taler_eth.conf                     |   5 +-
 test/conf/taler_eth_bump.conf                |   5 +-
 test/conf/taler_eth_lifetime.conf            |   5 +-
 test/gateway/api.sh                          |  15 ++-
 test/gateway/auth.sh                         |  31 ++++++
 wire-gateway/src/main.rs                     |  50 ++++++---
 27 files changed, 289 insertions(+), 165 deletions(-)
 copy docs/{taler-btc.conf => taler-btc-full.conf} (79%)
 rename docs/{taler-btc.conf => taler-btc-min.conf} (51%)
 rename docs/{taler-eth.conf => taler-eth-full.conf} (79%)
 create mode 100644 docs/taler-eth-min.conf
 create mode 100644 test/conf/taler_btc_auth.conf
 create mode 100644 test/gateway/auth.sh

diff --git a/README.md b/README.md
index 718abca..1e3b569 100644
--- a/README.md
+++ b/README.md
@@ -35,12 +35,14 @@ The configuration is based on
 
 You can find filled configurations for each implementation:
 
-- [btc-wire](docs/taler-btc.conf)
-- [eth-wire](docs/taler-eth.conf)
+- btc-wire: [minimal](docs/taler-btc-min.conf) or
+  [full](docs/taler-btc-full.conf)
+- eth-wire: [minimal](docs/taler-eth-min.conf) or
+  [full](docs/taler-eth-full.conf)
 
 ### Initialization
 
-This is the minimal required config for initialization.
+This is the required configuration for initialization.
 
 ```ini
 # taler.conf - (fill all ___)
@@ -55,8 +57,6 @@ BASE_URL = ___
 [depolymerizer-___]
 # Postgres connection URL
 DB_URL   = ___
-# Wire payto URL
-# PAYTO = ___
 ```
 
 `PAYTO` is to be added after wallet initialization.
@@ -96,8 +96,12 @@ BOUNCE_FEE   = 0.00001
 
 ```ini
 [depolymerizer-___]
+# Port on which the server listen
 PORT          = 8080
+# Path on which the server listen (replace port)
 UNIXPATH      =
+# HTTP Authentication Scheme (basic or none)
+AUTH_METHOD   =
 ```
 
 ### Process lifetime
diff --git a/btc-wire/src/bin/btc-wire-utils.rs 
b/btc-wire/src/bin/btc-wire-utils.rs
index cbda59a..841df85 100644
--- a/btc-wire/src/bin/btc-wire-utils.rs
+++ b/btc-wire/src/bin/btc-wire-utils.rs
@@ -80,9 +80,8 @@ pub fn auto_wallet(rpc: &mut Rpc, config: &BitcoinConfig, 
name: &str) -> (Rpc, A
 fn main() {
     common::log::init();
     let args = Args::parse();
-    let (taler_config, currency) = load_taler_config(args.config.as_deref());
-    let btc_config =
-        BitcoinConfig::load(args.datadir.unwrap_or(taler_config.custom), 
currency).unwrap();
+    let (taler_config, path, currency) = 
load_taler_config(args.config.as_deref());
+    let btc_config = BitcoinConfig::load(args.datadir.unwrap_or(path), 
currency).unwrap();
     let mut rpc = Rpc::common(&btc_config).unwrap();
 
     match args.cmd {
@@ -118,7 +117,7 @@ fn main() {
         }
         Cmd::Resetdb => {
             let hash: BlockHash = rpc.get_genesis().unwrap();
-            let mut db = taler_config.db_config.connect(NoTls).unwrap();
+            let mut db = taler_config.db_config().connect(NoTls).unwrap();
             let mut tx = db.transaction().unwrap();
             // Clear transaction tables and reset state
             tx.execute("DELETE FROM tx_in", &[]).unwrap();
diff --git a/btc-wire/src/btc_config.rs b/btc-wire/src/btc_config.rs
index 0c8114b..fa61c6f 100644
--- a/btc-wire/src/btc_config.rs
+++ b/btc-wire/src/btc_config.rs
@@ -87,14 +87,14 @@ impl BitcoinConfig {
 
         let port = if let Some(addr) = section.and_then(|s| s.get("rpcport")) {
             addr.parse()
-                .or_fail(|_| "bitcoin config value 'rpcport' is not a valid 
port number".into())
+                .or_fail(|_| "bitcoin config 'rpcport' is not a valid port 
number".into())
         } else {
             rpc_port(network)
         };
 
         let addr = if let Some(addr) = section.and_then(|s| s.get("rpcbind")) {
             SocketAddr::from_str(addr)
-                .or_fail(|_| "bitcoin config value 'rpcbind' is not a valid 
socket address".into())
+                .or_fail(|_| "bitcoin config 'rpcbind' is not a valid socket 
address".into())
         } else {
             ([127, 0, 0, 1], port).into()
         };
diff --git a/btc-wire/src/lib.rs b/btc-wire/src/lib.rs
index 534e57c..4bc533e 100644
--- a/btc-wire/src/lib.rs
+++ b/btc-wire/src/lib.rs
@@ -162,29 +162,28 @@ pub struct WireState {
 
 impl WireState {
     pub fn load_taler_config(file: Option<&Path>) -> Self {
-        let (taler_config, currency) = load_taler_config(file);
-        let btc_config = BitcoinConfig::load(taler_config.custom, currency)
-            .expect("Failed to read bitcoin configuration file");
-        let init_confirmation = 
taler_config.confirmation.unwrap_or(DEFAULT_CONFIRMATION) as u32;
+        let (taler_config, path, currency) = load_taler_config(file);
+        let btc_config =
+            BitcoinConfig::load(path, currency).expect("Failed to read bitcoin 
configuration file");
+        let init_confirmation = 
taler_config.confirmation().unwrap_or(DEFAULT_CONFIRMATION) as u32;
         Self {
             confirmation: AtomicU32::new(init_confirmation),
             max_confirmation: init_confirmation * 2,
-            btc_config,
-            bounce_fee: config_bounce_fee(&taler_config.bounce_fee, currency),
-            lifetime: taler_config.wire_lifetime,
-            bump_delay: taler_config.bump_delay,
-            base_url: taler_config.base_url,
-            db_config: taler_config.db_config,
+            bounce_fee: config_bounce_fee(&taler_config.bounce_fee(), 
currency),
+            lifetime: taler_config.wire_lifetime(),
+            bump_delay: taler_config.bump_delay(),
+            base_url: taler_config.base_url(),
+            db_config: taler_config.db_config(),
             currency,
+            btc_config,
         }
     }
 }
 
 // Load taler config with btc-wire specific config
-pub fn load_taler_config(file: Option<&Path>) -> (TalerConfig<PathBuf>, 
CurrencyBtc) {
-    let config = TalerConfig::load_with_custom(file, |dep| {
-        common::config::path(dep, "CONF_PATH").unwrap_or_else(default_data_dir)
-    });
+pub fn load_taler_config(file: Option<&Path>) -> (TalerConfig, PathBuf, 
CurrencyBtc) {
+    let config = TalerConfig::load(file);
+    let path = config.path("CONF_PATH").unwrap_or_else(default_data_dir);
     let currency = match config.currency {
         Currency::BTC(it) => it,
         _ => fail(format!(
@@ -192,7 +191,7 @@ pub fn load_taler_config(file: Option<&Path>) -> 
(TalerConfig<PathBuf>, Currency
             config.currency.to_str()
         )),
     };
-    (config, currency)
+    (config, path, currency)
 }
 
 // Parse bitcoin amount from config bounce fee
@@ -203,7 +202,7 @@ fn config_bounce_fee(bounce_fee: &Option<String>, currency: 
CurrencyBtc) -> Amou
         .and_then(|a| taler_to_btc(&a, currency))
         .or_fail(|a| {
             format!(
-                "config value BOUNCE_FEE={} is not a valid bitcoin amount: {}",
+                "config BOUNCE_FEE={} is not a valid bitcoin amount: {}",
                 config, a
             )
         })
diff --git a/btc-wire/src/main.rs b/btc-wire/src/main.rs
index 0304bf8..52c8b39 100644
--- a/btc-wire/src/main.rs
+++ b/btc-wire/src/main.rs
@@ -64,16 +64,16 @@ fn main() {
 
 fn init(config: Option<PathBuf>, init: Init) {
     // Parse taler config
-    let (taler_config, currency) = load_taler_config(config.as_deref());
+    let (taler_config, path, currency) = load_taler_config(config.as_deref());
 
     // Connect to database
     let mut db = taler_config
-        .db_config
+        .db_config()
         .connect(NoTls)
         .expect("Failed to connect to database");
     // Parse bitcoin config
-    let btc_conf = BitcoinConfig::load(taler_config.custom, currency)
-        .expect("Failed to load bitcoin configuration");
+    let btc_conf =
+        BitcoinConfig::load(path, currency).expect("Failed to load bitcoin 
configuration");
     // Connect to bitcoin node
     let mut rpc = Rpc::common(&btc_conf).expect("Failed to connect to bitcoin 
RPC server");
     match init {
diff --git a/common/src/config.rs b/common/src/config.rs
index 8e7ede9..252a47f 100644
--- a/common/src/config.rs
+++ b/common/src/config.rs
@@ -28,33 +28,14 @@ use crate::{
 pub use ini;
 
 // Depolymerizer taler config
-pub struct TalerConfig<T> {
-    // common
-    pub db_config: postgres::Config,
+pub struct TalerConfig {
+    conf: Ini,
+    section_name: &'static str,
     pub currency: Currency,
-    pub base_url: Url,
-    // wire-gateway
-    pub http_lifetime: Option<u32>,
-    pub port: u16,
-    pub unix_path: Option<PathBuf>,
-    // wire common
-    pub confirmation: Option<u16>,
-    pub bounce_fee: Option<String>,
-    pub wire_lifetime: Option<u32>,
-    pub bump_delay: Option<u32>,
-    pub payto: Option<Url>,
-    // custom config
-    pub custom: T,
 }
 
-impl TalerConfig<()> {
+impl TalerConfig {
     pub fn load(file: Option<&Path>) -> Self {
-        Self::load_with_custom(file, |_| {})
-    }
-}
-
-impl<T> TalerConfig<T> {
-    pub fn load_with_custom(file: Option<&Path>, lambda: fn(&Properties) -> T) 
-> Self {
         // Load config using taler-config
         let mut cmd = Command::new("taler-config");
         cmd.arg("-d");
@@ -76,44 +57,102 @@ impl<T> TalerConfig<T> {
             .expect("Failed to parse config");
         let taler = section(&conf, "taler");
         let currency = required(taler, "CURRENCY", string);
-        let currency = Currency::from_str(&currency).or_fail(|_| {
-            format!(
-                "config value CURRENCY={} is an unsupported currency",
-                currency
-            )
-        });
+        let currency = Currency::from_str(&currency)
+            .or_fail(|_| format!("config CURRENCY={} is an unsupported 
currency", currency));
         let section_name = match currency {
             Currency::BTC(_) => "depolymerizer-bitcoin",
             Currency::ETH(_) => "depolymerizer-ethereum",
         };
 
-        let dep = section(&conf, section_name);
         Self {
-            db_config: required(dep, "DB_URL", postgres),
+            conf,
+            section_name,
             currency,
-            base_url: required(section(&conf, "exchange"), "BASE_URL", url),
-            confirmation: nb(dep, "CONFIRMATION"),
-            bounce_fee: string(dep, "BOUNCE_FEE"),
-            wire_lifetime: nb(dep, "WIRE_LIFETIME")
-                .and_then(|nb| (nb != 0).then(|| Some(nb)))
-                .unwrap_or(None),
-            bump_delay: nb(dep, "BUMP_DELAY")
-                .and_then(|nb| (nb != 0).then(|| Some(nb)))
-                .unwrap_or(None),
-            port: nb(dep, "PORT").unwrap_or(8080),
-            unix_path: path(dep, "UNIXPATH"),
-            payto: url(dep, "PAYTO"),
-            http_lifetime: nb(dep, "HTTP_LIFETIME")
-                .and_then(|nb| (nb != 0).then(|| Some(nb)))
-                .unwrap_or(None),
-            custom: lambda(dep),
         }
     }
 
-    // Enforce payto requirement
-    pub fn require_payto(&self) -> Url {
-        expect_config(self.payto.clone(), "PAYTO")
+    fn section(&self) -> &Properties {
+        section(&self.conf, self.section_name)
+    }
+
+    fn non_zero_option(&self, name: &str) -> Option<u32> {
+        nb(self.section(), name)
+            .and_then(|nb| (nb != 0).then(|| Some(nb)))
+            .unwrap_or(None)
+    }
+}
+
+impl TalerConfig {
+    /* ----- Common ----- */
+
+    pub fn db_config(&self) -> postgres::Config {
+        required(self.section(), "DB_URL", postgres)
+    }
+
+    pub fn base_url(&self) -> Url {
+        required(section(&self.conf, "exchange"), "BASE_URL", url)
+    }
+
+    /* ----- Wire Gateway ----- */
+
+    pub fn payto(&self) -> Url {
+        required(self.section(), "PAYTO", url)
+    }
+
+    pub fn port(&self) -> u16 {
+        nb(self.section(), "PORT").unwrap_or(8080)
+    }
+
+    pub fn unix_path(&self) -> Option<PathBuf> {
+        path(self.section(), "UNIXPATH")
+    }
+
+    pub fn http_lifetime(&self) -> Option<u32> {
+        self.non_zero_option("HTTP_LIFETIME")
+    }
+
+    pub fn auth_method(&self) -> AuthMethod {
+        let section = self.section();
+        match required(section, "AUTH_METHOD", string).as_str() {
+            "none" => AuthMethod::None,
+            "basic" => AuthMethod::Basic(required(section, "AUTH_TOKEN", 
string)),
+            it => fail(format!(
+                "unknown config auth method AUTH_METHOD={} expected 'none' or 
'basic'",
+                it
+            )),
+        }
+    }
+
+    /* ----- Wire Common ----- */
+
+    pub fn confirmation(&self) -> Option<u16> {
+        nb(self.section(), "CONFIRMATION")
     }
+
+    pub fn bounce_fee(&self) -> Option<String> {
+        string(self.section(), "BOUNCE_FEE")
+    }
+
+    pub fn wire_lifetime(&self) -> Option<u32> {
+        self.non_zero_option("WIRE_LIFETIME")
+    }
+
+    pub fn bump_delay(&self) -> Option<u32> {
+        self.non_zero_option("BUMP_DELAY")
+    }
+
+    /* ----- Custom ----- */
+
+    pub fn path(&self, name: &str) -> Option<PathBuf> {
+        path(self.section(), name)
+    }
+}
+
+/* ----- Auth Method ----- */
+
+pub enum AuthMethod {
+    Basic(String),
+    None,
 }
 
 /* ----- Helper parsing functions ----- */
@@ -141,30 +180,26 @@ pub fn string(properties: &Properties, name: &str) -> 
Option<String> {
 
 pub fn path(properties: &Properties, name: &str) -> Option<PathBuf> {
     properties.get(name).map(|s| {
-        PathBuf::from_str(s).or_fail(|_| format!("config value {}={} is not a 
valid path", name, s))
+        PathBuf::from_str(s).or_fail(|_| format!("config {}={} is not a valid 
path", name, s))
     })
 }
 
 pub fn nb<T: FromStr>(properties: &Properties, name: &str) -> Option<T> {
     properties.get(name).map(|s| {
         s.parse()
-            .or_fail(|_| format!("config value {}={} is not a number", name, 
s))
+            .or_fail(|_| format!("config {}={} is not a number", name, s))
     })
 }
 
 pub fn url(properties: &Properties, name: &str) -> Option<Url> {
     properties.get(name).map(|s| {
-        Url::parse(s).or_fail(|e| format!("config value {}={} is not a valid 
url: {}", name, s, e))
+        Url::parse(s).or_fail(|e| format!("config {}={} is not a valid url: 
{}", name, s, e))
     })
 }
 
 pub fn postgres(properties: &Properties, name: &str) -> 
Option<postgres::Config> {
     properties.get(name).map(|s| {
-        postgres::Config::from_str(s).or_fail(|e| {
-            format!(
-                "config value {}={} is not a valid postgres url: {}",
-                name, s, e
-            )
-        })
+        postgres::Config::from_str(s)
+            .or_fail(|e| format!("config {}={} is not a valid postgres url: 
{}", name, s, e))
     })
 }
diff --git a/common/src/currency.rs b/common/src/currency.rs
index 14bc37a..9420762 100644
--- a/common/src/currency.rs
+++ b/common/src/currency.rs
@@ -16,12 +16,12 @@
 
 use std::str::FromStr;
 
-pub const MAIN_ETH: &str = "ETHEREUM-ETH";
-pub const ROPSTEN_ETH: &str = "TEST-ROPSTEN-ETH";
-pub const DEV_ETH: &str = "DEV-ETH";
-pub const MAIN_BTC: &str = "BITCOIN-BTC";
-pub const REGTEST_BTC: &str = "TEST-BTC";
-pub const TESTNET_BTC: &str = "DEV-BTC";
+pub const MAIN_ETH: &str = "ETHEREUMETH";
+pub const ROPSTEN_ETH: &str = "TESTROPSTENETH";
+pub const DEV_ETH: &str = "DEVETH";
+pub const MAIN_BTC: &str = "BITCOINBTC";
+pub const REGTEST_BTC: &str = "TESTBTC";
+pub const TESTNET_BTC: &str = "DEVBTC";
 
 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
 pub enum Currency {
diff --git a/docs/taler-btc.conf b/docs/taler-btc-full.conf
similarity index 79%
copy from docs/taler-btc.conf
copy to docs/taler-btc-full.conf
index 196fff7..915f279 100644
--- a/docs/taler-btc.conf
+++ b/docs/taler-btc-full.conf
@@ -1,6 +1,6 @@
 # Full btc-wire configuration
 [taler]
-CURRENCY      = BITCOIN-BTC
+CURRENCY      = BITCOINBTC
 
 [exchange]
 BASE_URL      = http://test.com
@@ -8,7 +8,10 @@ BASE_URL      = http://test.com
 [depolymerizer-bitcoin]
 DB_URL        = postgres://%2Fvar%2Frun%2Fpostgresql/btc-wire
 PAYTO         = payto://bitcoin/bc1qcr40fzphnh4mcwlv65kvdam4dxq977t2rn54qx
+AUTH_METHOD   = basic
+AUTH_TOKEN    = YWRtaW46cGFzc3dvcmQ=
 PORT          = 8080
+UNIX_PATH     =
 CONF_PATH     = ~/.bitcoin
 CONFIRMATION  = 6
 BOUNCE_FEE    = 0.00001
diff --git a/docs/taler-btc.conf b/docs/taler-btc-min.conf
similarity index 51%
rename from docs/taler-btc.conf
rename to docs/taler-btc-min.conf
index 196fff7..328a3cc 100644
--- a/docs/taler-btc.conf
+++ b/docs/taler-btc-min.conf
@@ -1,6 +1,6 @@
-# Full btc-wire configuration
+# Minimal btc-wire configuration
 [taler]
-CURRENCY      = BITCOIN-BTC
+CURRENCY      = BITCOINBTC
 
 [exchange]
 BASE_URL      = http://test.com
@@ -8,10 +8,4 @@ BASE_URL      = http://test.com
 [depolymerizer-bitcoin]
 DB_URL        = postgres://%2Fvar%2Frun%2Fpostgresql/btc-wire
 PAYTO         = payto://bitcoin/bc1qcr40fzphnh4mcwlv65kvdam4dxq977t2rn54qx
-PORT          = 8080
-CONF_PATH     = ~/.bitcoin
-CONFIRMATION  = 6
-BOUNCE_FEE    = 0.00001
-HTTP_LIFETIME = 0
-WIRE_LIFETIME = 0
-BUMP_DELAY    = 0
+AUTH_METHOD   = none
diff --git a/docs/taler-eth.conf b/docs/taler-eth-full.conf
similarity index 79%
rename from docs/taler-eth.conf
rename to docs/taler-eth-full.conf
index 83a2c6a..9729a26 100644
--- a/docs/taler-eth.conf
+++ b/docs/taler-eth-full.conf
@@ -1,6 +1,6 @@
 # Full eth-wire configuration
 [taler]
-CURRENCY      = ETHEREUM-ETH
+CURRENCY      = ETHEREUMETH
 
 [exchange]
 BASE_URL      = http://test.com
@@ -8,7 +8,10 @@ BASE_URL      = http://test.com
 [depolymerizer-ethereum]
 DB_URL        = postgres://%2Fvar%2Frun%2Fpostgresql/eth-wire
 PAYTO         = payto://ethereum/425870d7b77ad5665ca982ff85c398222a70fe7c
+AUTH_METHOD   = basic
+AUTH_TOKEN    = YWRtaW46cGFzc3dvcmQ=
 PORT          = 8080
+UNIX_PATH     =
 IPC_PATH      = ~/.ethereum/geth/geth.ipc
 CONFIRMATION  = 24
 BOUNCE_FEE    = 0.00001
diff --git a/docs/taler-eth-min.conf b/docs/taler-eth-min.conf
new file mode 100644
index 0000000..2616bad
--- /dev/null
+++ b/docs/taler-eth-min.conf
@@ -0,0 +1,11 @@
+# Minimal eth-wire configuration
+[taler]
+CURRENCY      = ETHEREUMETH
+
+[exchange]
+BASE_URL      = http://test.com
+
+[depolymerizer-ethereum]
+DB_URL        = postgres://%2Fvar%2Frun%2Fpostgresql/eth-wire
+PAYTO         = payto://ethereum/425870d7b77ad5665ca982ff85c398222a70fe7c
+AUTH_METHOD   = none
diff --git a/eth-wire/src/bin/eth-wire-utils.rs 
b/eth-wire/src/bin/eth-wire-utils.rs
index 7173807..f6da903 100644
--- a/eth-wire/src/bin/eth-wire-utils.rs
+++ b/eth-wire/src/bin/eth-wire-utils.rs
@@ -104,9 +104,9 @@ enum Cmd {
 fn main() {
     init();
     let args: Args = Args::parse();
-    let (taler_config, currency) = load_taler_config(args.config.as_deref());
+    let (taler_config, ipc_path, currency) = 
load_taler_config(args.config.as_deref());
 
-    let ipc_path = args.datadir.unwrap_or(taler_config.custom);
+    let ipc_path = args.datadir.unwrap_or(ipc_path);
     let mut rpc = Rpc::new(ipc_path).unwrap();
     let passwd = password();
     match args.cmd {
@@ -167,7 +167,7 @@ fn main() {
         }
         Cmd::Resetdb => {
             let block = rpc.earliest_block().unwrap();
-            let mut db = taler_config.db_config.connect(NoTls).unwrap();
+            let mut db = taler_config.db_config().connect(NoTls).unwrap();
             let mut tx = db.transaction().unwrap();
             // Clear transaction tables and reset state
             tx.execute("DELETE FROM tx_in", &[]).unwrap();
diff --git a/eth-wire/src/lib.rs b/eth-wire/src/lib.rs
index b615acd..90117cc 100644
--- a/eth-wire/src/lib.rs
+++ b/eth-wire/src/lib.rs
@@ -233,19 +233,19 @@ pub struct WireState {
 
 impl WireState {
     pub fn load_taler_config(file: Option<&Path>) -> Self {
-        let (taler_config, currency) = load_taler_config(file);
-        let init_confirmation = 
taler_config.confirmation.unwrap_or(DEFAULT_CONFIRMATION) as u32;
-        let payto = taler_config.require_payto();
+        let (taler_config, ipc_path, currency) = load_taler_config(file);
+        let init_confirmation = 
taler_config.confirmation().unwrap_or(DEFAULT_CONFIRMATION) as u32;
+        let payto = taler_config.payto();
         Self {
             confirmation: AtomicU32::new(init_confirmation),
             max_confirmations: init_confirmation * 2,
             address: eth_payto_addr(&payto).unwrap(),
-            ipc_path: taler_config.custom,
-            bounce_fee: config_bounce_fee(&taler_config.bounce_fee, currency),
-            lifetime: taler_config.wire_lifetime,
-            bump_delay: taler_config.bump_delay,
-            base_url: taler_config.base_url,
-            db_config: taler_config.db_config,
+            ipc_path,
+            bounce_fee: config_bounce_fee(&taler_config.bounce_fee(), 
currency),
+            lifetime: taler_config.wire_lifetime(),
+            bump_delay: taler_config.bump_delay(),
+            base_url: taler_config.base_url(),
+            db_config: taler_config.db_config(),
             payto,
             currency,
         }
@@ -253,10 +253,9 @@ impl WireState {
 }
 
 // Load taler config with eth-wire specific config
-pub fn load_taler_config(file: Option<&Path>) -> (TalerConfig<PathBuf>, 
CurrencyEth) {
-    let config = TalerConfig::load_with_custom(file, |dep| {
-        common::config::path(dep, "IPC_PATH").unwrap_or_else(default_data_dir)
-    });
+pub fn load_taler_config(file: Option<&Path>) -> (TalerConfig, PathBuf, 
CurrencyEth) {
+    let config = TalerConfig::load(file);
+    let path = config.path("IPC_PATH").unwrap_or_else(default_data_dir);
     let currency = match config.currency {
         Currency::ETH(it) => it,
         _ => fail(format!(
@@ -264,7 +263,7 @@ pub fn load_taler_config(file: Option<&Path>) -> 
(TalerConfig<PathBuf>, Currency
             config.currency.to_str()
         )),
     };
-    (config, currency)
+    (config, path, currency)
 }
 
 // Parse ethereum value from config bounce fee
@@ -275,7 +274,7 @@ fn config_bounce_fee(bounce_fee: &Option<String>, currency: 
CurrencyEth) -> U256
         .and_then(|a| taler_to_eth(&a, currency))
         .or_fail(|a| {
             format!(
-                "config value BOUNCE_FEE={} is not a valid ethereum amount: 
{}",
+                "config BOUNCE_FEE={} is not a valid ethereum amount: {}",
                 config, a
             )
         })
diff --git a/eth-wire/src/main.rs b/eth-wire/src/main.rs
index 85e9274..70b748e 100644
--- a/eth-wire/src/main.rs
+++ b/eth-wire/src/main.rs
@@ -19,8 +19,9 @@ use std::path::PathBuf;
 use clap::StructOpt;
 use common::{named_spawn, password, postgres::NoTls, 
reconnect::auto_reconnect_db};
 use eth_wire::{
+    load_taler_config,
     rpc::{auto_rpc_common, auto_rpc_wallet, Rpc, RpcClient},
-    SyncState, WireState, load_taler_config,
+    SyncState, WireState,
 };
 use ethereum_types::H160;
 use loops::{analysis::analysis, watcher::watcher, worker::worker};
@@ -59,14 +60,14 @@ fn main() {
 
 fn init(config: Option<PathBuf>, init: Init) {
     // Parse taler config
-    let (taler_config, _) = load_taler_config(config.as_deref());
+    let (taler_config, ipc_path, _) = load_taler_config(config.as_deref());
     // Connect to database
     let mut db = taler_config
-        .db_config
+        .db_config()
         .connect(NoTls)
         .expect("Failed to connect to database");
     // Connect to ethereum node
-    let mut rpc = Rpc::new(taler_config.custom).expect("Failed to connect to 
ethereum RPC server");
+    let mut rpc = Rpc::new(ipc_path).expect("Failed to connect to ethereum RPC 
server");
 
     match init {
         Init::Initdb => {
diff --git a/instrumentation/src/main.rs b/instrumentation/src/main.rs
index db509ea..cc79f9c 100644
--- a/instrumentation/src/main.rs
+++ b/instrumentation/src/main.rs
@@ -94,7 +94,7 @@ pub fn main() {
     common::log::init();
     let args = Args::parse();
     let taler_config = TalerConfig::load(args.config.as_deref());
-    let base_url = format!("http://localhost:{}";, taler_config.port);
+    let base_url = format!("http://localhost:{}";, taler_config.port());
 
     match taler_config.currency {
         Currency::BTC(_) => btc_test(args.config.as_deref(), &base_url),
diff --git a/makefile b/makefile
index 91178a5..28a8316 100644
--- a/makefile
+++ b/makefile
@@ -10,6 +10,7 @@ install_test: install
 
 test_gateway: install_test
        test/gateway/api.sh
+       test/gateway/auth.sh
 
 test_btc: install_test
        test/btc/wire.sh
diff --git a/test/common.sh b/test/common.sh
index a7637f7..7ea872a 100644
--- a/test/common.sh
+++ b/test/common.sh
@@ -50,7 +50,7 @@ function load_config() {
     echo -e "\nCONF_PATH = ${WIRE_DIR}" >> $CONF
     echo -e "IPC_PATH = ${WIRE_DIR}" >> $CONF
     source <(grep = $CONF | sed 's/ *= */=/' | sed 's/=\(.*\)/="\1"/g1')
-    BANK_ENDPOINT=http://127.0.0.1:$PORT/
+    BANK_ENDPOINT=http://127.0.0.1:${PORT:-8080}/
     if [[ "$CURRENCY" =~ "BTC" ]]; then
         WIRE_CLI="btc-wire -c $CONF"
         WIRE_UTILS="btc-wire-utils -c $CONF"
diff --git a/test/conf/taler_btc.conf b/test/conf/taler_btc.conf
index bee9768..e7cf964 100644
--- a/test/conf/taler_btc.conf
+++ b/test/conf/taler_btc.conf
@@ -1,5 +1,5 @@
 [taler]
-CURRENCY     = DEV-BTC
+CURRENCY     = DEVBTC
 
 [exchange]
 BASE_URL     = http://test.com
@@ -9,3 +9,4 @@ DB_URL       = 
postgres://localhost:5454/postgres?user=postgres&password=passwor
 PORT         = 8060
 PAYTO        = payto://bitcoin/bcrt1qgkgxkjj27g3f7s87mcvjjsghay7gh34cx39prj
 CONFIRMATION = 3
+AUTH_METHOD  = none
diff --git a/test/conf/taler_btc_auth.conf b/test/conf/taler_btc_auth.conf
new file mode 100644
index 0000000..1424b6a
--- /dev/null
+++ b/test/conf/taler_btc_auth.conf
@@ -0,0 +1,19 @@
+[taler]
+CURRENCY                 = DEVBTC
+
+[exchange]
+BASE_URL                 = http://test.com
+
+[exchange-accountcredentials-admin]
+WIRE_GATEWAY_URL         = http://localhost:8060/
+WIRE_GATEWAY_AUTH_METHOD = basic
+USERNAME                 = admin
+PASSWORD                 = password
+
+[depolymerizer-bitcoin]
+DB_URL                   = 
postgres://localhost:5454/postgres?user=postgres&password=password
+PORT                     = 8060
+PAYTO                    = 
payto://bitcoin/bcrt1qgkgxkjj27g3f7s87mcvjjsghay7gh34cx39prj
+AUTH_METHOD              = basic
+AUTH_TOKEN               = YWRtaW46cGFzc3dvcmQ=
+
diff --git a/test/conf/taler_btc_bump.conf b/test/conf/taler_btc_bump.conf
index 9d2ba29..a3f5389 100644
--- a/test/conf/taler_btc_bump.conf
+++ b/test/conf/taler_btc_bump.conf
@@ -1,5 +1,5 @@
 [taler]
-CURRENCY      = DEV-BTC
+CURRENCY      = DEVBTC
 
 [exchange]
 BASE_URL      = http://test.com
@@ -10,4 +10,5 @@ PORT          = 8060
 PAYTO         = payto://bitcoin/bcrt1qgkgxkjj27g3f7s87mcvjjsghay7gh34cx39prj
 CONFIRMATION  = 3
 BUMP_DELAY    = 5
-BOUNCE_FEE    = 0.00001
\ No newline at end of file
+BOUNCE_FEE    = 0.00001
+AUTH_METHOD  = none
\ No newline at end of file
diff --git a/test/conf/taler_btc_lifetime.conf 
b/test/conf/taler_btc_lifetime.conf
index 8119783..c24c27f 100644
--- a/test/conf/taler_btc_lifetime.conf
+++ b/test/conf/taler_btc_lifetime.conf
@@ -1,5 +1,5 @@
 [taler]
-CURRENCY      = DEV-BTC
+CURRENCY      = DEVBTC
 
 [exchange]
 BASE_URL      = http://test.com
@@ -10,4 +10,5 @@ PORT          = 8060
 PAYTO         = payto://bitcoin/bcrt1qgkgxkjj27g3f7s87mcvjjsghay7gh34cx39prj
 CONFIRMATION  = 3
 HTTP_LIFETIME = 10
-WIRE_LIFETIME = 10
\ No newline at end of file
+WIRE_LIFETIME = 10
+AUTH_METHOD  = none
\ No newline at end of file
diff --git a/test/conf/taler_eth.conf b/test/conf/taler_eth.conf
index 48f53b7..b5fabc1 100644
--- a/test/conf/taler_eth.conf
+++ b/test/conf/taler_eth.conf
@@ -1,5 +1,5 @@
 [taler]
-CURRENCY     = DEV-ETH
+CURRENCY     = DEVETH
 
 [exchange]
 BASE_URL     = http://test.com
@@ -7,4 +7,5 @@ BASE_URL     = http://test.com
 [depolymerizer-ethereum]
 DB_URL       = 
postgres://localhost:5454/postgres?user=postgres&password=password
 PORT         = 8060
-CONFIRMATION = 3
\ No newline at end of file
+CONFIRMATION = 3
+AUTH_METHOD  = none
\ No newline at end of file
diff --git a/test/conf/taler_eth_bump.conf b/test/conf/taler_eth_bump.conf
index 91756ea..4180be5 100644
--- a/test/conf/taler_eth_bump.conf
+++ b/test/conf/taler_eth_bump.conf
@@ -1,5 +1,5 @@
 [taler]
-CURRENCY     = DEV-ETH
+CURRENCY     = DEVETH
 
 [exchange]
 BASE_URL     = http://test.com
@@ -8,4 +8,5 @@ BASE_URL     = http://test.com
 DB_URL       = 
postgres://localhost:5454/postgres?user=postgres&password=password
 PORT         = 8060
 CONFIRMATION = 3
-BUMP_DELAY   = 5
\ No newline at end of file
+BUMP_DELAY   = 5
+AUTH_METHOD  = none
\ No newline at end of file
diff --git a/test/conf/taler_eth_lifetime.conf 
b/test/conf/taler_eth_lifetime.conf
index 4dadebc..f27e8a8 100644
--- a/test/conf/taler_eth_lifetime.conf
+++ b/test/conf/taler_eth_lifetime.conf
@@ -1,5 +1,5 @@
 [taler]
-CURRENCY      = DEV-ETH
+CURRENCY      = DEVETH
 
 [exchange]
 BASE_URL      = http://test.com
@@ -9,4 +9,5 @@ DB_URL        = 
postgres://localhost:5454/postgres?user=postgres&password=passwo
 PORT          = 8060
 CONFIRMATION  = 3
 HTTP_LIFETIME = 10
-WIRE_LIFETIME = 10
\ No newline at end of file
+WIRE_LIFETIME = 10
+AUTH_METHOD  = none
\ No newline at end of file
diff --git a/test/gateway/api.sh b/test/gateway/api.sh
index 799f4ab..f81b7e4 100644
--- a/test/gateway/api.sh
+++ b/test/gateway/api.sh
@@ -17,7 +17,6 @@ function cleanup() {
 trap cleanup EXIT
 
 source "${BASH_SOURCE%/*}/../common.sh"
-ADDRESS=mpTJZxWPerz1Gife6mQSdHT8mMuJK6FP85
 CONFIG=taler_btc.conf
 
 echo  "----- Setup -----"
@@ -35,7 +34,7 @@ echo -n "Making wire transfer to exchange:"
 for n in `seq 1 9`; do
     taler-exchange-wire-gateway-client \
         -b $BANK_ENDPOINT \
-        -D payto://bitcoin/$ADDRESS \
+        -D payto://bitcoin/$CLIENT \
         -a $CURRENCY:0.0000$n > /dev/null
 done
 echo " OK"
@@ -51,7 +50,7 @@ echo -n "Making wire transfer from exchange:"
 for n in `seq 1 9`; do
    taler-exchange-wire-gateway-client \
     -b $BANK_ENDPOINT \
-    -C payto://bitcoin/$ADDRESS \
+    -C payto://bitcoin/$CLIENT \
     -a $CURRENCY:0.0000$n > /dev/null
 done
 
@@ -75,16 +74,16 @@ test `curl -w %{http_code} -s -o /dev/null 
${BANK_ENDPOINT}transfer` -eq 405 &&
 echo "----- Request format -----"
 
 echo -n "Bad payto url:"
-for bad_payto in http://bitcoin/$ADDRESS payto://btc/$ADDRESS 
payto://bitcoin/$ADDRESS?id=admin payto://bitcoin/$ADDRESS#admin; do
+for bad_payto in http://bitcoin/$CLIENT payto://btc/$CLIENT 
payto://bitcoin/$CLIENT?id=admin payto://bitcoin/$CLIENT#admin; do
    taler-exchange-wire-gateway-client -b $BANK_ENDPOINT -C $bad_payto -a 
$CURRENCY:0.00042 2>&1 | grep -q "(400/24)" && echo -n " OK" || echo " Failed"
 done
 echo ""
 
 echo -n "Bad bitcoin address:"
-taler-exchange-wire-gateway-client -b $BANK_ENDPOINT -C 
payto://bitcoin/42$ADDRESS -a $CURRENCY:0.00042 2>&1 | grep -q "(400/24)" && 
echo " OK" || echo " Failed"
+taler-exchange-wire-gateway-client -b $BANK_ENDPOINT -C 
payto://bitcoin/42$CLIENT -a $CURRENCY:0.00042 2>&1 | grep -q "(400/24)" && 
echo " OK" || echo " Failed"
 
 echo -n "Bad transaction amount:"
-taler-exchange-wire-gateway-client -b $BANK_ENDPOINT -C 
payto://bitcoin/$ADDRESS -a ATC:0.00042 2>&1 | grep -q "(400/26)" && echo " OK" 
|| echo " Failed"
+taler-exchange-wire-gateway-client -b $BANK_ENDPOINT -C 
payto://bitcoin/$CLIENT -a ATC:0.00042 2>&1 | grep -q "(400/26)" && echo " OK" 
|| echo " Failed"
 
 echo -n "Bad history delta:"
 for bad_delta in incoming outgoing incoming?delta=0 outgoing?delta=0; do
@@ -106,12 +105,12 @@ for endpoint in incoming outgoing; do
 done
 
 echo "----- Transfer idempotence -----"
-DATA="{\"request_uid\":\"0ZSX8SH0M30KHX8K3Y1DAMVGDQV82XEF9DG1HC4QMQ3QWYT4AF00ZSX8SH0M30KHX8K3Y1DAMVGDQV82XEF9DG1HC4QMQ3QWYT4AF00\",\"amount\":\"$CURRENCY:0.000034\",\"exchange_base_url\":\"$BASE_URL\",\"wtid\":\"0ZSX8SH0M30KHX8K3Y1DAMVGDQV82XEF9DG1HC4QMQ3QWYT4AF00\",\"credit_account\":\"payto://bitcoin/$ADDRESS\"}"
+DATA="{\"request_uid\":\"0ZSX8SH0M30KHX8K3Y1DAMVGDQV82XEF9DG1HC4QMQ3QWYT4AF00ZSX8SH0M30KHX8K3Y1DAMVGDQV82XEF9DG1HC4QMQ3QWYT4AF00\",\"amount\":\"$CURRENCY:0.000034\",\"exchange_base_url\":\"$BASE_URL\",\"wtid\":\"0ZSX8SH0M30KHX8K3Y1DAMVGDQV82XEF9DG1HC4QMQ3QWYT4AF00\",\"credit_account\":\"payto://bitcoin/$CLIENT\"}"
 echo -n "Same:"
 test `curl -w %{http_code} -s -o /dev/null -H "Content-Type: application/json" 
-d $DATA ${BANK_ENDPOINT}transfer` -eq 200 && echo -n " OK" || echo -n " Failed"
 test `curl -w %{http_code} -s -o /dev/null -H "Content-Type: application/json" 
-d $DATA ${BANK_ENDPOINT}transfer` -eq 200 && echo " OK" || echo " Failed"
 echo -n "Collision:"
-DATA="{\"request_uid\":\"0ZSX8SH0M30KHX8K3Y1DAMVGDQV82XEF9DG1HC4QMQ3QWYT4AF00ZSX8SH0M30KHX8K3Y1DAMVGDQV82XEF9DG1HC4QMQ3QWYT4AF00\",\"amount\":\"$CURRENCY:0.000042\",\"exchange_base_url\":\"$BASE_URL\",\"wtid\":\"0ZSX8SH0M30KHX8K3Y1DAMVGDQV82XEF9DG1HC4QMQ3QWYT4AF00\",\"credit_account\":\"payto://bitcoin/$ADDRESS\"}"
+DATA="{\"request_uid\":\"0ZSX8SH0M30KHX8K3Y1DAMVGDQV82XEF9DG1HC4QMQ3QWYT4AF00ZSX8SH0M30KHX8K3Y1DAMVGDQV82XEF9DG1HC4QMQ3QWYT4AF00\",\"amount\":\"$CURRENCY:0.000042\",\"exchange_base_url\":\"$BASE_URL\",\"wtid\":\"0ZSX8SH0M30KHX8K3Y1DAMVGDQV82XEF9DG1HC4QMQ3QWYT4AF00\",\"credit_account\":\"payto://bitcoin/$CLIENT\"}"
 test `curl -w %{http_code} -s -o /dev/null -H "Content-Type: application/json" 
-d $DATA ${BANK_ENDPOINT}transfer` -eq 409 && echo " OK" || echo " Failed"
 
 echo "----- Security -----"
diff --git a/test/gateway/auth.sh b/test/gateway/auth.sh
new file mode 100644
index 0000000..240f22e
--- /dev/null
+++ b/test/gateway/auth.sh
@@ -0,0 +1,31 @@
+#!/bin/bash
+
+## Test wire-gateway authentication
+
+set -eu
+
+source "${BASH_SOURCE%/*}/../common.sh"
+CONFIG=taler_btc_auth.conf
+
+echo  "----- Setup -----"
+echo "Load config file"
+load_config
+echo "Start bitcoin node"
+init_btc
+echo "Start gateway"
+gateway
+echo ""
+
+echo "----- Authentication -----"
+
+echo -n "Check 401:"
+test `curl -w %{http_code} -s -o /dev/null ${BANK_ENDPOINT}history/outgoing` 
-eq 401 && echo " OK" || echo " Failed"
+
+echo -n "Check auth:"
+taler-exchange-wire-gateway-client \
+    --config $CONF -s exchange-accountcredentials-admin \
+    -C payto://bitcoin/$CLIENT \
+    -a $CURRENCY:0.0000$n > /dev/null
+echo " OK"
+
+echo "All tests passed!"
diff --git a/wire-gateway/src/main.rs b/wire-gateway/src/main.rs
index 49f5310..45ee186 100644
--- a/wire-gateway/src/main.rs
+++ b/wire-gateway/src/main.rs
@@ -20,7 +20,7 @@ use common::{
         HistoryParams, IncomingBankTransaction, IncomingHistory, 
OutgoingBankTransaction,
         OutgoingHistory, TransferRequest, TransferResponse,
     },
-    config::TalerConfig,
+    config::{AuthMethod, TalerConfig},
     currency::Currency,
     error_codes::ErrorCode,
     log::{
@@ -61,6 +61,7 @@ struct ServerState {
     notify: Notify,
     lifetime: Option<AtomicU32>,
     status: AtomicBool,
+    auth: AuthMethod,
 }
 
 impl ServerState {
@@ -109,17 +110,17 @@ async fn main() {
     common::log::log::warn!("Running with test admin endpoint unsuitable for 
production");
 
     // Parse postgres url
-    let config = &taler_config.db_config;
+    let db_config = taler_config.db_config();
     // TODO find a way to clean this ugly mess
     let mut cfg = deadpool_postgres::Config::new();
-    cfg.user = config.get_user().map(|it| it.to_string());
-    cfg.password = config
+    cfg.user = db_config.get_user().map(|it| it.to_string());
+    cfg.password = db_config
         .get_password()
         .map(|it| String::from_utf8(it.to_vec()).unwrap());
-    cfg.dbname = config.get_dbname().map(|it| it.to_string());
-    cfg.options = config.get_options().map(|it| it.to_string());
+    cfg.dbname = db_config.get_dbname().map(|it| it.to_string());
+    cfg.options = db_config.get_options().map(|it| it.to_string());
     cfg.host = Some(
-        config
+        db_config
             .get_hosts()
             .iter()
             .map(|it| match it {
@@ -129,20 +130,21 @@ async fn main() {
             })
             .collect(),
     );
-    cfg.ports = Some(config.get_ports().to_vec());
-    cfg.application_name = config.get_application_name().map(|it| 
it.to_string());
-    cfg.connect_timeout = config.get_connect_timeout().cloned();
+    cfg.ports = Some(db_config.get_ports().to_vec());
+    cfg.application_name = db_config.get_application_name().map(|it| 
it.to_string());
+    cfg.connect_timeout = db_config.get_connect_timeout().cloned();
 
     let pool = cfg.create_pool(Some(Runtime::Tokio1), NoTls).unwrap();
-    let payto = taler_config.require_payto();
+    let payto = taler_config.payto();
     let state = ServerState {
         pool,
         notify: Notify::new(),
-        lifetime: taler_config.http_lifetime.map(AtomicU32::new),
+        lifetime: taler_config.http_lifetime().map(AtomicU32::new),
         status: AtomicBool::new(true),
-        db_config: taler_config.db_config,
+        db_config,
         payto,
         currency: taler_config.currency,
+        auth: taler_config.auth_method(),
     };
     let state: &'static ServerState = Box::leak(Box::new(state));
     std::thread::spawn(move || status_watcher(state));
@@ -190,7 +192,7 @@ async fn main() {
         if let Err(e) = server.await {
             error!("server: {}", e);
         }
-    } else if let Some(path) = taler_config.unix_path {
+    } else if let Some(path) = taler_config.unix_path() {
         use hyperlocal::UnixServerExt;
         info!("Server listening on unix domain socket {:?}", path);
         if let Err(err) = std::fs::remove_file(&path) {
@@ -206,7 +208,7 @@ async fn main() {
             error!("server: {}", e);
         }
     } else {
-        let addr = ([0, 0, 0, 0], taler_config.port).into();
+        let addr = ([0, 0, 0, 0], taler_config.port()).into();
         info!("Server listening on http://{}";, &addr);
         let server = Server::bind(&addr)
             .serve(make_service)
@@ -301,6 +303,24 @@ async fn router(
             .unwrap());
     }
 
+    // Check auth method
+    match &state.auth {
+        AuthMethod::Basic(auth) => {
+            if !matches!(parts.headers
+                .get(hyper::header::AUTHORIZATION)
+                .and_then(|h| h.to_str().ok())
+                .and_then(|s| s.strip_prefix("Basic ")),
+                Some(token) if token == auth )
+            {
+                return Ok(Response::builder()
+                    .status(StatusCode::UNAUTHORIZED)
+                    .body(Body::empty())
+                    .unwrap());
+            }
+        }
+        AuthMethod::None => {}
+    }
+
     let response = match parts.uri.path() {
         "/transfer" => {
             assert_method(parts, Method::POST)?;

-- 
To stop receiving notification emails like this one, please contact
gnunet@gnunet.org.



reply via email to

[Prev in Thread] Current Thread [Next in Thread]