Giter Site home page Giter Site logo

kazdragon / telnetpp Goto Github PK

View Code? Open in Web Editor NEW
62.0 6.0 10.0 3.31 MB

A C++ library for interacting with Telnet streams

License: MIT License

CMake 5.84% C++ 93.69% Shell 0.27% Dockerfile 0.20%
telnet telnet-protocol rfc-1153 rfc-857 rfc-1950 rfc-1073 rfc-1572 rfc-858 rfc-1091 telnet-session

telnetpp's Introduction

Telnet++

Documentation License GitHub Releases MSVC Build status Linux Build status Coverage Status Codacy Badge Github Issues

Gitter

Telnet++ is an implementation of the Telnet Session Layer protocol that is used primarily to negotiate a feature set between a client and server, the former of which is usually some kind of text-based terminal, Commonly used terminals include Xterm, PuTTY, and a whole host of Telnet-enabled MUD clients including Tintin++, MushClient, and more.

Requirements

Telnet++ requires a C++17 compiler and the following libraries:

  • Boost (At least version 1.69.0)
  • GSL-lite (At least version 1.38)
  • (Optionally) ZLib
  • (For testing only) Google Test

Installation - CMake

Telnet++ can be installed from source using CMake. This requires Boost, GSL-Lite and any other dependencies to have been installed beforehand, using their own instructions, or for the call to cmake --configure to be adjusted appropriately (e.g. -DBOOST_ROOT=... or -Dgsl-lite_DIR=...). If you do not wish to install into a system directory, and thus avoid the use of sudo, you can also pass -DCMAKE_INSTALL_PREFIX=... into the cmake --configure call.

git clone https://github.com/KazDragon/telnetpp.git && cd telnetpp
mkdir build && cd build
cmake --configure -DCMAKE_BUILD_TYPE=Release ..
cmake --build .
sudo cmake --install .

Features / Roadmap / Progress

  1. Basic constants and structures for use with Telnet
  • Constants such as IAC, SB, SE, WILL, WONT, DO, DONT
  • Helper structures for commands, negotiations, and subnegotiations
  • Parser that can convert a stream of bytes into a variant of commands, negotiations, subnegotiations and plain text.
  • Generator that can convert a stream of the above-mentioned variant into a stream of bytes.
  1. A framework for managing Telnet
  • Structures for client and server options
  • A method of routing incoming negotiations and subnegotiations to the relevant options.
  1. Reference implementations of various standard options
  • Arbitrary "subnegotiationless" options
  • Echo
  • NAWS
  • Terminal Type
  • New Environ
  1. Reference implementations of some domain-specific options for MUDs
  1. Structures to hide the complexity of the layer (e.g. routers, parsers, generators).
  • Session class that understands all of the helper structures and how to convert to and from a stream of bytes.

Status

Telnet++ is automatically tested with MSVC 2019 and GNU g++ 7.5.

The Basics

The protocol has three basic elements, all of which are accessed by using the 0xFF character called "Interpret As Command", or IAC.

Commands

Without needing to negotiate any capabilities, Telnet offers some out-of-the-box commands. These include Are You There, which is usually sent by the client to provoke a response from an otherwise-busy server; Erase Line, which could be used in interative applications to cancel a set of input, and several other commands used for negotiations between options.

Commands are represented by the telnetpp::command class.

Negotiations

The Telnet protocol describes a model whereby the client and server maintain separate lists of features, called "options", which can be enabled or disabled by the remote side. Individual options may each be described as "server" or "client" options, and server and client options may be mixed on each side of the connection. It is even possible in some cases that both sides of the connection can be both client and server for the same option. These options are negotiated by using the commands DO, DONT, WILL and WONT.

The various Telnet option specifications are not consistent in what is considered a server and what is considered a client, but for the purposes of this library, the server is considered as the side of the connection that does the thing, and the client is the side of the connection that wants the thing. That is, the server reacts to DO and DONT and sends WILL and WONT, and the client reacts to WILL and WONT and sends DO and DONT.

