Skip to content

Commit

Permalink
Finish basic negotiation
Browse files Browse the repository at this point in the history
  • Loading branch information
michidk committed Dec 28, 2023
1 parent e55f664 commit 73d3378
Show file tree
Hide file tree
Showing 2 changed files with 81 additions and 9 deletions.
82 changes: 74 additions & 8 deletions src/client.zig
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,26 @@ const fs = std.fs;
const os = std.os;
const print = std.debug.print;
const telnet = @import("telnet.zig");
const Command = telnet.Command;
const Option = telnet.Option;

const State = enum {
normal,
iac,
negotiating,
};

const StateInfo = union(State) {
normal: void,
iac: void,
negotiating: telnet.Command,
};

pub const TelnetClient = struct {
stream: net.Stream,
reader: net.Stream.Reader,
writer: net.Stream.Writer,
state: State,
state: StateInfo,

pub fn init(stream: net.Stream) TelnetClient {
return TelnetClient{
Expand All @@ -33,7 +41,7 @@ pub const TelnetClient = struct {
switch (self.state) {
.normal => {
if (byte == telnet.IAC_BYTE) {
print("IAC ", .{});
print("Server ({s}): IAC ", .{@tagName(self.state)});
self.state = .iac;
} else {
print("{c}", .{byte});
Expand Down Expand Up @@ -88,27 +96,85 @@ pub const TelnetClient = struct {
},
.will => {
print("WILL ", .{});
self.state = .negotiating;
self.state = StateInfo{ .negotiating = .will };
},
.wont => {
print("WONT ", .{});
self.state = .negotiating;
self.state = StateInfo{ .negotiating = .wont };
},
.do => {
print("DO ", .{});
self.state = .negotiating;
self.state = StateInfo{ .negotiating = .do };
},
.dont => {
print("DONT ", .{});
self.state = .negotiating;
self.state = StateInfo{ .negotiating = .dont };
},
}
},
.negotiating => {
var opt: telnet.Option = @enumFromInt(byte);
.negotiating => |command| {
const opt: telnet.Option = @enumFromInt(byte);
print("{s}\n", .{@tagName(opt)});

switch (opt) {
.echo => {
switch (command) {
.will => {
try self.send(.do, .echo);
},
.wont => {
try self.send(.dont, .echo);
},
else => {},
}
},
.suppressGoAhead => {
switch (command) {
.will => {
try self.send(.do, .suppressGoAhead);
},
.wont => {
try self.send(.dont, .suppressGoAhead);
},
else => {},
}
},
.negotiateAboutWindowSize => {
// https://datatracker.ietf.org/doc/html/rfc1073
switch (command) {
.do => {
try self.send(.will, .negotiateAboutWindowSize);
try self.writer.writeAll(&[_]u8{ telnet.IAC_BYTE, telnet.SB_BYTE, @intFromEnum(Option.negotiateAboutWindowSize), 0, 80, 0, 24, telnet.IAC_BYTE, telnet.SE_BYTE });
},
.dont => {
try self.send(.wont, .negotiateAboutWindowSize);
},
else => {},
}
},
.terminalType => {
// https://datatracker.ietf.org/doc/html/rfc1091
switch (command) {
.do => {
try self.send(.wont, .terminalType);
try self.writer.writeAll(&[_]u8{ telnet.IAC_BYTE, telnet.SB_BYTE, @intFromEnum(Option.terminalType), 0, 0, 0, 0, 0, 0, 0, 0, 0, telnet.IAC_BYTE, telnet.SE_BYTE });
},
.dont => {
try self.send(.wont, .terminalType);
},
else => {},
}
},
else => {},
}

self.state = .normal;
},
}
}

fn send(self: *TelnetClient, command: Command, option: Option) anyerror!void {
print("Client ({s}): IAC {s} {s}\n", .{ @tagName(self.state), @tagName(command), @tagName(option) });
try self.writer.writeAll(&telnet.instruction(command, option));
}
};
8 changes: 7 additions & 1 deletion src/telnet.zig
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
pub const DEFAULT_PORT: u16 = 23;

pub const IAC_BYTE: u8 = 255;
pub const SB_BYTE: u8 = 250;
pub const SE_BYTE: u8 = 240;

pub const Command = enum(u8) {
se = 240, // End of subnegotiation parameters
Expand All @@ -24,7 +26,7 @@ pub const Option = enum(u8) {
transmitBinary = 0, // Binary Transmission (RFC 856)
echo = 1, // Echo (RFC 857)
reconnection = 2, // Reconnection (NIC 15391 of 1973)
suppressGoAhead = 3, // Suppress Go Ahead (RFC 858)
suppressGoAhead = 3, // Suppress Go Ahead (RFC 858): no "go ahead" signal will be sent (required for half-duplex transmissions) -> full-duplex
approxMessageSizeNegotiation = 4, // Approx Message Size Negotiation (NIC 15393 of 1973)
status = 5, // Status (RFC 859)
timingMark = 6, // Timing Mark (RFC 860)
Expand Down Expand Up @@ -78,3 +80,7 @@ pub const Option = enum(u8) {
unassigned141To254 = 141, // Unassigned (141-254)
extendedOptionsList = 255, // Extended-Options-List (RFC 861)
};

pub fn instruction(command: Command, option: Option) [3]u8 {
return [3]u8{ IAC_BYTE, @intFromEnum(command), @intFromEnum(option) };
}

0 comments on commit 73d3378

Please sign in to comment.