Wednesday, October 13, 2021

Learning Typescript, and why I'm not a fan

A work project I'm helping out with uses Typescript, I tried to use it for my extension, and now I just use JavaScript. It's all because TypeScript documentation is bad, the module system is silly, their conversion times are slow, and their target user is 100% not me.

I'm a little bit of a computer language enthusiast, and have been for years. My first intern project was to make a YACC grammar for a Fortran "wirelist" program for Teradyne (hi, Chuck!); I designed and built a technically-oriented terminal-based hypertext system for electric engineering; I created an incredibly simple search language for a game company (technical requirement: must be functional in less than one day, because otherwise we'd have to use my boss's approach, and he was wrong). 

I was enthused by having a reason to jump into Typescript for this project. I 100% love the concept of typescript: it's like JavaScript, but adds in types, so you make fewer mistakes. Who wouldn't like that? I'm not a fan of being all loosey-goosey with naming, and appreciate the little boost that Typescript add. The generated JavaScript code matches well with the original, making debugging easier.

And then in all went wrong. After a successful start, within a day I stopped working on the Typescript source and instead just edited the JavaScript file. 

The compile speeds take me out of the flow. My file is just a few hundred lines long; in JavaScript I can just reload. With TypeScript, you have an awkward pause. The pause is for no technical reason; my files are small, a reasonable program would be able to read it, parse it, and convert it in under a second. (my own current language project is a language converter; my own goal is <1second for a 1K line file)

The module documentation is much to terse. Specifically, if you already know how modules work, and know what you want, then you can understand the module documentation. Otherwise, it fails to provide basic information about what the settings do, and when to use them.

Modules simply emit errors. The goal of Typescript is that it generates working JavaScript. There are two settings for modules: ones that generate non-working JavaScript (the browser sees an import statement and complains that it doesn't know what requires means), and ones that spit out long lists of compiler errors about not finding some package that I'm not asking for (some configuration language).

If your customers are highly motived people then you can get away with badly documented features that generate errors. I'm not that highly motivated, and have an alternative.

Why do I even need modules? Typescript requires modules for two reasons: 

The -watch command that's needed to make compile times acceptable only work with the -build switch and that in turn only works with modules. It would have been nice if I would have just typed tsc file.ts --watch and be done with it. 

As soon as you have two files, you have to have modules. Otherwise, nothing works.

The language documentation is a barrier to understanding. The documentation for Typescript hardly presents an easy onboarding experience. There's pretty much nothing that I found that presents a high-level work flow, or explains their design choices. 

Mathematicians are the bane of computer documentation. I firmly believe that there's a mathematicians brain that some people have, such that they read in equations and very short, very succinct descriptions, and from that generate an entire field. It's actually an awesome ability, and it makes them write completely useless documentation for the rest of us. (Note: I have a degree in mathematics).

Typescript is full of the mathematicians approach: provide a tiny number of words, with no worked-out example, and starting from first principles (which no beginner know) instead of from what starting people need to read.

I wanted typescript to be a powerful new tool in my toolbox for designing programs. Instead, after multiple fruitless hours of trying to make Typescript work within my work-flow, I simply gave up and embraced JavaScript. And it makes me sad

Monday, May 31, 2021

Filtering out distant Bluetooth signals

 TL/DR: nearby Bluetooth devices have a RawSignalStrengthInDbm in the 50s and 60s.

I love playing with Bluetooth devices and writing little apps to control them (including the very special Gopher of Things). One of the hassles with developing, though, is that we're in a sea of Bluetooth devices. Any "watcher" code you write will be inundated with events from everyone else's device (notably their Apple devices which helpfully send lots of Bluetooth advertisements)

So how to filter them out? Step 1 is to look at the RawSignalStrengthInDbm in your Bluetooth watcher's BluetoothLEAdvertisementReceivedEventArgs argument. I did a little experiment: all of the devices I was interested in coding for had a signal strength in the 50's and 60's. Everything in the 80's and higher was noise from the rest of the house.

Note, though, that the strength is in decibels. A strong signal is -50 and a weak signal is 89. To quickly return when the signal strength is too low, do this:

    const int filterLevel = -75;
    if (args.RawSignalStrengthInDBm < filterLevel)
    {
         return;
    }


In my test, this filters out most of the undesired signals.


Wednesday, February 24, 2021

Everything wrong with the FINGER protocol

 Everything wrong with the FINGER protocol 

For those of you who have never heard of it, Finger is one of the old "litle"¹ TCP services. As a user of a big multi-user machine, you can edit the ".plan" file in your directory; people can then run a command like finger person@example.com and it will retrieve your .plan file along with other information like where and when you last logged in. It was a super useful way to coordinate with teammates back in the days before cell phones had been created. 

 The protocol itself is pretty simple: the finger command sends a single line of data with the user name, and the server replies with a bunch of text and then closes the connection. So what could go wrong? In this minor screed, I list both things that should have been known at the time, and also things that we know about protocols today that weren’t known then. 

TL/DR: the spec is wrong, confusing, incorrectly implemented and potentially dangerous. But other than that, it works pretty well :-) 