Negotiations are represented by the telnetpp::negotiation class.

Subnegotiations

After an option has been negotiated, a new channel opens up to be able to communicate in an option-specific way to the remote terminal. These are called subnegotiations, and each protocol defines its own sub-protocol. For example, the NAWS (Negotiate About Window Size) sends five bytes when reporting window size, the first of which represents an "IS" token, and the following four bytes represent two two-byte pairs that are the window extends.

Subnegotiations are represented by the telnetpp::subnegotiation class.

Dataflow: Elements, Tokens and Streams

A telnetpp::element is a variant that may contain a command, a negotiation, a subnegotiation, or just a plain sequence of bytes representing non-Telnet-specific input/output.

Stream-Unaware

The Telnet++ library does not impose any requirement on any kind of data stream API. In order to accomplish this, it makes heavy use of a channel concept. See the telnetpp::session class for an in-depth explanation of how this works.

Options

As alluded to earlier, each distinct feature is represented by either a telnetpp::client_option or a telnetpp::server_option. These both enjoy the same API; they only differ in the underlying protocol. The user needs to know little about which actual negotiations and commands are sent. There are two key functions and one signal for the option classes:

  • activate() - this is used to request activation on the remote side.
  • deactive() - this is used to request deactivation on the remote side.
  • on_state_changed - this is a signal that is called when the option is either being activated, active, being deactivated, or has become inactive.

Session

All of the above can be quite complicated to manage, so Telnet++ provides the telnetpp::session class. This is the key abstraction of the Telnet++ library, and is used to manage an entire Telnet feature set for a connection. This is accomplished by "install"ing handlers for commands and options:

// A user-supplied class that models the channel concept
class channel {
  void write(telnetpp::bytes);
  void async_read(std::function<void (telnetpp::bytes)>);
  bool is_alive() const;
  void close();
}

channel my_channel;

// Create a session object, which manages the inputs and outputs from my channel.
telnetpp::session session{my_channel};

// An echo server (provided with Telnet++) is used to control whether a server responds to input from
// a client by transmitting the same text back to the client.  By default, this does not happen, and
// clients print out locally whatever is typed in.  By activating this option, the client no longer
// locally echos input, and the server is totally in control of what appears on the screen.
telnetpp::options::echo::server echo_server{session};

// The session now knows we want this feature to be handled and does all the heavy lifting for us.
session.install(echo_server);

// By default, options sit there in a deactivated state unless explicitly activated either locally
// in code or in protocol from the remote.  Here, we activate it ourselves.  This uses the session
// to ensure that the protocol bytes are forwarded to the channel.
echo_server.activate();

// Sessions just pass on commands to functions installed on a per-command basis.  Here we pass a
// lambda to handle the Are You There command.
session.install(
    telnetpp::command{telnetpp::ayt},
    [&]
    {
        // We respond here by forwarding plain text as a sequence of bytes via the session.
        using telnetpp::literals;
        auto const message = "Yes, I'm here"_tb;
        
        session.write(message, my_socket_send);
    });

Receiving data is slightly more complex since it is asynchronous and so requires a callback that is called when data is received.

// A user-specified function that transmits data up to the application.
// Note: the session indicates that an async_read is complete by sending
// an empty packet of data.  This can be used to prompt a new async_read,
// for example.
void my_application_receive(telnetpp::bytes data);

session.async_read(
    [&](telnetpp::bytes data)
    {
        my_application_receive(data);
    });

telnetpp's People

Contributors

bitdeli-chef avatar kazdragon avatar mbeutel avatar pktiuk avatar waffle-iron avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

telnetpp's Issues

Create Router Framework

The Router Framework would allow a user to set handlers for the different Telnet token types, and for default handling of unregistered options. This would also include a method for passing a token from a stream of tokens to an appropriate router (e.g. command router, negotiation router).

Create TELNET Parser

Must be capable of transforming a stream of bytes into a stream of commands, negotiations, and subnegotiations.

