08 Oct, 2011, Deimos wrote in the 1st comment:
Votes: 0
First let me disclaim that I'm not looking for help on how to implement the actual protocol itself. That's between me and the RFCs. Rather, I'd like some opinions on where it belongs in the IO process. In other words, should it be a high-level stream filter like ANSI or anything else, or should it be at the socket level? Or somewhere else altogether? What are the benefits or pitfalls of your particular strategy? Do things that need to keep state even qualify as a filter?
08 Oct, 2011, Scandum wrote in the 2nd comment:
Votes: 0
It should be at the socket level. The biggest pitfall is probably forgetting to handle packet fragmentation.
08 Oct, 2011, Deimos wrote in the 3rd comment:
Votes: 0
@Scandum: Any particular reasoning behind that? Or was packet fragmentation the reason? I imagine that this issue would affect any protocol (MXP , MSDP, etc.), wouldnt it? I certainly don't want to implement them all at the socket level because of that.
08 Oct, 2011, Scandum wrote in the 4th comment:
Votes: 0
In theory packet fragmentation affects all protocols.

All you're really doing is splitting the telnet and input data, next handle each separately, though the typical telnet implementation goes ahead and deals with the telnet data right away.

One option is to add a pre-parser which is only concerned with detecting terminated telnet sequences, and use a separate routine to deal with the various protocols.

Another pitfall worth mentioning is that some telnet options contain NUL bytes, so you can't treat it as string data.
08 Oct, 2011, Twisol wrote in the 5th comment:
Votes: 0
Telnet is a layer directly above the socket. You may need to fit things like compression (MCCP) and encryption (should a MUD ever use that) in beneath it, but it definitely belongs very close to the socket since it demultiplexes the Telnet stream. At higher levels, like ANSI and MXP and whatnot, you only want to process the data received over the primary channel (meaning not subnegations).

Packet fragmentation is easily provided for by using a state machine. You just pass a chunk to your state machine, it processes it as best it can, and if it stops in the middle of a sequence (right after an IAC, say) it just waits for more data. My telnet library, Anachronism, already handles all of this.

Out of curiosity, why are you writing your own? Every codebase seems to use its own Telnet parser, and more often than not, they're completely broken (sorry). I don't understand the duplication of effort - is there any reason you can't use Elanthis' libtelnet or my Anachronism?

Scandum said:
Another pitfall worth mentioning is that some telnet options contain NUL bytes, so you can't treat it as string data.


Only really applies if you're using a language that fundamentally uses C-strings. And if you're using C…
struct netstring {
size_t length;
char* data;
};

Use it, people. Simplest thing on earth. :sad:


EDIT: Just want to mention, I'm not sure "at the socket level" is a meaningful phrase. Once you get your data from the socket, the socket is out of the picture, right? From then on, it's just an exercise in dataflow. You need a pipeline of layers or filters that take a data stream as input and emit one or more data streams as output.
08 Oct, 2011, Runter wrote in the 6th comment:
Votes: 0
long live C strings
08 Oct, 2011, Deimos wrote in the 7th comment:
Votes: 0
@Twisol: Like most developers, I find it easier to write things myself, because inevitably I'll want to edit something down the line and the time/effort saved in using an existing project initially is more than offset later when I have to learn the inner workings of that project because it doesn't do what I want it to (or how I want it to).

