The Punter C1 Protocol

The Punter file transfer protocol (“New Punter”/“Punter C1”) is an alternative to the XMODEM family of protocols, which was and still is very popular on BBSes for Commodore computers. It is notorious for being badly documented. Let’s fix that.

I quickly developed quite a distaste for the badly designed punter protocol. Especially with the awful and rather smug “documentation”.

— MagerValp

The Protocol

The protocol allows the transmission of one file from the “sender” to the “receiver”. No file name, but a 1-byte file type is transmitted (0=PRG, 1=SEQ).

For handshaking, the three-byte ASCII strings GOO, BAD, ACK, S/B, SYN are used.

The transmission of a file consists of two almost identical phases:

  • Phase A transmits the file type, and consists of a lot of handshaking and overhead to transmit a single block with a single payload byte (the file type).
  • Phase B transmits the file data in one or more blocks. “B2” is repeated for each block.

A File Type

B File Contents

A1 Start

Sender Receiver
1 GOO
2 GOO

B1 Start

Sender Receiver
17 GOO

A2 Block

Sender Receiver
3 ACK
4 S/B
5 block data
6 GOO*

B2 Blocks(repeated)

Sender Receiver
18 ACK
19 S/B
20 block data
21 GOO*

A3 End-Off

Sender Receiver
7 ACK
8 S/B
9 SYN
10 SYN
11 S/B
12 (wait 1s) (ignored)
13 S/B
14 (wait 1s) (ignored)
15 S/B
16 (wait 1s) (ignored)

B3 End-Off

Sender Receiver
22 ACK
23 S/B
24 SYN
25 SYN
26 S/B
27 (wait 1s) (ignored)
28 S/B
29 (wait 1s) (ignored)
30 S/B
31 (wait 1s) (ignored)

*Step 6/21: After receiving the block data, if the checksum is incorrect, the receiver sends BAD, moving the protocol to step 3/18 with the same block.

Blocks

Every block has a 7 byte header:

offset size description
0 2 “additive” checksum
2 2 “cyclic” checksum
4 1 size of next block
5 2 block index

The two checksums are calculated over all bytes of the block, starting with index 4.

  • The “additive” checksum is calculated by adding all bytes.
  • The “cyclic” checksum is calculated by XORing all bytes, rotating the 16 bit result left after each byte.

Block sizes can be freely chosen and can vary from block to block. The maximum size is 255. This includes the 7 header bytes, so the payload is up to 248 bytes. Every block contains the size of the following block. The size of the first block is fixed (per phase). (For the last block in a sequence, this field is unused.)

The block index starts with 0. The last block (or only block) in a sequence has index -1 ($FFFF), though it is enough to only check the hi byte. This allows file sizes of almost 16 MB.

File Type Transmission

File type transmission only consists of a single 8 byte block. Its index is -1, since it is the last block. The single payload byte is the file type.

File Data Transmission

File data transmission starts with a 7 byte header block that contains no payload. Its purpose is to communicate the size of the first block that actually contains file data.

Bugs

Checksum

The two checksums are 32 bits total, yet CRC16 would probably provide better integrity guarantees.

End-Off Sequence

Steps 11-16 and 26-31 in the “End-Off” sequence in both phases are buggy. In the original implementation

  • The sender repeats the following three times: transmit S/B, then keep reading a byte from the modem about 5000 times, discarding the data. This takes about one second on a C64.
  • The receiver expects just one S/B, then proceeds to the next step. The GOO sent in step 17 will be ignored by the sender.

Because of the resends in the original implementation, the protocol can recover from this.

(Lines 5070-5120 in the original source are responsible for this. The call to accept with an argument of 0 will continue reading bytes from the modem until it times out, because it can’t match any of the codes. Maybe this was meant to match one or any specific code at some point.)

New implementations should expect three S/B and then wait for two seconds before continuing.

Multi-Punter

Multi-Punter is a very simple extension to the protocol that can transmit several files at a time and includes the file names and types. It has no error detection or re-send capability on a file level whatsoever.

Every file is transmitted like this:

  • 16× code 0x09 (TAB)
  • FILENAME,P (PRG) or FILENAME,S (SEQ)
  • code 0x0D (CR)

…followed by the transfer using the Punter protocol.

After the last file, the following is sent:

  • 16× code 0x09 (TAB)
  • 16× code 0x04 (EOT)
  • code 0x0D (CR)

Documentation

  • punter_c1.txt: The original documentation by Steve Punter with additional comments by Geoffrey Welsh and Matthew Desmond
  • Thread on CSDb, mostly by Oliver VieBrooks (Six), about reverse engineering the protocol

Implementations

  • The original 6502 source by Steve Punter was posted to net.micro.cbm in 1985. Almost all software supporting the Punter protocol was based on this implementation.
  • CCGMS Future contains a cleaned-up and heavily commented version of the original code.
  • C*Base contains an independent 6502 implementation. (Source is available in the downloads.)
  • There is a C implementation as part of CGTerm by Per Olofsson (MagerValp). It only supports downloading and interoperates with the C*Base implementation, but not the original implementation. There is a hacked version in the CCGMS Future repo that works with the original code (and CCGMS).
  • CBMTerm by Oliver VieBrooks (Six) contains a C# implementation, including Multi-Punter.
  • There is also a Rust implementation called punter-server by Steven Walter.

2 thoughts on “The Punter C1 Protocol”

  1. This bring back memories of getting file transfers on the Pet 2001 to work. I wrote the terminal program and file transfer code in Basic and got my files from Toronto. I remember because I did one file before the phone’s long distance started at 11 PM, And my parents were not happy about the phone bill.

  2. I am thinking undertaking a project of porting ALL of C-Base 3.0 with the exception of networking and the games and modular subs and ud support to Python.

    Please, someone stop me in my tracks, if you think the porting of PunterC1 or ANY protocol to Python is a fool’s errand. I know the whole thing is timing specific and my ideas live on different OSI layers, but I think at least in theory, that it could work.
    Maybe someone has already done it.

    Thoughts?

Leave a Reply to Earl Colby Pottinger Cancel reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.