Document Intent and Usage

In particular:

  • Overall intent of library
  • Datastream-agnosticism
  • How to use the library in other code

Create Terminal Type Client

A TT Client is responsible for querying a server's terminal types. Upon receipt of these types, they should be reported to an upper layer. There should be no more logic than this, since the way in which this data is received may differ from application to application - some may wish to cycle through all types, some may be satisfied with only one type.

Don't Assume Text

At the moment, it is assumed that everything that is transmitted to and from the layer below is a stream of bytes.

Although it must be that a stream of bytes is indeed received and transmitted, other types should be passed through.

Integrate into Paradice

Not a specific task of the library itself, but it's really necessary to have this working in a product before release.

Provide helper for parser

At the moment, parser only knows about vectors. And it should stay that way.

It needs a helper, though, since telnetpp will require a stream of (vector | any), and those anys must be passed through.

Therefore, we need a parser helper that accepts the compound type and returns a vector, where all the vectors have been buffered up and parsed into elements, and all the anys have been passed through.

Note: the anys must be passed through as close to their reception point as necessary. It's expected that these will always occur on element boundaries within the streams, but it's not necessarily true.

Migrate away from CppUnit

It's difficult to use CppUnit with Clang's memory sanitizer. Therefore, it may be of more use to move to a different testing infrastructure.

Add a converter from vector<variant<token, any>> to vector<token>

At the bottom of the protocol stack, it is necessary to output just a straightforward stream of bytes. But, before that takes place, it is necessary to filter out the boost::any tags/objects that may be in the vector of tokens.

This filter should be supplied by the library, rather than having every user of the library re-implement it.

Note: all boost::any objects should have been consumed by this point in time, so these can be treated as errors if they remain present.

Documentation (especially the readme, which states to do this manually) should be updated to reflect this feature.

Create TELNET Generator

Must be capable of transforming a stream of TELNET commands, negotiations, subnegotiations and ordinary text into a stream of bytes.

Move protocol constants into detail namespace

Most protocol headers expose constants to do with the protocol: option numbers, constants, etc. However, users never have to deal with these, so they should be private to the library.

Create Client and Server Option Structures

An option should be able to:

  • register with routers to receive negotiations and subnegotiations that target the option
  • know and pro-actively report on the state of the option.

Consider using container with SBO for byte arrays

std::vector cannot implement any kind of small buffer optimization. This means that every non-0-length vector requires a memory allocation.

Consider replacing usages std::vector with an alternative type that uses SBO. For example:

  • boost::containers::small_buffer<T, S>
  • std::basic_string

Add a convenience header

Add something like telnetpp/telnetpp.hpp which includes all the main headers (e.g. routers, parser, generator, etc.)

Investigate improvements to interface

The vector<token, any> interface is still somewhat clunky, and could do with investigating to see if it can be made cleaner.

Ideally, this would occur after the implementation of the MCCP option, since the clunkiness exists to serve that particular option in the first place.

Create Command, Negotiation, Subnegotiation structures

According to the TELNET Standard, Commands are initiated by bytes from 0xEF (239) to 0xFF (255). For the purposes of this issue, this will exclude the bytes between 0xFA (250) and 0xFE (254), since these represent SB, WILL, WONT, DO and DONT, which are for Subnegotiations and Negotiations.

Allow options to be "activatable"

Activatable options will be dormant by default, but will respond positively to requests to active that originate from the remote end.

Create NAWS Server

The NAWS Server is responsible for maintaining a screen size, sending it upon activation and thence whenever it is set.

Cache GTest build

Now that #95 is complete, it should be possible to cache the GTest build so that it doesn't need to be done every time.

Create Subnegotiationless Client, Server

A subnegotiationless option implements only basic functionality. In general, this is used to enable or disable a feature in another layer of the application (e.g. echo, suppress go-ahead, etc.)

Create NAWS Client

A NAWS Client is responsible for interpreting subnegotiations from a server and reporting any changes in screen size that it sends.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.