From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from mails.dpdk.org (mails.dpdk.org [217.70.189.124]) by inbox.dpdk.org (Postfix) with ESMTP id CBCC243D2E; Sat, 23 Mar 2024 11:09:56 +0100 (CET) Received: from mails.dpdk.org (localhost [127.0.0.1]) by mails.dpdk.org (Postfix) with ESMTP id A33A6402BE; Sat, 23 Mar 2024 11:09:56 +0100 (CET) Received: from NAM10-DM6-obe.outbound.protection.outlook.com (mail-dm6nam10on2055.outbound.protection.outlook.com [40.107.93.55]) by mails.dpdk.org (Postfix) with ESMTP id B49F1402BD; Sat, 23 Mar 2024 11:09:54 +0100 (CET) ARC-Seal: i=1; a=rsa-sha256; s=arcselector9901; d=microsoft.com; cv=none; b=LzuGd3ZHM2oOrk7j7R/EmwQbCKSF9HIVVcqKhtJmCFYety1NqcinhAwOovHWJq9z/EyNL4CHDcvJs/kwg9tTjr5/R5Icay6FHW1CRWtYctepX+sSKsCTA29FqG9zHC9w97gxg9xkag95TsfHTSIIbKNRqtPRWSLVseh1Wu/iUAnw5ADHonLc5nHhwOc9rIOroz1Yrew5EeBCkH8/B1JuaZ4k1m2KxGtpdVwuTzGOsSniVkN5aM8jKf8rrNTvWm6YbgW5qdLLpqDwmncBZYcuptIFf0hwU+QVM6VTv5iDsw1DsanBHYMj6slM6irWZK3/T7Ku7R9BDres0GXnzK+XgA== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=microsoft.com; s=arcselector9901; h=From:Date:Subject:Message-ID:Content-Type:MIME-Version:X-MS-Exchange-AntiSpam-MessageData-ChunkCount:X-MS-Exchange-AntiSpam-MessageData-0:X-MS-Exchange-AntiSpam-MessageData-1; bh=dGfzY7tTVDkzPVuf37TLXtA4usaPV/fzWoUL4RUgO0Y=; b=nIxArq6VSQbG4GKLWRlg9BCpZvMyfaJFq5/VZPYXnuENtxhJZ/LqT460DY8P7/35jVVQTA6UPPkphhT2jh51+Lb0PwB4PHvTTSOXaL/PcOt7uqrmMqmOGZVlPrsFNcdC9uqZEZKr+hTqyor/R+eYKUmVdNcPpacbsKkbBiYhCNog8WctUJs1+sxHqqwUAoAFDS8eTbzbbYZeO8J5f2Q7EgT9RYxshRf/PxJktCrdJdSXy6w6ZjdX6E8PluYf8S+E/M8dSjTsStm9hXd5hds2h7dnVd0nxnWUWFAuIg+fJMkC81EXcQby7+i6MT9qcFKDXDkLgxf6MGi7yN0l3x9FpA== ARC-Authentication-Results: i=1; mx.microsoft.com 1; spf=pass smtp.mailfrom=nvidia.com; dmarc=pass action=none header.from=nvidia.com; dkim=pass header.d=nvidia.com; arc=none DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=Nvidia.com; s=selector2; h=From:Date:Subject:Message-ID:Content-Type:MIME-Version:X-MS-Exchange-SenderADCheck; bh=dGfzY7tTVDkzPVuf37TLXtA4usaPV/fzWoUL4RUgO0Y=; b=Dy7iUGqtAZCZw9nJ+zB3E+JilEBwkfYniBpBy/TqxZN1APsUuJHwPcNW7UUELjlmnqIqKaZZAcg56KolhjPiuC/uqn7PhFj4yZqb/9iTZH1cPETvr2sZVT+WU6OXnYsTPEUxgji/VvulQIvVN5sxleu6mUk6tY9ol1kKO5yeFHiJCVsgOi2gCpeMeBjE7dF6L5+MxDB6gCaohsuCrCn+T6OkRuUN/7g331UXW/td0n2YgYWIHlg33E2mEOPK+yeg82hxQdwxkJibXz1e3WEpIYIuEYMcZksBAKsQPUzLlpKmN8q58cZHT43Xd9BLKXMX8tcnNA9E58RQqFWF21eA9A== Received: from DS7PR12MB6336.namprd12.prod.outlook.com (2603:10b6:8:93::8) by SN7PR12MB8059.namprd12.prod.outlook.com (2603:10b6:806:32b::7) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.20.7409.15; Sat, 23 Mar 2024 10:09:51 +0000 Received: from DS7PR12MB6336.namprd12.prod.outlook.com ([fe80::6663:e249:6313:776d]) by DS7PR12MB6336.namprd12.prod.outlook.com ([fe80::6663:e249:6313:776d%6]) with mapi id 15.20.7409.022; Sat, 23 Mar 2024 10:09:51 +0000 From: Gregory Etelson To: Ferruh Yigit , Shani Peretz , "dev@dpdk.org" CC: Maayan Kashani , Ori Kam , Aman Singh , Yuying Zhang , "ci@dpdk.org" , Honnappa Nagarahalli , Patrick Robb , Asaf Penso Subject: Re: [PATCH] app/testpmd: fix auto completion for indirect list action Thread-Topic: [PATCH] app/testpmd: fix auto completion for indirect list action Thread-Index: AQHaeRWts5MFtI3UGU+ry2X7SeClerE/J6AAgAAKyICAAPA+B4AASF8AgASwVU4= Date: Sat, 23 Mar 2024 10:09:51 +0000 Message-ID: References: <20240318092109.87656-1-shperetz@nvidia.com> <62669854-8add-4fdd-b882-a63d78f0d6e8@amd.com> <46653254-c995-4de0-82fa-2531ed4cfaa0@amd.com> In-Reply-To: <46653254-c995-4de0-82fa-2531ed4cfaa0@amd.com> Accept-Language: en-US Content-Language: en-US X-MS-Has-Attach: X-MS-TNEF-Correlator: msip_labels: authentication-results: dkim=none (message not signed) header.d=none;dmarc=none action=none header.from=nvidia.com; x-ms-publictraffictype: Email x-ms-traffictypediagnostic: DS7PR12MB6336:EE_|SN7PR12MB8059:EE_ x-ms-office365-filtering-correlation-id: 3841dbfd-427b-46ad-67be-08dc4b2160ef x-ms-exchange-senderadcheck: 1 x-ms-exchange-antispam-relay: 0 x-microsoft-antispam: BCL:0; x-microsoft-antispam-message-info: iu/Rm2vc7Aw9cwWlyDH8TCiLGc2AB91Xp6pmqXGJYn80bOVtBaLNVXvLQYBnS8gvL38gZcUtg3Xk/9ZXdgICEuH973g4aZdW1YQKuEqPVmOfYDmrP1XXiOPrblaKvUIdTw1OoCbeu1d3e+nxSoaHr/0vddNuxgfLh0zIDs58fHpGGrgc7Vfkj7hJkyo+GyBPVbMjq8gcBICnIV+uuNJSalwExlG0n1ibd807mtt1cMXIBtBfNiGMNfYlP2sSiQ4cLf7HSF3OmrCZaBcZ5Pz0KPlp+zcHX6wUEY2Go+SFcziZu+ssaO6DEucJ0G+eEzAVHW8PyL3mMIQwEholzr9jNE/8BNvZ70aKy2TPAkl72FZ+8TU26c5gvLAbcQ7k5M1WprdIXWhB5GKPi4MBnZGS9MPvXnOvVeczd+ngxX0u1iXiB+iz5kENS+avdReRoJYXiOC8TnQqnI8BR9dnPOqTHONgu06nWCBaNyaweT0EujQq0Dy8atz1feol196cTRUXTaZsmmHYtPYvLlYE7ry/sEf4LJNsdVANxKE4ngPYmzf2oaYl4yC46pT+DjxIZdo6pvwRi3NlvykpJ0s38PSX+Cna4ZPWIaWrQsZdlLNb4yBmCSIl+1w/dRBma7hh3KR5uyVFUkb+iCQ8CHCH9n3/kBOZPxjIAMvDzPDuloOC3p1Reg/YC8jxosM8verF0fsGSeTeY0YMCoNVsNcbPO4XIoern1kKjErOZWGafw+5OaI= x-forefront-antispam-report: CIP:255.255.255.255; CTRY:; LANG:en; SCL:1; SRV:; IPV:NLI; SFV:NSPM; H:DS7PR12MB6336.namprd12.prod.outlook.com; PTR:; CAT:NONE; SFS:(13230031)(376005)(1800799015)(366007)(38070700009); DIR:OUT; SFP:1101; x-ms-exchange-antispam-messagedata-chunkcount: 1 x-ms-exchange-antispam-messagedata-0: =?iso-8859-1?Q?AJbhEE281IxNptvt27GiD1qrgAnzis1QUL+oM675A7YD5OWyc1ixnuDKyw?= =?iso-8859-1?Q?miyXseA95E6TEftpZQprniv4NA5QvtkB1IMjFePzg8eiI5Hvf25Trs/kNh?= =?iso-8859-1?Q?kE8ErT/MocBhnpiEHMfd1/wi5mx9hTLUW1LuPeOeDAgVSgJdmyW/lp4J4a?= =?iso-8859-1?Q?IzzNdnAerzec8twUP7E2g28A2OYxEXRZQWMkwam17F73pA+27CI/pB9qcC?= =?iso-8859-1?Q?s+PJ7byc8vYvCILPLkB3J4j2v1n/oEL4lZJJuU+6mfLxPBMW4fqwamfNc6?= =?iso-8859-1?Q?P+T2jRmQs3hWncnRI0kkGDJejR/UGHSDaxaTVY7S95qv0egWP0utN3xXxt?= =?iso-8859-1?Q?/otNTBJxgu0eHBN4wlOPRppjpFhk8871NbGQ34zWXWb15Wu7yAaN7IG5jR?= =?iso-8859-1?Q?khYsKrq2ZSX7jK63k2+drq3OYRhFYhELL/nv5KF69oL5laO7RNgWWn+ZSl?= =?iso-8859-1?Q?PRjoxzwgvXesXa3A7AqWx3gEeGs7pBOjY0l/uahFwgvEHWjQXm0rOovQYe?= =?iso-8859-1?Q?kTZuSNSv5OfglExrf+fXam203jNhMhXxqGzBQMZ6kPTOJviC+aY1dkayz7?= =?iso-8859-1?Q?wJzqOCxn63eouLoXvMl0tkQNJMHECX3IoOd68ZNuZXp0TJumIhFf/lr6TC?= =?iso-8859-1?Q?hV8cMuAYSr+EMqIJ8+a5ug2Q3roKF5//9d+jF5MuqraQw/HccgCE44KFxW?= =?iso-8859-1?Q?WPRG3prYBsR3IEZqA2UU0brwowCG94zvrUQXIf16qWSjdX6ziBDcy2/UI6?= =?iso-8859-1?Q?pUw95MzyJjDeXlKC9QLDGu4RRhG26ky8rRATnkrgf0CCOUMfsCaJ18kCcq?= =?iso-8859-1?Q?j12WrtkpYnbqb8DsRTfMSq7N4RkJQN7ejIGR1cGWp3ShR+Cdpwa41x7TGr?= =?iso-8859-1?Q?hh6h1hkUlWTWd0wsTrWpOC63IHLwJy7N5ir8ZKWDYk8veooWx9IYUYwBk6?= =?iso-8859-1?Q?VUZyBMUQAukzfg/0vpU43kJIey3fCwJ0tua2NawEwqHgFLV1Vh2HqINNIC?= =?iso-8859-1?Q?yKNxn2sYcV3VbvPrkot1SuqBuY3dSG6Ea3i5DSIcEh0MuKybN8P935CpCR?= =?iso-8859-1?Q?DE+3VdQP+eKOw1JkydFvfsmngUErZcLT6YjWZwBlUA9dTnJEdMN/NLwrJu?= =?iso-8859-1?Q?Nm7w+5hGQIoxokSdIGkz4CbcJcefe19+En7Evzhtt4CXxSw+6g5uEJ2ddz?= =?iso-8859-1?Q?229i66NufL0UkYJt/vguzyxtKzIkVaBkeWTt3szFB5t+hSQYwshyxNTZYv?= =?iso-8859-1?Q?pIJ8DzseNjkoy3e5hxeqrdmgd1uCzDlDSRXmsqTG/13WBb+oAMo6qzxTp0?= =?iso-8859-1?Q?VPyDjrJlI3DZ4vSh8jBdfzdOE2i5Kpoz2sg6HNQbdHFmXbLqQYy0YLak8v?= =?iso-8859-1?Q?9RtXkq/QJaHFNInlpygq8twvrqkimCEN7s1ZlIGuWjcuptQKpFiWPiFbNK?= =?iso-8859-1?Q?Jic7E6v4/U85Ag8OTKhxjQAjyVgSNIzyqCTq72HuPShfOAlLE4BgGGK+tm?= =?iso-8859-1?Q?szThaQ51tbZ9rE+FjM3vOcmHTU61g4slF9j1Ig2xb6neMt9gCPFR+7g1b5?= =?iso-8859-1?Q?OEaJFSc8iHlBBsgzXilLr3HP3mIWAYzJzII6F+SI+qNlJBARyyq1LPIhVL?= =?iso-8859-1?Q?THp9CbU4Vzn7domsYpJ3h1kJz75Hnkl8uY?= Content-Type: multipart/alternative; boundary="_000_DS7PR12MB6336564F8003AA4296B4AACAA5302DS7PR12MB6336namp_" MIME-Version: 1.0 X-OriginatorOrg: Nvidia.com X-MS-Exchange-CrossTenant-AuthAs: Internal X-MS-Exchange-CrossTenant-AuthSource: DS7PR12MB6336.namprd12.prod.outlook.com X-MS-Exchange-CrossTenant-Network-Message-Id: 3841dbfd-427b-46ad-67be-08dc4b2160ef X-MS-Exchange-CrossTenant-originalarrivaltime: 23 Mar 2024 10:09:51.1982 (UTC) X-MS-Exchange-CrossTenant-fromentityheader: Hosted X-MS-Exchange-CrossTenant-id: 43083d15-7273-40c1-b7db-39efd9ccc17a X-MS-Exchange-CrossTenant-mailboxtype: HOSTED X-MS-Exchange-CrossTenant-userprincipalname: 3Ny9N7NHPDzfsCpaJo5WTeGaNwPl8fTrpp6ewpiwJV8ZhhK+C8dY3a1qa5SX2i8ZTfboCDOMFMD44N7Ks9OO4Q== X-MS-Exchange-Transport-CrossTenantHeadersStamped: SN7PR12MB8059 X-BeenThere: ci@dpdk.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: DPDK CI discussions List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: ci-bounces@dpdk.org --_000_DS7PR12MB6336564F8003AA4296B4AACAA5302DS7PR12MB6336namp_ Content-Type: text/plain; charset="iso-8859-1" Content-Transfer-Encoding: quoted-printable Hello Ferruh, > But I guess it won't catch this issue, as it uses full flow commands. > This issue is related to the testpmd command parsing code. I wonder if > we can find a way to verify testpmd parsing code? What if flow command validation program breaks a tested command into tokens= and then pass each token followed by the TAB key to the controlled testpmd= process ? There are 3 possible outputs for the token + TAB sequence: 1. a valid token will trigger meaningful testpmd help ouput that can be = matched. 2. invalid token output will fail [1]. 3. invalid token will crash the controlled testpmd process. I've compiled a small programm to test that idea. Please check it out. Regards, Gregory. commit e4a27c2c2892cfd408b473b18192b30927e4281c Author: Gregory Etelson Date: Sat Mar 23 11:38:44 2024 +0200 validate testpmd flow command diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..3498868 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name =3D "testpmd-validate" +version =3D "0.1.0" +edition =3D "2021" +rust-version =3D "1.76.0" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/r= eference/manifest.html + +[dependencies] +ssh2 =3D { version =3D "0.9.4", features =3D ["vendored-openssl"] } +time =3D "0.3.34" +regex =3D "1.10.4" diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..90dfdac --- /dev/null +++ b/src/main.rs @@ -0,0 +1,128 @@ +use std::io::{Read, Write}; +use std::{io, thread, time}; +use std::path::Path; +use std::ops::Add; +use std::time::{SystemTime}; +use ssh2::{Channel, Error, ExtendedData, Session}; +use regex::{RegexSet}; + +fn continue_after_error(err:&io::Error) -> bool { + match err.kind() { + io::ErrorKind::WouldBlock =3D> return true, + _ =3D> return false, + } +} + +fn wrap_ssh(mut func:F) -> T + where F: FnMut() -> Result { + loop { + match func() { + Ok(val) =3D> { return val }, + Err(ssh_err) =3D> { + let io_err =3D io::Error::from(ssh_err); + if !continue_after_error(&io_err) { + panic!("ssh2 error: {:?}", io_err); + } + } + } + } +} + +fn rsh_write(channel:&mut Channel, buf:&str) { + match channel.write(buf.as_bytes()) { + Ok(_) =3D> (), + Err(e) =3D> panic!("write failure: {:?}", e) + } +} + +const RSH_READ_MAX_DURATION:time::Duration =3D time::Duration::from_millis= (350); +const RSH_READ_IDLE_TIMEOUT:time::Duration =3D time::Duration::from_millis= (10); +fn rsh_read(channel:&mut Channel) -> String { + let mut end =3D SystemTime::now().add(RSH_READ_MAX_DURATION); + let mut output =3D String::new(); + loop { + let mut buffer: [u8; 1024] =3D [0; 1024]; + match channel.read(&mut buffer) { + Ok(0) =3D> { return output } + Ok(_) =3D> { + let aux =3D String::from_utf8(buffer.into()).unwrap(); + // println!("{aux}"); + output.push_str(&aux); + end =3D SystemTime::now().add(RSH_READ_MAX_DURATION); + } + Err(err) =3D> { + if !continue_after_error(&err) { panic!("read error {:?}",= err) } + if SystemTime::now().gt(&end) { break; } + thread::sleep(RSH_READ_IDLE_TIMEOUT); + } + } + } + output +} + +const TESTPMD_PATH:&str =3D "/tmp/build/app"; +const TESTPMD_CMD:&str =3D "dpdk-testpmd -a 0000:01:00.0 -- -i"; + +const TOKEN_PATTERN:[&str; 2] =3D [ + r#"(?m)\[.*\]:"#, + r#"(?m)\[RETURN]"#, +]; + +const CTRL_C:[u8;1] =3D [0x3]; + + +fn rsh_connect(hostname:&str) -> Channel { + let stream =3D std::net::TcpStream::connect(format!("{}:22", hostname)= ).unwrap(); + stream.set_nonblocking(true).unwrap(); + let mut session =3D Session::new().unwrap(); + let private_key =3D Path::new("/home/garik/.ssh/id_rsa"); + session.set_blocking(false); + session.set_tcp_stream(stream); + wrap_ssh(|| session.handshake()); + wrap_ssh(|| session.userauth_pubkey_file("root", None, &private_key, N= one)); + let mut ch =3D wrap_ssh(|| session.channel_session()); + wrap_ssh(|| ch.handle_extended_data(ExtendedData::Merge)); + wrap_ssh(|| ch.request_pty("vt100", None, None)); + ch +} + +fn validate_flow_command(command:&str, ch:&mut Channel) { + let rset =3D RegexSet::new(TOKEN_PATTERN).unwrap(); + let delay =3D time::Duration::from_millis(300); + + println!("validate: {command}"); + for token in command.split_whitespace() { + let candid =3D format!("{token} \t"); + rsh_write(ch, &candid); + print!("validate token \'{token}\' "); + thread::sleep(delay); + let output =3D rsh_read(ch); + if rset.is_match(&output) { + println!(" ok"); + } else { + println!("failed"); + println!(">>>>>>>>>>>>>>>>>>"); + println!("{output}"); + println!("<<<<<<<<<<<<<<<<<<"); + break; + } + } + let _ =3D ch.write(&CTRL_C); +} + +fn main() { + let mut ch =3D rsh_connect("10.237.157.85"); + let command =3D format!("{}/{}", TESTPMD_PATH, TESTPMD_CMD); + wrap_ssh(|| ch.exec(&command)); + thread::sleep(time::Duration::from_secs(1)); + println!("{}", rsh_read(&mut ch)); + + let good_flow =3D "flow create 0 ingress group 0 priority 0 pattern et= h / end actions drop / end"; + let bad_flow =3D "flow create 0 ingress group 0 priority 0 pattern eth= / end actions drop / end"; + + validate_flow_command(good_flow, &mut ch); + validate_flow_command(bad_flow, &mut ch); + + wrap_ssh(|| ch.close()); + println!("EOF {:?}", ch.exit_status()); +} --_000_DS7PR12MB6336564F8003AA4296B4AACAA5302DS7PR12MB6336namp_ Content-Type: text/html; charset="iso-8859-1" Content-Transfer-Encoding: quoted-printable
Hello Ferruh,