I'd like a system designed around modularity, and I'm currently using streams for socket IO, upon which I'd like to apply the various filters. The problem I'm running into is that protocols aren't really filters, or seem to require unfilterlike behavior, anyway. Trying to figure out where that state should be kept, where the logic for it should be run, etc. is a hurdle I'm trying to get over. For example, should a filter be allowed to send output based on incoming input? This seems kludgy to me. I'm used to the concept of a stream filter where the only thing they can do is modify the data they're given on its way through; not send data back the other way. If they're allowed to do that, then now they need access to higher level state (ex. whether the user wants echo on). That leads to code where objects need references to the objects that composition them and it leads to ugly, tightly coupled code, IMO.
08 Oct, 2011, Twisol wrote in the 8th comment:
Votes: 0
(edit: What language are you using, anyways? I have Node.js bindings for Anachronism, and slightly outdated Ruby bindings. It's really not hard to write a binding to, so if you're using Python or something don't let that discourage you…)

Deimos said:
@Twisol: Like most developers, I find it easier to write things myself, because inevitably I'll want to edit something down the line and the time/effort saved in using an existing project initially is more than offset later when I have to learn the inner workings of that project because it doesn't do what I want it to (or how I want it to).

I think that's an excellent reason for building your own if existing solutions don't suit your needs. What I see oftentimes is that people don't evaluate the existing solutions, especially when it comes to Telnet.

Deimos said:
I'd like a system designed around modularity, and I'm currently using streams for socket IO, upon which I'd like to apply the various filters. The problem I'm running into is that protocols aren't really filters, or seem to require unfilterlike behavior, anyway. Trying to figure out where that state should be kept, where the logic for it should be run, etc. is a hurdle I'm trying to get over. For example, should a filter be allowed to send output based on incoming input? This seems kludgy to me. I'm used to the concept of a stream filter where the only thing they can do is modify the data they're given on its way through; not send data back the other way. If they're allowed to do that, then now they need access to higher level state (ex. whether the user wants echo on). That leads to code where objects need references to the objects that composition them and it leads to ugly, tightly coupled code, IMO.

I think you'd like Anachronism if you gave it a chance. I built Anachronism as a layer decoupled from anything above it or below it. Using the native C API, you create a telnet object with a set of callbacks for primary events (main incoming data, commands, and outgoing data) and telopt events (toggle, focus, and data), and a hook for negotiation queries (WILLs and DOs). You just pipe incoming data to telnet_receive(), and it emits events as needed.

You have full control over what happens with these events from the callbacks; Anachronism's job only real job is to transform the Telnet stream into logical events. It does manage telopts for you, and if the remote end wants to disable an option it automatically sends an affirmative response (because you must comply with a request to disable), but the negotiation hook gives you full control over responding to WILLS and DOs. And naturally you can send your own WILLs, WONTs, DOs, and DONTs through the API.

Anachronism also supports a high degree of modularity, particularly when it comes to telopts. Because the telopt events are separated from the primary stream events, you can either handle all of your telopts in the one callback (bleh) or use that callback just to route to modular implementations of each option. I took advantage of that in my Node.js binding, which I'm using in a side-project right now.

GitHub: https://github.com/Twisol/anachronism

If you're interested or have any questions about Anachronism, drop me a PM or hit me on AIM. If there's something you don't like, I'm not averse to making changes if I think they're worthwhile. The current documentation isn't the best, but I got someone else going quickly and they like it.
09 Oct, 2011, Deimos wrote in the 9th comment:
Votes: 0
@Twisol: I'm using PHP. It's a codebase that I began working on a couple years ago that I'm refactoring from the ground up to include proper telnet support and MXP/MCCP, with MSDP or ATCP possibly added in later as modules. I'll take a look at Anachronism, but I'm not familiar with bindings in PHP, or if it's even possible. I'll tell you though, one thing that already raised my eyebrow is that you say disable requests are auto-accepted and replied to within Anachronism. This is one thing I would want to change. I understand that to be compliant with the RFCs, these requests must be respected, but what if my disable event handler is broken? We would be confirming to the client that an option is disabled without being sure that it actually was disabled. In my opinion, the response should be handled higher up. But, I could br misunderstanding something and admittedly havent reviewed the source yet.
09 Oct, 2011, Twisol wrote in the 10th comment:
Votes: 0
Deimos said:
I'll take a look at Anachronism, but I'm not familiar with bindings in PHP, or if it's even possible.

It is, according to this article.

Deimos said:
I'll tell you though, one thing that already raised my eyebrow is that you say disable requests are auto-accepted and replied to within Anachronism. This is one thing I would want to change. I understand that to be compliant with the RFCs, these requests must be respected, but what if my disable event handler is broken? We would be confirming to the client that an option is disabled without being sure that it actually was disabled. In my opinion, the response should be handled higher up.

I don't understand what you mean. Handling negotiation requests properly is something you'd have to deal with whether you use Anachronism or not. Anachronism still emits a toggle event, which lets you know when an option is enabled or disabled; the only difference is that the negotiation hook isn't consulted to determine the proper response (since the only proper response is to disable). You have full control over what you actually do when the option is disabled.

For the sake of discussion, lets imagine that Anachronism didn't respond automatically. Because it's a negotiation, the DONT or WONT would be sent to the negotiation hook. At this point, you can either respond WILL/DO, which is in violation of the standard and will either be ignored by the remote side or treated as a new request, or respond WONT/DONT, which is well-defined and the expected behavior. If you respond WONT/DONT, you still deal with the toggle event in the telopt event handler same as before. What would occur in your scenario?
09 Oct, 2011, Deimos wrote in the 11th comment:
Votes: 0
@Twisol: It's mostly a separation of concerns issue, I guess. It's not that I would want the ability to respond incorrectly, but rather, I'd want the decision to be made by whatever callback handles the disable event, as that seems the logical place for it to be handled (as it would be for enable events, I assume).
09 Oct, 2011, Twisol wrote in the 12th comment:
Votes: 0
Deimos said:
@Twisol: It's mostly a separation of concerns issue, I guess. It's not that I would want the ability to respond incorrectly, but rather, I'd want the decision to be made by whatever callback handles the disable event, as that seems the logical place for it to be handled (as it would be for enable events, I assume).

The toggle hasn't actually occurred yet when the request is received. When it is actually enabled or disabled, the toggle callback is executed. Separation of concerns is precisely why it was done this way: they're two separate stages of a negotiation. The negotiation hook gets to ask "Do I want this to happen?", and the toggle handler gets to do something when it actually happens.

Imagine if you're not responding to a request, but actually sending a request yourself: telnet_telopt_enable(telnet, 200, TELNET_REMOTE). When you get a response back, the negotiation handler isn't called. Anachronism knows you asked for it to be turned on, so it goes straight to the toggle handler. Because of this separation, the toggle callback doesn't need to care who initiated the request. All it needs to know is that it's been enabled (or disabled).
0.0/12