Files
LibretroAdmin c04b91fe69 network: modem V.42 and 56K, the Broadband Adapter, DCNet
The modem:
- V.42 error correction (LAPM) and V.42bis compression for the games
  that dial in sync mode, which were forced to async, and V.8bis and
  56K (V.90/K56 speeds).
- Receive and transmit FIFOs timed by the line's speeds, the TXFNF/TXHF
  status bits, and Power Smash's symmetric speed.
- PPP waits for the game to speak first, on the network thread; this
  replaces the quarter-second delay on reads.
- Its ID reports the country as USA, as upstream's.
- Frames short or long, an XID with bad lengths, a REJ for a frame that
  is gone, a V.8bis message without its end, a V.42bis step-up of a
  dictionary not a power of two: none can read or write past a buffer,
  loop for ever or be refused wrongly any more.

The Broadband Adapter could not be turned on: settings forced it off
and nothing set it. The option "Expansion Port Device (Restart
Required)" (reicast_network_device) chooses the modem, the adapter or
nothing; the modem by default, as before. The adapter gets its
link-change interrupt 10 ms after a reset, as KallistiOS waits for, frames
under 6 or over 1514 bytes are dropped, and the network starts with the
game's first frame and stops on a GAPS reset.

DCNet: "Use DCNet" (reicast_dcnet, off by default) goes online through
the dcnet.flyca.st relay instead of picoTCP, on the same network thread,
non-blocking, without locks. Power Smash and Pro Yakyuu always go
through it, as upstream has them.

picoTCP: dns.flyca.st as the name server; a socket that is ready is read
whole (an upload no longer stalls); the ports of Daytona, Sega Tetris,
Golf Shiyouyo 2, Dee Dee Planet, Driving Strikers, Floigan and the
Internet Game Pack. Stopping the network thread no longer waits for it.

In the save state (V24), on a Dreamcast: the device chosen and what the
modem (FIFOs, V.42, V.42bis, V.8bis) and the adapter (GAPS, its RAM, the
RTL8139) hold, 34 KB. A live connection cannot be in a state: one loaded
replays what came over the line at the times it came, and a line the
state does not have hangs up, without waiting for the thread.

tools/threads/pico_test.cpp tests the thread with the new interface,
and DCNet against a stand-in relay.
2026-10-13 14:37:55 +02:00

1326 lines
29 KiB
C++

