We now have sufficient infrastructure to begin actually reading data from the network.
An message is broken into blocks, which are transmitted one at a time (to a first approximation). Each block is framed by control characters that delimit the data and indicate the position of the block in the message. A parity byte completes the block.
All idle time on the link is filled with [SYN] bytes.
The Reader trait is implemented by immutable objects that represent each state in the receive FSM. The FSM is driven by applying each byte received to current Reader, returning a new instance that represents the next state.
A single variable within an Akka Actor stores the current Reader.
The Reader instances are then pattern matched to drive the required side effects.
The following is simplified primarily by removing error handling.
For example, the message “A” would be represented by …[SYN][SOH][SEL]A[EM][ETX][parity byte][SYN]…
The actor state variable would then be:
- SYNReader
- SELReader
- TextReader
- EndReader
- MessageParityReader
- AcceptedMessageReader
- SOHReader
The SYNReader state silently consumes SYN characters, by returning itself. A byte that indicates the start of a block is handed off to next, whose value is returned. (which explains the above jump fron SYNReader to SELReader) Any other character is silently consumed (an area where the real implementation is more complex)
The SOHReader matches the first byte of the first block of the message, returning a SELReader to match the next character. Any other character causes the process to return to the initial state, looking for a stream of SYNs followed by SOH Note the SOHReader is an object since it has no state.
Start reading the text content once the SEL has been seen.
SYN bytes are silently ignored. The EM byte indicates the end of message; all other control characters are ignored. A data byte creates a new TextReader with a new Content that includes the byte.
The ETX byte returns the MessageParityReader to process the trailing parity byte. SYN bytes are ignored. Other bytes are ignored (in reality they trigger error recovery)
The MessageParityReader compares the (parity) byte against the parity computed over the block. A mismatched parity returns ResendReader, indicating the transmitter must resend the message. A match parity returns AcceptedMessageReader.
The AcceptedMessageReader simply indicates the message was successfully received. The asString method returns the received messsage. Any received byte is handed to SOHReader, which represents the start of the next message.
ResendReader indicates the block was malformed. Its existence triggers a retransmit.
A Content instance contains the message text received to date. The apply method returns a new instance that includes the byte. (i.e. an immutable, persistent data structure, much like List). The reject method returns a new Content that omits the last block.