summaryrefslogtreecommitdiffstats
path: root/README.md
diff options
context:
space:
mode:
Diffstat (limited to 'README.md')
-rw-r--r--README.md432
1 files changed, 432 insertions, 0 deletions
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..4722445
--- /dev/null
+++ b/README.md
@@ -0,0 +1,432 @@
+# zmodem.js - ZMODEM for JavaScript
+
+[![build status](https://api.travis-ci.org/FGasper/zmodemjs.svg?branch=master)](http://travis-ci.org/FGasper/zmodemjs)
+
+# SYNOPSIS
+
+ let zsentry = new Zmodem.Sentry( {
+ to_terminal(octets) { .. }, //i.e. send to the terminal
+
+ sender(octets) { .. }, //i.e. send to the ZMODEM peer
+
+ on_detect(detection) { .. }, //for when Sentry detects a new ZMODEM
+
+ on_retract() { .. }, //for when Sentry retracts a Detection
+ } );
+
+ //We have to configure whatever gives us new input to send that
+ //input to zsentry.
+ //
+ //EXAMPLE: From web browsers that use WebSocket …
+ //
+ ws.addEventListener("message", function(evt) {
+ zsentry.consume(evt.data);
+ } );
+
+The `on_detect(detection)` function call is probably the most complex
+piece of the above; one potential implementation might look like:
+
+ on_detect(detection) {
+
+ //Do this if we determine that what looked like a ZMODEM session
+ //is actually not meant to be ZMODEM.
+ if (no_good) {
+ detection.deny();
+ return;
+ }
+
+ zsession = detection.confirm();
+
+ if (zsession.type === "send") {
+
+ //Send a group of files, e.g., from an <input>’s “.files”.
+ //There are events you can listen for here as well,
+ //e.g., to update a progress meter.
+ Zmodem.Browser.send_files( zsession, files_obj );
+ }
+ else {
+ zsession.on("offer", (xfer) => {
+
+ //Do this if you don’t want the offered file.
+ if (no_good) {
+ xfer.skip();
+ return;
+ }
+
+ xfer.accept().then( () => {
+
+ //Now you need some mechanism to save the file.
+ //An example of how you can do this in a browser:
+ Zmodem.Browser.save_to_disk(
+ xfer.get_payloads(),
+ xfer.get_details().name
+ );
+ } );
+ });
+
+ zsession.start();
+ }
+ }
+
+# DESCRIPTION
+
+zmodem.js is a JavaScript implementation of the ZMODEM
+file transfer protocol, which facilitates file transfers via a terminal.
+
+# STATUS
+
+This library is BETA quality. It should be safe for general use, but
+breaking changes may still happen.
+
+# HOW TO USE THIS LIBRARY
+
+The basic workflow is:
+
+1. Create a `Zmodem.Sentry` object. This object must scan all input for
+a ZMODEM initialization string. See `zsentry.js`’s documentation for more
+details.
+
+2. Once that initialization is found, the `on_detect` event is fired
+with a `Detection` object as parameter. At this point you can `deny()`
+that Detection or `confirm()` it; the latter will return a Session
+object.
+
+3. Now you do the actual file transfer(s):
+
+ * If the session is a receive session, do something like this:
+
+ zsession.on("offer", (offer) => { ... });
+ let { name, size, mtime, mode, serial, files_remaining, bytes_remaining } = offer.get_details();
+
+ offer.skip();
+
+ //...or:
+
+ offer.on("input", (octets) => { ... });
+
+ //accept()’s return resolves when the transfer is complete.
+ offer.accept().then(() => { ... });
+ });
+ zsession.on("session_end", () => { ... });
+ zsession.start();
+
+ The `offer` handler receives an Offer object. This object exposes the details
+ about the transfer offer. The object also exposes controls for skipping or
+ accepting the offer.
+
+ * Otherwise, your session is a send session. Now the user chooses
+zero or more files to send. For each of these you should do:
+
+ zsession.send_offer( { ... } ).then( (xfer) => {
+ if (!xfer) ... //skipped
+
+ else {
+ xfer.send( chunk );
+ xfer.end( chunk ).then(after_end);
+ }
+ } );
+
+ Note that `xfer.end()`’s return is a Promise. The resolution of this
+Promise is the point at which either to send another offer or to do:
+
+ zsession.close().then( () => { ... } );
+
+ The `close()` Promise’s resolution is the point at which the session
+has ended successfully.
+
+That should be all you need. If you want to go deeper, though, each module
+in this distribution has JSDoc and unit tests.
+
+# RATIONALE
+
+ZMODEM facilitates terminal-based file transfers.
+This was an important capability in the 1980s and early 1990s because
+most modem use was for terminal applications, especially
+[BBS](https://en.wikipedia.org/wiki/Bulletin_board_system)es.
+(This was how, for example,
+popular shareware games like [Wolfenstein 3D](http://3d.wolfenstein.com)
+were often distributed.) The World Wide Web in the
+mid-1990s, however, proved a more convenient way to accomplish most of
+what BBSes were useful for, as a result of which the problem that ZMODEM
+solved became a much less important one.
+
+ZMODEM stuck around, though, as it remained a convenient solution
+for terminal users who didn’t want open a separate session to transfer a
+file. [Uwe Ohse](https://uwe.ohse.de/)’s
+[lrzsz](https://ohse.de/uwe/software/lrzsz.html) package
+provided a portable C implementation of the protocol (reworked from
+the last public domain release of the original code) that is installed on
+many systems today.
+
+Where `lrzsz` can’t reach, though, is terminals that don’t have command-line
+access—such as terminals that run in JavaScript. Now that
+[WebSocket](https://en.wikipedia.org/wiki/WebSocket) makes real-time
+applications like terminals possible in a web browser,
+there is a use case for a JavaScript
+implementation of ZMODEM to allow file transfers in this context.
+
+# GENERAL FLOW OF A ZMODEM SESSION:
+
+The following is an overview of an error-free ZMODEM session.
+
+0. If you call the `sz` command (or equivalent), that command will send
+a special ZRQINIT “pre-header” to signal your terminal to be a ZMODEM
+receiver.
+
+1. The receiver, upon recognizing the ZRQINIT header, responds with
+a ZRINIT header.
+
+2. The sender sends a ZFILE header along with information about the file.
+(This may also include the size and file count for the entire batch of files.)
+
+3. The recipient either accepts the file or skips it.
+
+4. If the recipient did not skip the file, then the sender sends the file
+contents. At the end the sender sends a ZEOF header to let the recipient
+know this file is done.
+
+5. The recipient sends another ZRINIT header. This lets the sender know that
+the recipient confirms receipt of the entire file.
+
+6. Repeat steps 2-5 until the sender has no more files to send.
+
+7. Once the sender has no more files to send, the sender sends a ZEOF header,
+which the recipient echoes back. The sender closes the session by sending
+`OO` (“over and out”).
+
+# PROTOCOL NOTES AND ASSUMPTIONS
+
+Here are some notes about this particular implementation.
+
+Particular notes:
+
+* We send with a maximum data subpacket size of 8 KiB (8,192 bytes). While
+the ZMODEM specification stipulates a maximum of 1 KiB, `lrzsz` accepts
+the larger size, and it seems to have become a de facto standard extension
+to the protocol.
+
+* Remote command execution (i.e., ZCOMMAND) is unimplemented. It probably
+wouldn’t work in browsers, which is zmodem.js’s principal use case.
+
+* No file translations are done. (Unix/Windows line endings are a
+future feature possibility.)
+
+* It is assumed that no error correction will be needed. All connections
+are assumed to be **“reliable”**; i.e.,
+data is received exactly as sent. We take this for granted today,
+but ZMODEM’s original application was over raw modem connections that
+often didn’t have reliable hardware error correction. TCP also wasn’t
+in play to do software error correction as generally happens
+today over remote connections. Because the forseeable use of zmodem.js
+is either over TCP or a local socket—both of which are reliable—it seems
+safe to assume that zmodem.js will not need to implement error correction.
+
+* zmodem.js sends with CRC-16 by default. Ideally we would just use CRC-16
+for everything, but lsz 0.12.20 has a [buffer overflow bug](https://github.com/gooselinux/lrzsz/blob/master/lrzsz-0.12.20.patch) that rears its
+head when you try to abort a ZMODEM session in the middle of a CRC-16 file
+transfer. To avoid this bug, zmodem.js advertises CRC-32 support when it
+receives a file, which makes lsz avoid the buffer overflow bug by using
+CRC-32.
+
+ The bug is reported, incidentally, and a fix is expected (nearly 20 years
+ after the last official lrzsz release!).
+
+* There is no XMODEM/YMODEM fallback.
+
+* Occasionally lrzsz will output things to the console that aren’t
+actual ZMODEM—for example, if you skip an offered file, `sz` will write a
+message about it to the console. For the most part we can accommodate these
+because they happen between ZMODEM headers; however, it’s possible to
+“poison” such messages, e.g., by sending a file whose name includes a
+ZMODEM header. So don’t do that. :-P
+
+# IMPLEMENTATION NOTES
+
+* I initially had success integrating zmodem.js with
+[xterm.js](https://xtermjs.org); however, that library’s plugin interface
+changed dramatically, and I haven’t created a new plugin to replace the
+old one. (It should be relatively straightforward if someone else wants to
+pick it up.)
+
+* Browsers don’t have an easy way to download only part of a file;
+as a result, anything the browser saves to disk must be the entire file.
+
+* ZMODEM is a _binary_ protocol. (There was an extension planned
+to escape everything down to 7-bit ASCII, but it doesn’t seem to have
+been implemented?) Hence, **if you use WebSocket, you’ll need to use
+binary messages, not text**.
+
+* lrzsz is the only widely-distributed ZMODEM implementation nowadays,
+which makes it a de facto standard in its
+own right. Thus far all end-to-end testing has been against it. It is
+thus possible that resolutions to disparities between `lrzsz` and the
+protocol specification may need to favor the implementation.
+
+* It is a generally-unavoidable byproduct of how ZMODEM works that
+the first header in a ZMODEM session will echo to the terminal. This
+explains the unsightly `**B0000…` stuff that you’ll see when you run
+either `rz` or `sz`.
+
+ That header
+ will include some form of line break. (From `lrzsz` means bytes 0x0d
+ and 0x8a—**not** 0x0a). Your terminal might react oddly to that;
+ if it does, try stripping out one or the other line ending character.
+
+# PROTOCOL CHOICE
+
+Both XMODEM and YMODEM (including the latter’s many variants) require the
+receiver to initiate the session by sending a “magic character” (ASCII SOH);
+the problem is that there’s nothing in the protocol to prompt the receiver
+to do so. ZMODEM is sender-driven, so the terminal can show a notice that
+says, “Do you want to receive a file?”
+
+This is a shame because these other two protocols are a good deal simpler
+than ZMODEM. The YMODEM-g variant in particular would be better-suited to
+our purpose because it doesn’t “litter” the transfer with CRCs.
+
+There is also [Kermit](http://www.columbia.edu/kermit/kermit.html), which
+seems to be more standardized than ZMODEM but **much** more complex.
+
+# DESIGN NOTES
+
+zmodem.js tries to avoid “useless” states:
+either we fail completely, or we succeed. To that end, some callbacks are
+required arguments (e.g., the Sentry constructor’s `to_terminal` argument),
+while others are registered separately.
+
+Likewise, for this reason some of the session-level logic is exposed only
+through the Transfer and Offer objects. The Session creates these
+internally then exposes them via callback
+
+# SOURCES
+
+ZMODEM is not standardized in a nice, clean, official RFC like DNS or HTTP;
+rather, it was one guy’s solution to a particular problem. There is
+documentation, but it’s not as helpful as it might be; for example,
+there’s only one example workflow given, and it’s a “happy-path”
+transmission of a single file.
+
+As part of writing zmodem.js I’ve culled together various resources
+about the protocol. As far as I know these are the best sources for
+information on ZMODEM.
+
+Two documents that describe ZMODEM are saved in the repository for reference.
+The first is the closest there is to an official ZMODEM specification:
+a description of the protocol from its author, Chuck Forsberg. The second
+seems to be based on the first and comes from
+[Jacques Mattheij](https://jacquesmattheij.com).
+
+**HISTORICAL:** The included `rzsz.zip` file (fetched from [ftp://archives.thebbs.org/file_transfer_protocols/](ftp://archives.thebbs.org/file_transfer_protocols/) on 16 October 2017)
+is the last public domain release
+from Forsberg. [http://freeware.nekochan.net/source/rzsz/](http://freeware.nekochan.net/source/rzsz/) has what is supposedly Forsberg’s last shareware release;
+I have not looked at it except for the README. I’m not sure of the
+copyright status of this software: Forsberg is deceased, and his company
+appears to be defunct. Regardless, neither it nor its public domain
+predecessor is likely in widespread use.
+
+Here are some other available ZMODEM implementations:
+
+* [lrzsz](https://ohse.de/uwe/software/lrzsz.html)
+
+ A widely-deployed adaptation of Forsberg’s last public domain ZMODEM
+ code. This is the de facto “reference” implementation, both by virtue
+ of its wide availability and its derivation from Forsberg’s original.
+ If your server has the `rz` and `sz` commands, they’re probably
+ from this package.
+
+* [SyncTERM](http://syncterm.bbsdev.net)
+
+ Based on Jacques Mattheij’s ZMODEM implementation, originally called
+ zmtx/zmrx. This is a much more readable implementation than lrzsz
+ but lamentably one that doesn’t seem to compile as readily.
+
+* [Qodem](https://github.com/klamonte/qodem)
+
+ This terminal emulator package appears to contain its own ZMODEM
+ implementation.
+
+* [PD Zmodem](http://pcmicro.com/netfoss/pdzmodem.html)
+
+ I know nothing of this one.
+
+* [zmodem (Rust)](https://github.com/lexxvir/zmodem)
+
+ A pure [Rust](http://rust-lang.org) implementation of ZMODEM.
+
+# REQUIREMENTS
+
+This library only supports modern browsers. There is no support for
+Internet Explorer or other older browsers planned.
+
+The tests have run successfully against node.js version 8.
+
+# DOCUMENTATION
+
+Besides this document, each module has inline [jsdoc](http://usejsdoc.org).
+You can see it by running `yarn` in the repository’s root directory;
+the documentation will build in a newly-created `documentation` directory.
+
+# CONTRIBUTING
+
+Contributions are welcome via
+[https://github.com/FGasper/zmodemjs](https://github.com/FGasper/zmodemjs).
+
+# TROUBLESHOOTING
+
+Before you do anything else, set `Zmodem.DEBUG` to true. This will log
+useful information about the ZMODEM session to your JavaScript console. That
+may give you all you need to fix your problem.
+
+If you have trouble transferring files, try these diagnostics:
+
+1. Transfer an empty file. (Run `touch empty.bin` to create one named `empty.bin`.)
+
+2. Transfer a small file. (`echo hello > small.txt`)
+
+3. Transfer a file that contains all possible octets. (`perl -e 'print chr for 0 .. 255' > all_bytes.bin`)
+
+4. If a specific file fails, does it still fail if you `truncate` a copy of
+the file down to, say, half size and transfer that truncated file? Does it
+work if you truncate the file down to 1 byte? If so, then use this method
+to determine which specific place in the file triggers the transfer error.
+
+**IF YOU HAVE DONE THE ABOVE** and still think the problem is with zmodem.js,
+you can file a bug report. Note that, historically, most bug reports have
+reflected implementation errors rather than bugs in zmodem.js.
+
+# TODO
+
+* Teach send sessions to “fast-forward” so as to honor requests for
+append-style sessions.
+
+* Implement newline conversions.
+
+* Teach Session how to do and to handle pre-CRC checks.
+
+* Possible: command-line `rz`, if there’s demand for it, e.g., in
+environments where `lrzsz` can’t run. (NB: The distribution includes
+a bare-bones, proof-of-concept `sz` replacement.)
+
+# KNOWN ISSUERS
+
+* In testing, Microsoft Edge appeared not to care what string was given
+to `<a>`’s `download` attribute; the saved filename was based on the
+browser’s internal Blob object URL instead.
+
+# COPYRIGHT
+
+Copyright 2017 Gasper Software Consulting
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+Parts of the CRC-16 logic are adapted from crc-js by Johannes Rudolph.