/*
Copyright 2025 flyinghead
This file is part of Flycast.
Flycast is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
Flycast is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Flycast. If not, see <https://www.gnu.org/licenses/>.
*/
#include "v42.h"
#include "hw/sh4/sh4_sched.h"
#include "hw/sh4/sh4_if.h"
#include <algorithm>
namespace modem
{
// V.42 timers
#define T400_CYCLES ((u64)SH4_MAIN_CLOCK / 1000 * 750) // detection phase
#define T403_CYCLES ((u64)SH4_MAIN_CLOCK / 1000 * 1000) // inactivity
void V14Encoder::write(u8 v)
{
transmitBit(0); // start bit
for (int i = 0; i < 8; i++) {
transmitBit(v & 1);
v >>= 1;
}
// stop bit(s)
for (int i = 0; i < txStopBits; i++)
transmitBit(1);
}
void V14Encoder::flush()
{
if (txBitCount != 0) {
while (txBitCount != 0)
transmitBit(1);
}
else {
for (int i = 0; i < 8; i++)
transmitBit(1);
}
}
void V14Encoder::reset()
{
buffer.clear();
txCurByte = 0;
txBitCount = 0;
txStopBits = 1;
}
void V14Encoder::transmitBit(u8 v)
{
txCurByte = (txCurByte >> 1) | (v << 7);
txBitCount++;
if (txBitCount == 8)
{
buffer.push(txCurByte);
txCurByte = 0;
txBitCount = 0;
}
}
void V14Encoder::serialize(void **data, unsigned int *total_size) const
{
u8 bitCount = (u8)txBitCount;
u8 stopBits = (u8)txStopBits;
buffer.serialize(data, total_size);
LIBRETRO_S(txCurByte);
LIBRETRO_S(bitCount);
LIBRETRO_S(stopBits);
}
void V14Encoder::unserialize(void **data, unsigned int *total_size)
{
u8 bitCount = 0;
u8 stopBits = 1;
buffer.unserialize(data, total_size);
LIBRETRO_US(txCurByte);
LIBRETRO_US(bitCount);
LIBRETRO_US(stopBits);
txBitCount = bitCount & 7;
txStopBits = stopBits >= 1 && stopBits <= 16 ? stopBits : 1;
}
void V14Decoder::write(u8 v)
{
for (int i = 0; i < 8; i++)
{
if (rxState == RX_STOP_BIT)
{
// waiting for 1st stop bit
if ((v & 1) == 0)
{
if (charsSinceMissingStop < 4)
INFO_LOG(MODEM, "V.14 stop bit missing after %d chars", charsSinceMissingStop);
charsSinceMissingStop = 0;
rxState = RX_RECEIVING;
rxBitCount = 0;
maxStopBits = std::max(maxStopBits, rxStopBits);
}
else {
rxStopBits = 1;
rxState = RX_IDLE;
}
}
else if (rxState == RX_IDLE)
{
// idle or more stop bits state
if ((v & 1) == 0)
{
// start bit => now receiving
rxState = RX_RECEIVING;
rxBitCount = 0;
maxStopBits = std::max(maxStopBits, rxStopBits);
}
else if (rxStopBits < 0x10000) {
rxStopBits++;
}
}
else
{
rxCurByte = (rxCurByte >> 1) | ((v & 1) << 7);
rxBitCount++;
if (rxBitCount == 8)
{
rxState = RX_STOP_BIT;
buffer.push(rxCurByte);
if (charsSinceMissingStop < 0x10000)
charsSinceMissingStop++;
rxStopBits = 0;
}
}
v >>= 1;
}
}
int V14Decoder::receivedStopBits()
{
int max = maxStopBits;
maxStopBits = 0;
return max;
}
void V14Decoder::reset()
{
buffer.clear();
rxCurByte = 0;
rxState = RX_IDLE;
rxBitCount = 0;
charsSinceMissingStop = 0;
rxStopBits = 0;
maxStopBits = 0;
}
void V14Decoder::serialize(void **data, unsigned int *total_size) const
{
u8 st = (u8)rxState;
u8 bitCount = (u8)rxBitCount;
s32 since = charsSinceMissingStop;
s32 stopBits = rxStopBits;
s32 maxStop = maxStopBits;
buffer.serialize(data, total_size);
LIBRETRO_S(rxCurByte);
LIBRETRO_S(st);
LIBRETRO_S(bitCount);
LIBRETRO_S(since);
LIBRETRO_S(stopBits);
LIBRETRO_S(maxStop);
}
void V14Decoder::unserialize(void **data, unsigned int *total_size)
{
u8 st = 0;
u8 bitCount = 0;
s32 since = 0;
s32 stopBits = 0;
s32 maxStop = 0;
buffer.unserialize(data, total_size);
LIBRETRO_US(rxCurByte);
LIBRETRO_US(st);
LIBRETRO_US(bitCount);
LIBRETRO_US(since);
LIBRETRO_US(stopBits);
LIBRETRO_US(maxStop);
rxState = st <= RX_STOP_BIT ? (RxState)st : RX_IDLE;
rxBitCount = bitCount <= 8 ? bitCount : 0;
charsSinceMissingStop = std::min(std::max(since, 0), 0x10000);
rxStopBits = std::min(std::max(stopBits, 0), 0x10000);
maxStopBits = std::min(std::max(maxStop, 0), 0x10000);
}
static u16 calcCrc16(const u8 *data, unsigned len)
{
u16 crc = 0xffff;
for (unsigned i = 0; i < len; i++)
{
u16 word = (crc ^ data[i]) & 0xFFu;
word = (u16)(word ^ (u16)((word << 4u) & 0xFFu));
word = (u16)((word << 8u) ^ (word << 3u) ^ (word >> 4u));
crc = ((crc >> 8) ^ word);
}
return crc ^ 0xffff;
}
template<unsigned N>
bool HdlcEncoder<N>::sendFrame(const u8 *data, unsigned len)
{
// Flags, the frame and its CRC with a stuffed bit after every five ones at most
if ((unsigned)buffer.room() < (len + 2) * 6 / 5 + 4)
return false;
// opening flag
sendFlag();
// data
for (unsigned i = 0; i < len; i++)
sendByte(data[i]);
// crc
u16 crc = calcCrc16(data, len);
sendByte(crc & 0xff);
sendByte(crc >> 8);
// closing flag
sendFlag();
return true;
}
template<unsigned N>
void HdlcEncoder<N>::sendFlag()
{
txBitBuffer |= 0x7e << (8 + txBitCount);
txBitBuffer >>= 8;
buffer.push(txBitBuffer & 0xff);
}
template<unsigned N>
void HdlcEncoder<N>::sendByte(u8 byte)
{
txBitBuffer |= (u32)byte << (8 + txBitCount);
u32 mask = 0x1f0 << txBitCount;
for (int i = 0; i < 8; i++)
{
if ((txBitBuffer & mask) == mask)
{
u32 lShiftMask = 0xffffffff << (8 + txBitCount + i + 1);
txBitBuffer = ((txBitBuffer & lShiftMask) << 1) | (txBitBuffer & ~lShiftMask);
txBitCount++;
mask <<= 1;
}
mask <<= 1;
}
txBitCount += 8;
while (txBitCount >= 8)
{
txBitBuffer >>= 8;
buffer.push(txBitBuffer & 0xff);
txBitCount -= 8;
}
}
template<unsigned N>
void HdlcEncoder<N>::reset()
{
buffer.clear();
txBitCount = 0;
txBitBuffer = 0;
}
template<unsigned N>
void HdlcEncoder<N>::serialize(void **data, unsigned int *total_size) const
{
u8 bitCount = (u8)txBitCount;
buffer.serialize(data, total_size);
LIBRETRO_S(txBitBuffer);
LIBRETRO_S(bitCount);
}
template<unsigned N>
void HdlcEncoder<N>::unserialize(void **data, unsigned int *total_size)
{
u8 bitCount = 0;
buffer.unserialize(data, total_size);
LIBRETRO_US(txBitBuffer);
LIBRETRO_US(bitCount);
// Fewer bits than an octet wait between two calls
txBitCount = bitCount & 7;
}
template<unsigned N>
void HdlcDecoder<N>::write(u8 v)
{
// (a frame not taken by now is dropped)
ready = false;
for (int bit = 0; bit < 8; bit++)
{
if (rxOnes >= 5)
{
if ((v & 1) == 1)
{
// we either have a flag or an abort
rxCurByte = (rxCurByte >> 1) | 0x80;
rxPosition++;
}
else
{
if (rxOnes == 6)
{
// we have a flag
if (rxSize != 0 || overflow)
{
if (overflow) {
INFO_LOG(MODEM, "HDLC frame too long");
}
else if (rxSize < 3) {
INFO_LOG(MODEM, "HDLC frame too short: %d bytes", rxSize);
}
else
{
u16 crc = calcCrc16(rxFrame, rxSize - 2);
if (crc != (rxFrame[rxSize - 2] | (rxFrame[rxSize - 1] << 8)))
{
WARN_LOG(MODEM, "Invalid CRC in received frame");
}
else
{
ready = true;
readySize = rxSize - 2;
}
}
rxSize = 0;
overflow = false;
}
rxCurByte = 0;
rxPosition = 0;
}
else if (rxOnes > 6)
{
// abort
INFO_LOG(MODEM, "HDLC abort received");
rxCurByte = 0;
rxPosition = 1; // keep the last 0
rxSize = 0;
overflow = false;
}
// ignore stuffed 0
rxOnes = 0;
}
}
else {
rxCurByte = (rxCurByte >> 1) | ((v & 1) << 7);
rxPosition++;
}
if (rxPosition == 8)
{
if (rxSize < N)
rxFrame[rxSize++] = rxCurByte;
else
overflow = true;
rxCurByte = 0;
rxPosition = 0;
}
if ((v & 1) == 1)
{
// (a long run of ones is an idle line: only how many up to 7 matters)
if (rxOnes < 7)
rxOnes++;
}
else
rxOnes = 0;
v >>= 1;
}
}
template<unsigned N>
void HdlcDecoder<N>::reset()
{
rxSize = 0;
overflow = false;
ready = false;
readySize = 0;
rxPosition = 0;
rxCurByte = 0;
rxOnes = 0;
}
template<unsigned N>
void HdlcDecoder<N>::serialize(void **data, unsigned int *total_size) const
{
u8 frame[N];
u16 size = (u16)rxSize;
u8 bits[4] = { (u8)overflow, (u8)rxOnes, (u8)rxPosition, rxCurByte };
for (unsigned i = 0; i < N; i++)
frame[i] = i < rxSize ? rxFrame[i] : 0;
LIBRETRO_S(size);
LIBRETRO_SA(frame, N);
LIBRETRO_SA(bits, 4);
}
template<unsigned N>
void HdlcDecoder<N>::unserialize(void **data, unsigned int *total_size)
{
u16 size = 0;
u8 bits[4] = { 0 };
LIBRETRO_US(size);
LIBRETRO_USA(rxFrame, N);
LIBRETRO_USA(bits, 4);
rxSize = size <= N ? size : 0;
overflow = bits[0] != 0;
rxOnes = std::min<int>(bits[1], 7);
rxPosition = bits[2] & 7;
rxCurByte = bits[3];
ready = false;
readySize = 0;
}
template class HdlcEncoder<2048>;
template class HdlcEncoder<256>;
template class HdlcDecoder<V42_MAX_INFO + 5 + 64>;
template class HdlcDecoder<128>;
static inline unsigned seqDistance(unsigned from, unsigned to) {
return (to - from) & 127;
}
static inline u8 incMod128(unsigned v) {
return (v + 1) & 127;
}
/* The time in SH4 cycles. The protocol of the modem's static object is
* reset before the machine is made, when there is no time yet. */
static inline u64 now() {
return p_sh4rcb != NULL ? sh4_sched_now64() : 0;
}
void V42Protocol::handleFrame()
{
if (!hdlcDecoder.frameAvailable())
return;
hdlcDecoder.frameTaken();
const u8 *rxFrame = hdlcDecoder.frame();
const unsigned size = hdlcDecoder.frameSize();
if (size < 2) {
WARN_LOG(MODEM, "Invalid frame: %d bytes", size);
return;
}
if ((rxFrame[0] & 1) == 0) {
WARN_LOG(MODEM, "Invalid frame: Unexpected extended address");
return;
}
if ((rxFrame[0] >> 2) != 0) {
WARN_LOG(MODEM, "Invalid frame: Unknown address %02x", rxFrame[0] >> 2);
return;
}
if ((rxFrame[1] & 3) != 3)
{
// Information and Supervisory frames have a second control byte
if (size < 3) {
WARN_LOG(MODEM, "Invalid frame: control %02x and %d bytes", rxFrame[1], size);
return;
}
// and they acknowledge sent frames (except SREJ)
if (rxFrame[1] != 0xd)
ackIFrame(rxFrame[2] >> 1);
}
inactivityStart = now();
switch (rxFrame[1] & 0xef)
{
case 0x01: // RR
if (!receiverReady)
NOTICE_LOG(MODEM, "Received RR");
receiverReady = true;
break;
case 0x05: // RNR
if (receiverReady)
WARN_LOG(MODEM, "Received RNR");
receiverReady = false;
break;
case 0x09: // REJECT
receiverReady = true;
handleReject(rxFrame[2]);
break;
case 0x0d: // SREJ
WARN_LOG(MODEM, "Received SREJ");
break;
case 0x6f: // SABME
handleSabme(rxFrame[0], rxFrame[1]);
break;
case 0x0f: // DM
break;
case 0x03: // UI
WARN_LOG(MODEM, "Received UI");
break;
case 0x43: // DISC
handleDisc(rxFrame[0]);
break;
case 0x63: // UA
receiverReady = true;
break;
case 0x87: // FRMR
WARN_LOG(MODEM, "Received FRMR");
break;
case 0xaf: // XID
handleXid(rxFrame, size);
break;
case 0xe3: // TEST
break;
default:
if (rxFrame[1] & 1)
{
WARN_LOG(MODEM, "Invalid HDLC command: %02x", rxFrame[1]);
return;
}
// I-frame
if (phase != Connected) {
WARN_LOG(MODEM, "I-frame received but not connected");
return;
}
handleIFrame(rxFrame, size);
break;
}
}
void V42Protocol::handleSabme(u8 address, u8 control)
{
if (phase == Connected)
WARN_LOG(MODEM, "V.42: SABME received while already connected");
else
INFO_LOG(MODEM, "V.42: Received SABME");
const u8 ua[] { address, (u8)(0x63 | (control & 0x10)) };
sendFrame(ua, sizeof(ua));
phase = Connected;
txSeqNum = 0;
rxSeqNum = 0;
txSeqAck = 0;
receiverReady = true;
memset(sentSize, 0, sizeof(sentSize));
resending = false;
}
void V42Protocol::handleDisc(u8 address)
{
INFO_LOG(MODEM, "V.42: Received DISC");
phase = Release;
const u8 ua[] { address, 0x73 };
sendFrame(ua, sizeof(ua));
}
void V42Protocol::handleIFrame(const u8 *rxFrame, unsigned size)
{
const int seqNum = rxFrame[1] >> 1;
if (seqNum != rxSeqNum)
{
INFO_LOG(MODEM, "Received frame num out of sequence: expected %d but recv %d", rxSeqNum, seqNum);
// send REJECT: N(R) is in the upper 7 bits
const u8 reject[] { rxFrame[0], 9, (u8)(rxSeqNum << 1) };
sendFrame(reject, sizeof(reject));
return;
}
rxSeqNum = incMod128(rxSeqNum);
if (compressionEnabled)
{
for (unsigned i = 3; i < size; i++)
{
if (!decompressor.write(rxFrame[i], outputStream))
{
// send DISC
const u8 disc[] { 1, 0x43 };
sendFrame(disc, sizeof(disc));
phase = Release;
memset(sentSize, 0, sizeof(sentSize));
resending = false;
return;
}
}
}
else {
for (unsigned i = 3; i < size; i++)
outputStream.write(rxFrame[i]);
}
// if P bit set, respond with RR
// if not set, respond with RR if no I-frame available to send
if ((rxFrame[2] & 1) || inputStream.available() == 0)
{
const u8 rr[] { rxFrame[0], 1, u8((rxSeqNum << 1) | (rxFrame[2] & 1)) };
sendFrame(rr, sizeof(rr));
}
}
// The value of a parameter: big-endian, 4 bytes at most
static u32 xidValue(const u8 *p, unsigned len)
{
u32 v = 0;
for (unsigned i = 0; i < len; i++)
v = (v << 8) | p[i];
return v;
}
static void xidSetValue(u8 *p, unsigned len, u32 v)
{
for (unsigned i = len; i > 0; i--)
{
p[i - 1] = v & 0xff;
v >>= 8;
}
}
/* The other end proposes the parameters of the link and of compression: the
* answer is the same frame, with the values this end cannot have lowered to
* what it can, which the negotiation allows. */
void V42Protocol::handleXid(const u8 *rxFrame, unsigned size)
{
u8 echo[sizeof(sentFrames[0]) + 64];
u32 maxCodeWords = v42b::MIN_TOTAL_CODEWORDS;
u32 maxStringLength = v42b::DEF_STRING_SIZE;
compressionEnabled = false;
// format identifier
if (size < 3 || rxFrame[2] != 0x82) {
WARN_LOG(MODEM, "Unexpected XID format: %02x", size < 3 ? 0 : rxFrame[2]);
return;
}
if (size > sizeof(echo))
size = sizeof(echo);
memcpy(echo, rxFrame, size);
unsigned echoSize = size;
// Iterate over parameter groups
enum {
GID_PARAM = 0x80,
GID_PRIVATE = 0xf0,
GID_USER_DATA = 0xff,
};
for (unsigned i = 3; i + 3 <= size; )
{
const u8 gid = echo[i];
if (gid == GID_USER_DATA)
{
// not answered
echoSize = i;
break;
}
const unsigned groupSize = echo[i + 1] * 256 + echo[i + 2];
const unsigned end = std::min(i + 3 + groupSize, size);
if (gid != GID_PARAM && gid != GID_PRIVATE)
{
INFO_LOG(MODEM, "Unexpected XID GI: %02x", gid);
i = end;
continue;
}
// Parameters
for (unsigned j = i + 3; j + 2 <= end; )
{
const u8 paramId = echo[j];
const u8 paramSize = echo[j + 1];
u8 *pv = &echo[j + 2];
if (j + 2 + paramSize > end)
break;
const bool small = paramSize <= 4;
const u32 v = small ? xidValue(pv, paramSize) : 0;
if (!small)
WARN_LOG(MODEM, "Unexpected param length for PI %d: %x", paramId, paramSize);
switch (paramId)
{
case 0: // (private) Parameter set identification
break;
case 1: // (private) Data compression request
if (gid == GID_PRIVATE && paramSize >= 1)
{
DEBUG_LOG(MODEM, "xid: Data compression request %x", pv[0]);
if (pv[0] == 3)
compressionEnabled = true;
else
// Disable all compression if only one direction is compressed (not used anyway)
pv[0] = 0;
}
break;
case 2: // (private) Number of codewords
if (gid == GID_PRIVATE && small)
{
DEBUG_LOG(MODEM, "xid: Number of codewords %d", v);
maxCodeWords = v;
if (v > v42b::MAX_TOTAL_CODEWORDS)
{
maxCodeWords = v42b::MAX_TOTAL_CODEWORDS;
xidSetValue(pv, paramSize, maxCodeWords);
}
}
break;
case 3:
if (gid == GID_PARAM && small)
{
// HDLC optional function (public)
DEBUG_LOG(MODEM, "xid: HDLC optional function %x", v);
}
else if (gid == GID_PRIVATE && small)
{
// (private) Maximum string length
DEBUG_LOG(MODEM, "xid: Maximum string length %d", v);
maxStringLength = v;
if (v > (u32)v42b::MAX_STRING_SIZE)
{
maxStringLength = v42b::MAX_STRING_SIZE;
xidSetValue(pv, paramSize, maxStringLength);
}
}
break;
case 5: // Maximum length of information field: tx
case 6: // Maximum length of information field: rx
if (gid == GID_PARAM && small)
{
DEBUG_LOG(MODEM, "xid: Max %s len %d bits", paramId == 5 ? "tx" : "rx", v);
u32 bits = v;
if (bits > V42_MAX_INFO * 8)
{
bits = V42_MAX_INFO * 8;
xidSetValue(pv, paramSize, bits);
}
if (paramId == 5 && bits >= 8)
txMaxSize = bits / 8;
}
break;
case 7: // Window size: tx
case 8: // Window size: rx
if (gid == GID_PARAM && small)
{
DEBUG_LOG(MODEM, "xid: %s window %d", paramId == 7 ? "Tx" : "Rx", v);
u32 k = v;
if (k > 15)
{
k = 15;
xidSetValue(pv, paramSize, k);
}
if (paramId == 7 && k >= 1)
txWindow = k;
}
break;
default:
WARN_LOG(MODEM, "Unexpected PI %d (size %x, group id %x)", paramId, paramSize, gid);
break;
}
j += 2 + paramSize;
}
i = end;
}
sendFrame(echo, echoSize);
if (compressionEnabled)
{
NOTICE_LOG(MODEM, "V.42bis compression enabled: max dictionary size %d", maxCodeWords);
compressor.setParams(maxCodeWords, maxStringLength);
compressor.reset();
decompressor.setParams(maxCodeWords, maxStringLength);
decompressor.reset();
}
}
void V42Protocol::handleReject(u8 control2)
{
const u8 seq = control2 >> 1;
INFO_LOG(MODEM, "Received REJECT %d", seq);
// The frames from N(R) on are sent again, one at a time as the line has room
if (seqDistance(txSeqAck, seq) <= seqDistance(txSeqAck, txSeqNum) && seq != txSeqNum)
{
resendSeq = seq;
resending = true;
}
}
void V42Protocol::sendIFrame()
{
if (!receiverReady || inputStream.available() == 0)
return;
// check that tx window isn't reached
if ((int)seqDistance(txSeqAck, txSeqNum) >= txWindow)
return;
u8 *frame = sentFrames[txSeqNum & 15];
unsigned len = 3;
frame[0] = 1;
frame[1] = txSeqNum << 1;
frame[2] = rxSeqNum << 1;
if (compressionEnabled)
{
while (compressor.available() < (int)txMaxSize)
{
int c = inputStream.read();
if (c == -1)
break;
compressor.write(c);
}
compressor.flush();
while (len - 3 < txMaxSize)
{
int cc = compressor.read();
if (cc == -1)
break;
frame[len++] = cc & 0xff;
}
}
else
{
while (len - 3 < txMaxSize)
{
int c = inputStream.read();
if (c == -1)
break;
frame[len++] = c & 0xff;
}
}
if (len == 3)
// don't send an empty frame
return;
sentSize[txSeqNum & 15] = len;
txSeqNum = incMod128(txSeqNum);
sendFrame(frame, len);
}
void V42Protocol::feedV14()
{
while (v14Encoder.available() < 64)
{
int c = inputStream.read();
if (c == -1)
break;
v14Encoder.write(c);
}
}
int V42Protocol::read()
{
if (phase == None || phase == Detection) {
v14Encoder.flush();
return v14Encoder.read();
}
else if (phase == V14)
{
feedV14();
if (v14Encoder.available() == 0)
v14Encoder.flush();
return v14Encoder.read();
}
if (hdlcEncoder.available() == 0)
{
if (phase == Connected)
{
// Frames rejected by the other end first
if (resending && seqDistance(txSeqAck, resendSeq) > seqDistance(txSeqAck, txSeqNum))
// acknowledged in the meantime
resendSeq = txSeqAck;
while (resending && hdlcEncoder.available() == 0)
{
if (resendSeq == txSeqNum) {
resending = false;
break;
}
const unsigned slot = resendSeq & 15;
if (sentSize[slot] != 0 && (sentFrames[slot][1] >> 1) == resendSeq)
{
sentFrames[slot][2] = rxSeqNum << 1;
sendFrame(sentFrames[slot], sentSize[slot]);
}
else {
WARN_LOG(MODEM, "REJ frame %d not found", resendSeq);
}
resendSeq = incMod128(resendSeq);
}
if (hdlcEncoder.available() == 0)
sendIFrame();
if (hdlcEncoder.available() == 0 && now() - inactivityStart >= T403_CYCLES)
{
// send an RR frame with P bit set
const u8 rr[] { 1, 1, u8((rxSeqNum << 1) | 1) };
sendFrame(rr, sizeof(rr));
}
}
if (hdlcEncoder.available() == 0)
sendFlag();
}
return hdlcEncoder.read();
}
void V42Protocol::write(u8 v)
{
if (phase == None || phase == Detection)
{
if (now() - detectionStart >= T400_CYCLES) {
INFO_LOG(MODEM, "Switching to V.14 mode");
phase = V14;
}
else
{
v14Decoder.write(v);
int c = v14Decoder.read();
if (c == -1)
return;
v = c;
}
}
if (phase == None || phase == Detection)
{
// V.42 ODP: DC1 with alternating parity followed by 8+1 or 16+1 ones
bool dc1 = false;
if (v == 0x11 || v == 0x91)
{
const int stopBits = v14Decoder.receivedStopBits();
dc1 = stopBits == 9 || stopBits == 17;
}
if (phase == None)
{
if (dc1)
{
phase = Detection;
lastRx = v;
odpCount = 1;
}
}
else
{
if (dc1 && v != lastRx)
{
odpCount++;
if (odpCount == 4)
{
odpCount = 0;
// send ADP
v14Encoder.setStopBits(9);
v14Encoder.write('E');
v14Encoder.write('C');
//v14Encoder.write(0); // switches to v14 only and start ppp nego
v14Encoder.setStopBits(1);
}
}
else
{
odpCount = 0;
if (v == 0x7e)
phase = Establish;
}
lastRx = v;
}
}
else if (phase == Establish || phase == Connected) {
hdlcDecoder.write(v);
handleFrame();
}
else if (phase == V14)
{
v14Decoder.write(v);
for (int c = v14Decoder.read(); c != -1; c = v14Decoder.read())
outputStream.write(c);
}
}
void V42Protocol::reset()
{
phase = None;
lastRx = 0;
odpCount = 0;
v14Encoder.reset();
v14Decoder.reset();
hdlcEncoder.reset();
hdlcDecoder.reset();
txSeqNum = 0;
rxSeqNum = 0;
txSeqAck = 0;
receiverReady = true;
txMaxSize = 128;
txWindow = 15;
memset(sentFrames, 0, sizeof(sentFrames));
memset(sentSize, 0, sizeof(sentSize));
resendSeq = 0;
resending = false;
detectionStart = now();
inactivityStart = detectionStart;
compressionEnabled = false;
compressor.setParams(v42b::MIN_TOTAL_CODEWORDS, v42b::DEF_STRING_SIZE);
compressor.reset();
decompressor.setParams(v42b::MIN_TOTAL_CODEWORDS, v42b::DEF_STRING_SIZE);
decompressor.reset();
}
void V42Protocol::ackIFrame(int seqNum)
{
// N(R) acknowledges the frames before it: no further than the last one sent
if (seqDistance(txSeqAck, seqNum) <= seqDistance(txSeqAck, txSeqNum))
{
for (unsigned seq = txSeqAck; seq != (unsigned)seqNum; seq = incMod128(seq))
sentSize[seq & 15] = 0;
txSeqAck = seqNum;
}
else {
WARN_LOG(MODEM, "Ack seq# %d outside of the frames sent %d-%d", seqNum, txSeqAck, txSeqNum);
}
}
void V42Protocol::serialize(void **data, unsigned int *total_size) const
{
u8 b[11] = {
(u8)phase, lastRx, (u8)odpCount, txSeqNum, rxSeqNum, txSeqAck, (u8)receiverReady,
(u8)txWindow, resendSeq, (u8)resending, (u8)compressionEnabled
};
u16 maxSize = (u16)txMaxSize;
LIBRETRO_SA(b, 11);
LIBRETRO_S(maxSize);
LIBRETRO_S(detectionStart);
LIBRETRO_S(inactivityStart);
LIBRETRO_SA(&sentSize[0], 16);
LIBRETRO_SA(&sentFrames[0][0], sizeof(sentFrames));
v14Encoder.serialize(data, total_size);
v14Decoder.serialize(data, total_size);
hdlcEncoder.serialize(data, total_size);
hdlcDecoder.serialize(data, total_size);
compressor.serialize(data, total_size);
decompressor.serialize(data, total_size);
}
void V42Protocol::unserialize(void **data, unsigned int *total_size)
{
u8 b[11] = { 0 };
u16 maxSize = 128;
LIBRETRO_USA(b, 11);
LIBRETRO_US(maxSize);
LIBRETRO_US(detectionStart);
LIBRETRO_US(inactivityStart);
LIBRETRO_USA(&sentSize[0], 16);
LIBRETRO_USA(&sentFrames[0][0], sizeof(sentFrames));
v14Encoder.unserialize(data, total_size);
v14Decoder.unserialize(data, total_size);
hdlcEncoder.unserialize(data, total_size);
hdlcDecoder.unserialize(data, total_size);
compressor.unserialize(data, total_size);
decompressor.unserialize(data, total_size);
phase = b[0] <= V14 ? (Phase)b[0] : None;
lastRx = b[1];
odpCount = b[2] & 3;
txSeqNum = b[3] & 127;
rxSeqNum = b[4] & 127;
txSeqAck = b[5] & 127;
receiverReady = b[6] != 0;
txWindow = b[7] >= 1 && b[7] <= 15 ? b[7] : 15;
resendSeq = b[8] & 127;
resending = b[9] != 0;
compressionEnabled = b[10] != 0;
txMaxSize = maxSize >= 1 && maxSize <= V42_MAX_INFO ? maxSize : 128;
if ((int)seqDistance(txSeqAck, txSeqNum) > txWindow)
txSeqAck = txSeqNum;
for (int i = 0; i < 16; i++)
if (sentSize[i] > 3 + V42_MAX_INFO)
sentSize[i] = 0;
}
u8 V8bisProtocol::detectTone()
{
switch (toneState)
{
case 0:
if (toneTime == 0) {
toneTime = now();
}
else if (now() - toneTime >= SH4_MAIN_CLOCK / 1000 * 400) {
toneState++;
toneTime = now();
}
break;
case 1: // Segment 1 dual tone received and single tone segment 2 is being detected
if (now() - toneTime >= SH4_MAIN_CLOCK / 1000 * 100) {
toneState++;
toneTime = now();
}
return 0xE0;
case 2:
return 0x20; // Mode Request (MRe) received
}
return 0;
}
void V8bisProtocol::sendCL()
{
dataMode = true;
static const u8 cl[] = {
0x12, // CL, rev.1
//0x13, // CLR, rev.1
// identification field (I)
0xc3, // NPar1: V.8, short V.8, non-standard field
0x80, // SPar1: network type not specified
// standard information field (S)
0x80, // NPar1
0x01, // SPar2[1]: data
0x80, // SPar2[2]
0x0F, // NPar2[1]: transparent data, V.42, V.42 bis, V.14
0x30, // NPar2[2]: V.34, V.32 bis
0xFF, // NPar2[3]: V.32, V.22 bis, V.22, V.21, V.90 digital+ana
//0xC0, // V.8 bis rev.2: NPar2[4]
// non-standard information (NS)
0x09, // size
0xb5, // country code (USA)
0x02, // manuf. code len
0x00, // manufacturer code (K56flex)
0x94,
0x81, // licensee code (Rockwell)
0x83, // capabilities (K56flex, V90?)
0x43, // K56flex version number
0x47, // Rockwell MDP version number
0xC4, // u-law (none) and controller version number (4)
};
encoder.sendFlag();
encoder.sendFrame(cl, sizeof(cl));
encoder.sendFlag();
encoder.sendFlag();
}
void V8bisProtocol::sendAck(int n)
{
const u8 ack = u8(0x13 + n); // 0x14: ACK(1), 0x15: ACK(2)
encoder.sendFlag();
encoder.sendFrame(&ack, 1);
encoder.sendFlag();
encoder.sendFlag();
}
void V8bisProtocol::sendNak(int n)
{
const u8 nak = u8(7 + n); // 8: NAK(1), 9: NAK(2), ...
encoder.sendFlag();
encoder.sendFrame(&nak, 1);
encoder.sendFlag();
encoder.sendFlag();
}
void V8bisProtocol::emitTone(u8 tone)
{
if (toneState == 2 && tone == 0x33) // emit Capabilities Request (CRd)
// should send CL or CLR
sendCL();
}
int V8bisProtocol::read()
{
if (!dataMode)
return -1;
else
return encoder.read();
}
int V8bisProtocol::available()
{
if (!dataMode)
return 0;
else
return encoder.available();
}
/* The fields of a V.8 bis message are runs of octets, the last of which has
* its top bit set (the delimiter): the length of the one at @p, delimiter
* included, as far as @end */
static unsigned paramSize(const u8 *p, const u8 *end, unsigned delimMask = 0x80)
{
const u8 *begin = p;
while (p < end && (*p & delimMask) == 0)
p++;
return p - begin + 1;
}
void V8bisProtocol::handleMS(const u8 *frame, unsigned size)
{
const u8 *end = frame + size;
// the octet at @p, 0 past the end of the message
#define AT(p) ((p) < end ? *(p) : 0)
// identification field (I)
// NPar1
const u8 *p = &frame[1];
const bool v8 = AT(p) & 1;
const bool shortV8 = AT(p) & 2;
const bool txAck = AT(p) & 8;
const bool hasNSF = AT(p) & 0x40;
unsigned sz = paramSize(p, end);
p += sz;
sz = paramSize(p, end);
if (AT(p) & 0x7f) {
p += sz;
p += paramSize(p, end); // NPar2's
}
else {
p += sz;
}
// standard info field (S)
p += paramSize(p, end); // NPar1
sz = paramSize(p, end);
const u8 spar1 = AT(p);
if ((spar1 & 1) == 0)
{
WARN_LOG(MODEM, "V.8 bis MS: Data bit not set in field S - SPar1");
sendNak();
return;
}
p += sz;
const bool v42 = AT(p) & 2;
const bool v42bis = AT(p) & 4;
const bool v14 = AT(p) & 8;
int speed = 0;
if ((AT(p) & 0xc0) == 0)
{
++p;
if (AT(p) & 0x10)
// V34
speed = 33600;
else if (AT(p) & 0x20)
// V32 bis
speed = 14400;
if (speed == 0 && (AT(p) & 0xc0) == 0)
{
++p;
if (AT(p) & 1)
// V32
speed = 9600;
else if (AT(p) & 2)
// V22 bis
speed = 2400;
else if (AT(p) & 4)
// V22
speed = 1200;
else if (AT(p) & 8)
// V21
speed = 300;
}
}
p += paramSize(p, end);
if (hasNSF && AT(p) >= 5 && AT(p + 2) == 2)
{
if (AT(p + 3) == 0 && AT(p + 4) == 0x94)
// K56flex / V90
speed = 56000;
}
#undef AT
if (txAck)
sendAck(1);
DEBUG_LOG(MODEM, "V8bis: received MS: V8 %d sV8 %d V14 %d V42 %d V42bis %d speed %d",
v8, shortV8, v14, v42, v42bis, speed);
}
void V8bisProtocol::write(u8 v)
{
if (!dataMode)
return;
decoder.write(v);
if (!decoder.frameAvailable())
return;
decoder.frameTaken();
const u8 *frame = decoder.frame();
const unsigned size = decoder.frameSize();
const u8 frameType = frame[0] & 0xf;
switch (frameType)
{
case 1: // MS
handleMS(frame, size);
break;
default:
WARN_LOG(MODEM, "Unhandled V.8 bis frame type %d", frameType);
break;
}
done = true;
}
void V8bisProtocol::reset()
{
encoder.reset();
decoder.reset();
dataMode = false;
toneState = 0;
toneTime = 0;
done = false;
}
void V8bisProtocol::serialize(void **data, unsigned int *total_size) const
{
u8 b[3] = { (u8)toneState, (u8)dataMode, (u8)done };
encoder.serialize(data, total_size);
decoder.serialize(data, total_size);
LIBRETRO_SA(b, 3);
LIBRETRO_S(toneTime);
}
void V8bisProtocol::unserialize(void **data, unsigned int *total_size)
{
u8 b[3] = { 0 };
encoder.unserialize(data, total_size);
decoder.unserialize(data, total_size);
LIBRETRO_USA(b, 3);
LIBRETRO_US(toneTime);
toneState = b[0] <= 2 ? b[0] : 0;
dataMode = b[1] != 0;
done = b[2] != 0;
}
} // namespace modem