The protocol spec is incorrect (/W). 

 Firstly, the finger spec, RFC  1288, is wrong. The "BNF" query notation, section 2.3, with query type #1, attempts to allow an optional /W before the user. The /W is the verbose switch (W stands for "whois") and servers can reply with more information when it's provided. (This is accessed by the finger -l person@example.com switch; -l stands for long). But that's not what the BNF actually says. What the BNF says is that the /W switch is required whenever a username is provided. What should be an optional switch into a mandatory one. 

Good news! Every actual finger client implements what the spec tried to say and not what it failed to say. Which is good, because a number of existing (as of February 2021) Finger servers implement the earlier RFC 742, which doesn’t allow the /W switch. 

The protocol BNF is clumsy. 

The protocol “BNF” in general is more formalistic than useful. There’s an old saying that every level of indirection makes code harder to follow; the corresponding saying for BNF is that simple and common definitions like CRLF should be spelled out each time they are used, not hidden behind a layer of naming indirection. The BNF also loves using short name; {C} is the name of the rule that eventually expands to CRLF, and {U} the rule for user names. 

Additionally, the BNF is split into two rules: one for direct user lookup, and one for an indirect network lookup (these are Q1 and Q2 in the BNF). But this makes the Q1 clumsy, as it has to handle both user lookups with no user, and user lookups with a user. A better split would be three query types: a NULL query (with or without a /W), a user query (also with or without a /W) and a network query. 

On-behalf-of is not good networking 

We can totes forgive the original spec from adding in the slightly weird “Q2” format. This format is used when we're asking server “A” to ask server “B” for information. It’s like the user can’t get the information they want directly; they have to go through a gatekeeper server. The other servers are called Remote User Information Program (RUIP). Back in the 1970s when the RFC was created, the internet was often provided to a single computer at a site; the site then used other protocols and network to connect to other computers at the site (hence the internet used to be described as a “network of networks” which were expected to use non-Internet protocols). 

But in modern times, the Q2 “on behalf of” experience isn’t needed. Indeed, none of the servers I found would handle it. 

Massive security issues 

 Finger servers often return the time and location of user logins. For example, FINGER might say that a particular user is currently logged in at a particular terminal in a particular room. This is handy when dealing with friendly teammates, but is totes wrong when dealing with stalkers and worse. Lots of people really don’t want other people to know where they are. 

Giant compat issues with modern servers 

You might be confused by this one – what could I possibly mean about modern Finger servers? Have there even been any modern Finger servers at all? Why would anyone build a new Finger server given that the Finger protocol is often blocked by firewalls and provides very few features needed by people. 

It turns out that just looking on GitHub shows a bunch of different Finger servers. These servers are mostly derived from the original RFC 742 Finger protocol. It’s almost the same as the RFC 1288 Finger, but doesn’t allow for the /W switch. Other servers attempt to handle the /W switch, but don’t do it correctly (finger.farm, for example, failed until recently).  


One more thing about the /W switch spec: case-insensitive

[Later edit]: the RFC set of specs has long declared that just strings in the BNF descriptions should always be assumed to be case-insensitive: "monday" is the same as "Monday" and "MONDAY" and "MoNDAy". The FINGER spec takes the opposite approach: the /W switch, AFAICT, is actually case-sensitive and should always be upper-case.

As a fun aside: the RFC editors are, in the instance, wrong. While I understand why they decided that BNF should be case-insensitive (it's part of our text-based heritage), it's also the case that the workaround they use (specify case-sensitive strings as hex characters) is demonstrably error-prone. I've personally filed about a half-dozen different bugs against Internet protocols for getting the HEX representation of strings wrong.

The best solution is to require each BNF description to say if they are case-sensitive or not.

Use these learning for your own protocols! 

Finger is part of the old tradition of text-based services that are almost designed for direct command-line manipulation. As such, it’s now mostly out of favor (when was the last time you read your email by directly talking to a POP server?). That said, there are still lessons from FINGER for today. 

  • Simple, direct protocol descriptions are easier to debug than complex ones. 
  • Be aware of bad actors. Don't let your APIs enable stalkers and thieves. 
  • Make sure that the easy path for handling your protocol also allows servers an upgrade path. 


 


Note¹: Finger is one of the litte TCP services noted in RFC 848 along with echo, discard, systat, netstat, quotd chargen, finger and a couple of time-related services.