d3270d now manages to connect and login with commands from netcat
This commit is contained in:
@@ -11,6 +11,7 @@ pub mod operation;
|
||||
pub mod types;
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
|
||||
#[serde(rename_all="kebab-case")]
|
||||
pub enum Indication {
|
||||
Bell {}, // TODO: make sure this emits/parses {"bell": {}}
|
||||
/// Indicates that the host connection has changed state.
|
||||
@@ -61,8 +62,6 @@ pub enum Indication {
|
||||
Setting(Setting),
|
||||
/// I/O statistics
|
||||
Stats(Stats),
|
||||
/// Reports the terminal name sent to the host during TELNET negotiation
|
||||
TerminalName(TerminalName),
|
||||
/// Change in the scrollbar thumb
|
||||
Thumb(Thumb),
|
||||
/// Indicates the name of the trace file
|
||||
@@ -101,6 +100,8 @@ pub enum InitializeIndication {
|
||||
ScreenMode(ScreenMode),
|
||||
/// Setting changed
|
||||
Setting(Setting),
|
||||
/// Reports the terminal name sent to the host during TELNET negotiation
|
||||
TerminalName(TerminalName),
|
||||
/// Scroll thumb position
|
||||
Thumb(Thumb),
|
||||
/// Indicates build-time TLS config
|
||||
|
||||
@@ -61,12 +61,12 @@ pub enum ConnectionState {
|
||||
TelnetPending,
|
||||
ConnectedNvt,
|
||||
ConnectedNvtCharmode,
|
||||
#[serde(rename="connected-3270")]
|
||||
Connected3270,
|
||||
ConnectedUnbound,
|
||||
ConnectedENvt,
|
||||
ConnectedESscp,
|
||||
#[serde(rename = "connected-e-tn3270e")]
|
||||
ConnectedETn3270e,
|
||||
ConnectedSscp,
|
||||
ConnectedTn3270e,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Copy)]
|
||||
@@ -154,7 +154,7 @@ pub enum OiaField {
|
||||
},
|
||||
/// Host command timer (minutes:seconds)
|
||||
Script {
|
||||
value: String,
|
||||
value: bool,
|
||||
},
|
||||
Timing {
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
@@ -200,8 +200,9 @@ impl OiaField {
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
|
||||
pub struct Proxy {
|
||||
pub name: String,
|
||||
pub username: String,
|
||||
pub port: u16,
|
||||
pub username: bool,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub port: Option<u16>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
|
||||
@@ -209,14 +210,15 @@ pub struct Setting {
|
||||
pub name: String,
|
||||
/// I'd love something other than depending on serde_json for this.
|
||||
pub value: Option<serde_json::Value>,
|
||||
pub cause: ActionCause,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub cause: Option<ActionCause>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq, Copy, Clone)]
|
||||
pub struct ScreenMode {
|
||||
pub model: u8,
|
||||
pub rows: u8,
|
||||
pub cols: u8,
|
||||
pub columns: u8,
|
||||
pub color: bool,
|
||||
pub oversize: bool,
|
||||
pub extended: bool,
|
||||
@@ -284,7 +286,7 @@ pub enum FileTransferState {
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
|
||||
#[serde(rename = "kebab-case")]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
pub struct Passthru {
|
||||
pub p_tag: String,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
@@ -304,7 +306,7 @@ pub struct Popup {
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq, Copy, Clone)]
|
||||
#[serde(rename = "kebab-case")]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
pub enum PopupType {
|
||||
/// Error message from a connection attempt
|
||||
ConnectError,
|
||||
@@ -321,14 +323,14 @@ pub enum PopupType {
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
|
||||
#[serde(rename = "kebab-case")]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
pub struct Row {
|
||||
pub row: u8,
|
||||
pub changes: Vec<Change>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
|
||||
#[serde(rename = "kebab-case")]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
pub enum CountOrText {
|
||||
Count(usize),
|
||||
Text(String),
|
||||
@@ -357,7 +359,7 @@ pub struct Screen {
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
|
||||
#[serde(rename = "kebab-case")]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
pub struct RunResult {
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub r_tag: Option<String>,
|
||||
@@ -371,7 +373,7 @@ pub struct RunResult {
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
|
||||
#[serde(rename = "kebab-case")]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
pub struct Scroll {
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub fg: Option<Color>,
|
||||
@@ -380,7 +382,7 @@ pub struct Scroll {
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
|
||||
#[serde(rename = "kebab-case")]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
pub struct Stats {
|
||||
pub bytes_received: usize,
|
||||
pub bytes_sent: usize,
|
||||
@@ -435,12 +437,23 @@ mod test {
|
||||
#[test]
|
||||
pub fn connection_state_serializes_as_expected() {
|
||||
assert_eq!(
|
||||
serde_json::to_string(&ConnectionState::ConnectedETn3270e).unwrap(),
|
||||
r#""connected-e-tn3270e""#
|
||||
serde_json::to_string(&ConnectionState::ConnectedTn3270e).unwrap(),
|
||||
r#""connected-tn3270e""#
|
||||
);
|
||||
assert_eq!(
|
||||
serde_json::to_string(&ConnectionState::ConnectedESscp).unwrap(),
|
||||
r#""connected-e-sscp""#
|
||||
serde_json::to_string(&ConnectionState::ConnectedSscp).unwrap(),
|
||||
r#""connected-sscp""#
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_row() {
|
||||
let instr = r#"[{"row":1,"changes":[{"column":1,"fg":"red","gr":"highlight,selectable","text":"z/OS V1R13 PUT Level 1401"},{"column":26,"fg":"red","gr":"highlight,selectable","count":26},{"column":52,"fg":"red","gr":"highlight,selectable","text":"IP Address = 10.24.74.32 "}]},{"row":2,"changes":[{"column":1,"fg":"red","gr":"highlight,selectable","count":51},{"column":52,"fg":"red","gr":"highlight,selectable","text":"VTAM Terminal = SC0TCP05 "}]},{"row":3,"changes":[{"column":1,"fg":"red","gr":"highlight,selectable","count":80}]},{"row":4,"changes":[{"column":1,"fg":"red","gr":"highlight,selectable","count":23},{"column":24,"fg":"red","gr":"highlight,selectable","text":"Application Developer System"},{"column":52,"fg":"red","gr":"highlight,selectable","count":29}]},{"row":5,"changes":[{"column":1,"fg":"red","gr":"highlight,selectable","count":80}]},{"row":6,"changes":[{"column":1,"fg":"red","gr":"highlight,selectable","count":32},{"column":33,"fg":"red","gr":"highlight,selectable","text":"// OOOOOOO SSSSS"},{"column":52,"fg":"red","gr":"highlight,selectable","count":29}]},{"row":7,"changes":[{"column":1,"fg":"red","gr":"highlight,selectable","count":31},{"column":32,"fg":"red","gr":"highlight,selectable","text":"// OO OO SS"},{"column":47,"fg":"red","gr":"highlight,selectable","count":34}]},{"row":8,"changes":[{"column":1,"fg":"red","gr":"highlight,selectable","count":23},{"column":24,"fg":"red","gr":"highlight,selectable","text":"zzzzzz // OO OO SS"},{"column":46,"fg":"red","gr":"highlight,selectable","count":35}]},{"row":9,"changes":[{"column":1,"fg":"red","gr":"highlight,selectable","count":25},{"column":26,"fg":"red","gr":"highlight,selectable","text":"zz // OO OO SSSS"},{"column":47,"fg":"red","gr":"highlight,selectable","count":34}]},{"row":10,"changes":[{"column":1,"fg":"red","gr":"highlight,selectable","count":23},{"column":24,"fg":"red","gr":"highlight,selectable","text":"zz // OO OO SS"},{"column":49,"fg":"red","gr":"highlight,selectable","count":32}]},{"row":11,"changes":[{"column":1,"fg":"red","gr":"highlight,selectable","count":21},{"column":22,"fg":"red","gr":"highlight,selectable","text":"zz // OO OO SS"},{"column":48,"fg":"red","gr":"highlight,selectable","count":33}]},{"row":12,"changes":[{"column":1,"fg":"red","gr":"highlight,selectable","count":19},{"column":20,"fg":"red","gr":"highlight,selectable","text":"zzzzzz // OOOOOOO SSSS"},{"column":45,"fg":"red","gr":"highlight,selectable","count":36}]},{"row":13,"changes":[{"column":1,"fg":"red","gr":"highlight,selectable","count":80}]},{"row":14,"changes":[{"column":1,"fg":"red","gr":"highlight,selectable","count":80}]},{"row":15,"changes":[{"column":1,"fg":"red","gr":"highlight,selectable","count":19},{"column":20,"fg":"red","gr":"highlight,selectable","text":"System Customization - ADCD.Z113H.*"},{"column":55,"fg":"red","gr":"highlight,selectable","count":26}]},{"row":16,"changes":[{"column":1,"fg":"red","gr":"highlight,selectable","count":80}]},{"row":17,"changes":[{"column":1,"fg":"red","gr":"highlight,selectable","count":80}]},{"row":18,"changes":[{"column":1,"fg":"red","gr":"highlight,selectable","count":80}]},{"row":19,"changes":[{"column":1,"fg":"red","gr":"highlight,selectable","count":80}]},{"row":20,"changes":[{"column":1,"fg":"red","gr":"highlight,selectable","text":" ===> Enter \"LOGON\" followed by the TSO userid. Example \"LOGON IBMUSER\" or "}]},{"row":21,"changes":[{"column":1,"fg":"red","gr":"highlight,selectable","text":" ===> Enter L followed by the APPLID"},{"column":37,"fg":"red","gr":"highlight,selectable","count":44}]},{"row":22,"changes":[{"column":1,"fg":"red","gr":"highlight,selectable","text":" ===> Examples: \"L TSO\", \"L CICSTS41\", \"L CICSTS42\", \"L IMS11\", \"L IMS12\" "}]},{"row":23,"changes":[{"column":1,"fg":"red","gr":"highlight,selectable","count":79},{"column":80,"fg":"green","count":1}]},{"row":24,"changes":[{"column":1,"fg":"green","count":79},{"column":80,"fg":"red","gr":"highlight,selectable","count":1}]}]"#;
|
||||
if let Err(err) = serde_json::from_slice::<Vec<Row>>(instr.as_bytes()) {
|
||||
let pos = err.column();
|
||||
println!("Parse error: {err}");
|
||||
let (pre, post) = instr.split_at(err.column());
|
||||
println!("Context: {pre}\x1b[1;31m{post}\x1b[0m");
|
||||
panic!("{}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
// {"run":{"actions":[{"action":"Connect","args":["10.24.74.37:3270"]}]}}
|
||||
// {"run":{"actions":"Key(a)"}}
|
||||
// {"run":{"actions":[{"action":"Key","args":["a"]}]}}
|
||||
// Operations
|
||||
#[derive(Serialize, Deserialize, Debug, Hash, Eq, PartialEq, Clone)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
|
||||
@@ -97,6 +97,9 @@ impl FromStr for GraphicRendition {
|
||||
type Err = String;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
if s == "default" {
|
||||
return Ok(GraphicRendition::empty());
|
||||
}
|
||||
s.split(",")
|
||||
.map(|attr| {
|
||||
FLAG_NAMES
|
||||
@@ -136,7 +139,7 @@ mod test {
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq, Copy, Clone)]
|
||||
#[serde(rename = "camelCase")]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[repr(u8)]
|
||||
pub enum Color {
|
||||
NeutralBlack,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::b3270::indication::{
|
||||
Change, Connection, ConnectionState, CountOrText, Cursor, Erase, Oia, OiaFieldName, Row,
|
||||
RunResult, Screen, ScreenMode, Scroll, Setting, TerminalName, Thumb, Tls, TraceFile,
|
||||
RunResult, Screen, ScreenMode, Scroll, Setting, Thumb, Tls, TraceFile,
|
||||
};
|
||||
use crate::b3270::types::{Color, GraphicRendition, PackedAttr};
|
||||
use crate::b3270::{Indication, InitializeIndication};
|
||||
@@ -23,7 +23,6 @@ pub struct Tracker {
|
||||
cursor: Cursor,
|
||||
connection: Connection,
|
||||
formatted: bool,
|
||||
terminal_name: Option<TerminalName>,
|
||||
trace_file: Option<String>,
|
||||
tls: Option<Tls>,
|
||||
|
||||
@@ -62,7 +61,7 @@ impl Tracker {
|
||||
self.erase.bg = erase.bg.or(self.erase.bg);
|
||||
|
||||
let rows = self.erase.logical_rows.unwrap_or(self.screen_mode.rows) as usize;
|
||||
let cols = self.erase.logical_cols.unwrap_or(self.screen_mode.cols) as usize;
|
||||
let cols = self.erase.logical_cols.unwrap_or(self.screen_mode.columns) as usize;
|
||||
|
||||
self.screen = vec![
|
||||
vec![
|
||||
@@ -92,6 +91,7 @@ impl Tracker {
|
||||
| InitializeIndication::Models(_)
|
||||
| InitializeIndication::Prefixes { .. }
|
||||
| InitializeIndication::Proxies(_)
|
||||
| InitializeIndication::TerminalName(_)
|
||||
| InitializeIndication::TlsHello(_)
|
||||
| InitializeIndication::Tls(_)
|
||||
| InitializeIndication::TraceFile(_) => static_init.push(indicator),
|
||||
@@ -169,7 +169,7 @@ impl Tracker {
|
||||
self.screen_mode = *mode;
|
||||
self.handle_indication(&mut Indication::Erase(Erase {
|
||||
logical_rows: Some(self.screen_mode.rows),
|
||||
logical_cols: Some(self.screen_mode.cols),
|
||||
logical_cols: Some(self.screen_mode.columns),
|
||||
fg: None,
|
||||
bg: None,
|
||||
}));
|
||||
@@ -187,9 +187,6 @@ impl Tracker {
|
||||
Indication::Setting(setting) => {
|
||||
self.settings.insert(setting.name.clone(), setting.clone());
|
||||
}
|
||||
Indication::TerminalName(term) => {
|
||||
self.terminal_name = Some(term.clone());
|
||||
}
|
||||
Indication::Thumb(thumb) => {
|
||||
self.thumb = thumb.clone();
|
||||
}
|
||||
@@ -239,9 +236,6 @@ impl Tracker {
|
||||
state: self.formatted,
|
||||
},
|
||||
];
|
||||
if let Some(terminal_name) = self.terminal_name.clone() {
|
||||
result.push(Indication::TerminalName(terminal_name));
|
||||
}
|
||||
if let Some(trace_file) = self.trace_file.clone() {
|
||||
result.push(Indication::TraceFile(TraceFile {
|
||||
name: Some(trace_file),
|
||||
@@ -283,7 +277,7 @@ impl Tracker {
|
||||
.map(Self::format_row)
|
||||
.enumerate()
|
||||
.map(|(row_id, changes)| Row {
|
||||
row: row_id as u8 - 1,
|
||||
row: row_id as u8 + 1,
|
||||
changes,
|
||||
})
|
||||
.collect(),
|
||||
@@ -297,7 +291,7 @@ impl Default for Tracker {
|
||||
screen: vec![],
|
||||
oia: Default::default(),
|
||||
screen_mode: ScreenMode {
|
||||
cols: 80,
|
||||
columns: 80,
|
||||
rows: 43,
|
||||
color: true,
|
||||
model: 4,
|
||||
@@ -329,7 +323,6 @@ impl Default for Tracker {
|
||||
cause: None,
|
||||
},
|
||||
formatted: false,
|
||||
terminal_name: None,
|
||||
trace_file: None,
|
||||
tls: None,
|
||||
static_init: vec![],
|
||||
|
||||
Reference in New Issue
Block a user