> But I guess it won't catch this issue, as it uses full flow comma= nds.
> This issue is related to the testpmd command parsing code. I wond= er if
> we can find a way to verify testpmd parsing code?

What if flow command validation program breaks a tested command into tokens= and then pass each token followed by the TAB key to the controlled testpmd= process ?
There are 3 possible outputs for the token + TAB sequence:
  1. a v= alid token will trigger meaningful testpmd help ouput that can be matched.<= br>
  2. inv= alid token output will fail [1].
  3. invalid token will crash the controlled testpmd process.

I've compiled a small programm to test that idea.
Please check it out.

Regards,
Gregory.

commit e4a27c2c2892cfd408b473b18192b30927e4281c
Author: Gregory Etelson <getelson@nvidia.com>
Date:   Sat Mar 23 11:38:44 2024 +0200

   validate testpmd flow command

diff --git a/Cargo.toml b/Cargo.toml
new file mode 100644
index 0000000..3498868
--- /dev/null
+++ b/Cargo.toml
@@ -0,0 +1,12 @@
+[package]
+name =3D "testpmd-validate"
+version =3D "0.1.0"
+edition =3D "2021"
+rust-version =3D "1.76.0"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/r= eference/manifest.html
+
+[dependencies]
+ssh2 =3D { version =3D "0.9.4", features =3D ["vendored-ope= nssl"] }
+time =3D "0.3.34"
+regex =3D "1.10.4"
diff --git a/src/main.rs b/src/main.rs
new file mode 100644
index 0000000..90dfdac
--- /dev/null
+++ b/src/main.rs
@@ -0,0 +1,128 @@
+use std::io::{Read, Write};
+use std::{io, thread, time};
+use std::path::Path;
+use std::ops::Add;
+use std::time::{SystemTime};
+use ssh2::{Channel, Error, ExtendedData, Session};
+use regex::{RegexSet};
+
+fn continue_after_error(err:&io::Error) -> bool {
+    match err.kind() {
+        io::ErrorKind::WouldBlock =3D> return true,=
+        _ =3D> return false,
+    }
+}
+
+fn wrap_ssh<F, T>(mut func:F) -> T
+    where F: FnMut() -> Result<T, Error> {
+    loop {
+        match func() {
+            Ok(val) =3D> { return val },<= /div>
+            Err(ssh_err) =3D> {
+                let io_err =3D io:= :Error::from(ssh_err);
+                if !continue_after= _error(&io_err) {
+                    pani= c!("ssh2 error: {:?}", io_err);
+                }
+            }
+        }
+    }
+}
+
+fn rsh_write(channel:&mut Channel, buf:&str) {
+    match channel.write(buf.as_bytes()) {
+        Ok(_) =3D> (),
+        Err(e) =3D> panic!("write failure: {:?= }", e)
+    }
+}
+
+const RSH_READ_MAX_DURATION:time::Duration =3D time::Duration::from_millis= (350);
+const RSH_READ_IDLE_TIMEOUT:time::Duration =3D time::Duration::from_millis= (10);
+fn rsh_read(channel:&mut Channel) -> String  {
+    let mut end =3D SystemTime::now().add(RSH_READ_MAX_DURATION)= ;
+    let mut output =3D String::new();
+    loop {
+        let mut buffer: [u8; 1024] =3D [0; 1024];
+        match channel.read(&mut buffer) {
+            Ok(0)  =3D> { return out= put }
+            Ok(_) =3D> {
+                let aux =3D String= ::from_utf8(buffer.into()).unwrap();
+                // println!("= {aux}");
+                output.push_str(&a= mp;aux);
+                end =3D SystemTime= ::now().add(RSH_READ_MAX_DURATION);
+            }
+            Err(err) =3D> {
+                if !continue_after= _error(&err) { panic!("read error {:?}", err) }
+                if SystemTime::now= ().gt(&end) { break; }
+                thread::sleep(RSH_= READ_IDLE_TIMEOUT);
+            }
+        }
+    }
+    output
+}
+
+const TESTPMD_PATH:&str =3D "/tmp/build/app";
+const TESTPMD_CMD:&str =3D "dpdk-testpmd -a 0000:01:00.0 -- -i&qu= ot;;
+
+const TOKEN_PATTERN:[&str; 2] =3D [
+    r#"(?m)\[.*\]:"#,
+    r#"(?m)\[RETURN]"#,
+];
+
+const CTRL_C:[u8;1] =3D [0x3];
+
+
+fn rsh_connect(hostname:&str) -> Channel {
+    let stream =3D std::net::TcpStream::connect(format!("{}= :22", hostname)).unwrap();
+    stream.set_nonblocking(true).unwrap();
+    let mut session =3D Session::new().unwrap();
+    let private_key =3D Path::new("/home/garik/.ssh/id_rsa&= quot;);
+    session.set_blocking(false);
+    session.set_tcp_stream(stream);
+    wrap_ssh(|| session.handshake());
+    wrap_ssh(|| session.userauth_pubkey_file("root", N= one, &private_key, None));
+    let mut ch =3D wrap_ssh(|| session.channel_session());
+    wrap_ssh(|| ch.handle_extended_data(ExtendedData::Merge));
+    wrap_ssh(|| ch.request_pty("vt100", None, None));<= /div>
+    ch
+}
+
+fn validate_flow_command(command:&str, ch:&mut Channel) {
+    let rset =3D RegexSet::new(TOKEN_PATTERN).unwrap();
+    let delay =3D time::Duration::from_millis(300);
+
+    println!("validate: {command}");
+    for token in command.split_whitespace() {
+        let candid =3D format!("{token} \t")= ;
+        rsh_write(ch, &candid);
+        print!("validate token \'{token}\' "= );
+        thread::sleep(delay);
+        let output =3D rsh_read(ch);
+        if rset.is_match(&output) {
+            println!(" ok");
+        } else {
+            println!("failed");
+            println!(">>>>&= gt;>>>>>>>>>>>>>");
+            println!("{output}");<= /div>
+            println!("<<<<&= lt;<<<<<<<<<<<<<");
+            break;
+        }
+    }
+    let _ =3D ch.write(&CTRL_C);
+}
+
+fn main() {
+    let mut ch =3D rsh_connect("10.237.157.85");
+    let command =3D format!("{}/{}", TESTPMD_PATH, TES= TPMD_CMD);
+    wrap_ssh(|| ch.exec(&command));
+    thread::sleep(time::Duration::from_secs(1));
+    println!("{}", rsh_read(&mut ch));
+
+    let good_flow =3D "flow create 0 ingress group 0 priori= ty 0 pattern eth / end actions drop / end";
+    let bad_flow =3D "flow create 0 ingress group 0 priorit= y 0 pattern eth/ end actions drop / end";
+
+    validate_flow_command(good_flow, &mut ch);
+    validate_flow_command(bad_flow, &mut ch);
+
+    wrap_ssh(|| ch.close());
+    println!("EOF {:?}", ch.exit_status());
+}



--_000_DS7PR12MB6336564F8003AA4296B4AACAA5302DS7PR12MB6336namp_--