From 2cce91e94fb8deab6e12ca6acdc77665dffc30ff Mon Sep 17 00:00:00 2001 From: Jonathan Naylor Date: Sun, 28 Feb 2016 20:34:37 +0000 Subject: [PATCH] Beginnings of System Fusion payload regeneration. --- MMDVMHost.vcxproj | 2 + MMDVMHost.vcxproj.filters | 6 + Makefile | 3 +- YSFControl.cpp | 58 +++++++- YSFControl.h | 2 + YSFConvolution.cpp | 11 +- YSFConvolution.h | 2 +- YSFDefines.h | 2 + YSFFICH.cpp | 58 +++++++- YSFFICH.h | 11 +- YSFPayload.cpp | 306 ++++++++++++++++++++++++++++++++++++++ YSFPayload.h | 40 +++++ 12 files changed, 482 insertions(+), 19 deletions(-) create mode 100644 YSFPayload.cpp create mode 100644 YSFPayload.h diff --git a/MMDVMHost.vcxproj b/MMDVMHost.vcxproj index 87f753f..4dbaa5d 100644 --- a/MMDVMHost.vcxproj +++ b/MMDVMHost.vcxproj @@ -194,6 +194,7 @@ + @@ -234,6 +235,7 @@ + diff --git a/MMDVMHost.vcxproj.filters b/MMDVMHost.vcxproj.filters index fdd11f7..2ae5e9f 100644 --- a/MMDVMHost.vcxproj.filters +++ b/MMDVMHost.vcxproj.filters @@ -155,6 +155,9 @@ Header Files + + Header Files + @@ -283,5 +286,8 @@ Source Files + + Source Files + \ No newline at end of file diff --git a/Makefile b/Makefile index 8596e72..545a998 100644 --- a/Makefile +++ b/Makefile @@ -7,7 +7,8 @@ LDFLAGS = -g OBJECTS = \ AMBEFEC.o BPTC19696.o Conf.o CRC.o Display.o DMRControl.o DMRCSBK.o DMRData.o DMRDataHeader.o DMREMB.o DMREmbeddedLC.o DMRFullLC.o DMRIPSC.o DMRLC.o DMRShortLC.o \ DMRSlot.o DMRSlotType.o DStarControl.o DStarHeader.o DStarNetwork.o DStarSlowData.o Golay2087.o Golay24128.o Hamming.o Log.o MMDVMHost.o Modem.o NullDisplay.o \ - QR1676.o RS129.o SerialController.o SHA256.o StopWatch.o Sync.o TFTSerial.o Timer.o UDPSocket.o Utils.o YSFControl.o YSFConvolution.o YSFFICH.o YSFParrot.o + QR1676.o RS129.o SerialController.o SHA256.o StopWatch.o Sync.o TFTSerial.o Timer.o UDPSocket.o Utils.o YSFControl.o YSFConvolution.o YSFFICH.o YSFParrot.o \ + YSFPayload.o all: MMDVMHost diff --git a/YSFControl.cpp b/YSFControl.cpp index 2ffce75..7fdd208 100644 --- a/YSFControl.cpp +++ b/YSFControl.cpp @@ -12,7 +12,7 @@ */ #include "YSFControl.h" -#include "YSFFICH.h" +#include "YSFPayload.h" #include "Utils.h" #include "Sync.h" #include "Log.h" @@ -36,6 +36,7 @@ m_queue(1000U, "YSF Control"), m_state(RS_RF_LISTENING), m_timeoutTimer(1000U, timeout), m_frames(0U), +m_fich(), m_parrot(NULL), m_fp(NULL) { @@ -91,6 +92,17 @@ bool CYSFControl::writeModem(unsigned char *data) CYSFFICH fich; fich.decode(data + 2U); + unsigned char fi = fich.getFI(); + unsigned char fn = fich.getFN(); + unsigned char ft = fich.getFT(); + unsigned char dt = fich.getDT(); + + LogMessage("YSF, EOT, FI=%X BN=%u BT=%u FN=%u FT=%u DT=%X", fich.getFI(), fich.getBN(), fich.getBT(), fich.getFN(), fich.getFT(), fich.getDT()); + + CYSFPayload payload; + payload.decode(data + 2U, fi, fn, ft, dt); + payload.encode(data + 2U); + m_frames++; if (m_duplex) { @@ -122,14 +134,46 @@ bool CYSFControl::writeModem(unsigned char *data) } else { CSync::addYSFSync(data + 2U); - CYSFFICH fich; - fich.decode(data + 2U); + if (valid) { + bool ret = m_fich.decode(data + 2U); + assert(ret); + + LogMessage("YSF, Valid FICH, FI=%X BN=%u BT=%u FN=%u FT=%u DT=%X", m_fich.getFI(), m_fich.getBN(), m_fich.getBT(), m_fich.getFN(), m_fich.getFT(), m_fich.getDT()); + } else { + LogMessage("YSF, invalid FICH"); + + // Reconstruct FICH based on the last valid frame + m_fich.setFI(0x01U); // Communication channel + + unsigned char fn = m_fich.getFN(); + unsigned char ft = m_fich.getFT(); + + fn++; + if (fn >= 8U) { + fn = 0U; + ft++; + if (ft >= 8U) + ft = 0U; + } + + m_fich.setFN(fn); + m_fich.setFT(ft); + } + + unsigned char fi = m_fich.getFI(); + unsigned char fn = m_fich.getFN(); + unsigned char ft = m_fich.getFT(); + unsigned char dt = m_fich.getDT(); + + CYSFPayload payload; + payload.decode(data + 2U, fi, fn, ft, dt); + // payload.encode(data + 2U); m_frames++; if (m_duplex) { - fich.setMR(YSF_MR_BUSY); - fich.encode(data + 2U); + m_fich.setMR(YSF_MR_BUSY); + m_fich.encode(data + 2U); data[0U] = TAG_DATA; data[1U] = 0x00U; @@ -137,8 +181,8 @@ bool CYSFControl::writeModem(unsigned char *data) } if (m_parrot != NULL) { - fich.setMR(YSF_MR_NOT_BUSY); - fich.encode(data + 2U); + m_fich.setMR(YSF_MR_NOT_BUSY); + m_fich.encode(data + 2U); data[0U] = TAG_DATA; data[1U] = 0x00U; diff --git a/YSFControl.h b/YSFControl.h index 0deb410..97e8a7a 100644 --- a/YSFControl.h +++ b/YSFControl.h @@ -24,6 +24,7 @@ #include "YSFParrot.h" #include "Display.h" #include "Defines.h" +#include "YSFFICH.h" #include "Timer.h" #include "Modem.h" @@ -47,6 +48,7 @@ private: RPT_RF_STATE m_state; CTimer m_timeoutTimer; unsigned int m_frames; + CYSFFICH m_fich; CYSFParrot* m_parrot; FILE* m_fp; diff --git a/YSFConvolution.cpp b/YSFConvolution.cpp index 9d62ba8..fea12e1 100644 --- a/YSFConvolution.cpp +++ b/YSFConvolution.cpp @@ -40,7 +40,7 @@ m_dp(NULL) { m_metrics1 = new uint16_t[16U]; m_metrics2 = new uint16_t[16U]; - m_decisions = new uint64_t[360U]; + m_decisions = new uint64_t[180U]; } CYSFConvolution::~CYSFConvolution() @@ -89,26 +89,27 @@ void CYSFConvolution::decode(uint8_t s0, uint8_t s1) ++m_dp; + assert((m_dp - m_decisions) <= 180U); + uint16_t* tmp = m_oldMetrics; m_oldMetrics = m_newMetrics; m_newMetrics = tmp; } -void CYSFConvolution::chainback(unsigned char* out) +void CYSFConvolution::chainback(unsigned char* out, unsigned int nBits) { assert(out != NULL); uint32_t state = 0U; - uint8_t nbits = 96U; - while (nbits-- > 0) { + while (nBits-- > 0) { --m_dp; uint32_t i = state >> (9 - K); uint8_t bit = uint8_t(*m_dp >> i) & 1; state = (bit << 7) | (state >> 1); - WRITE_BIT1(out, nbits, bit != 0U); + WRITE_BIT1(out, nBits, bit != 0U); } } diff --git a/YSFConvolution.h b/YSFConvolution.h index 945257e..3fc4956 100644 --- a/YSFConvolution.h +++ b/YSFConvolution.h @@ -30,7 +30,7 @@ public: void start(); void decode(uint8_t s0, uint8_t s1); - void chainback(unsigned char* out); + void chainback(unsigned char* out, unsigned int nBits); void encode(const unsigned char* in, unsigned char* out, unsigned int nBits) const; diff --git a/YSFDefines.h b/YSFDefines.h index ea45778..41414fd 100644 --- a/YSFDefines.h +++ b/YSFDefines.h @@ -24,6 +24,8 @@ const unsigned int YSF_FRAME_LENGTH_BYTES = 120U; const unsigned char YSF_SYNC_BYTES[] = {0xD4U, 0x71U, 0xC9U, 0x63U, 0x4DU}; const unsigned int YSF_SYNC_LENGTH_BYTES = 5U; +const unsigned int YSF_FICH_LENGTH_BYTES = 25U; + const unsigned char YSF_FI_MASK = 0xC0U; const unsigned char YSF_DT_MASK = 0x30U; diff --git a/YSFFICH.cpp b/YSFFICH.cpp index 474ff25..17a03dd 100644 --- a/YSFFICH.cpp +++ b/YSFFICH.cpp @@ -87,7 +87,7 @@ bool CYSFFICH::decode(const unsigned char* bytes) } unsigned char output[13U]; - viterbi.chainback(output); + viterbi.chainback(output, 96U); unsigned int b0 = CGolay24128::decode24128(output + 0U); unsigned int b1 = CGolay24128::decode24128(output + 3U); @@ -159,9 +159,29 @@ void CYSFFICH::encode(unsigned char* bytes) } } +unsigned char CYSFFICH::getFI() const +{ + return (m_fich[0U] >> 6) & 0x03U; +} + unsigned char CYSFFICH::getCM() const { - return m_fich[0U] & 0x0CU; + return (m_fich[0U] >> 2) & 0x03U; +} + +unsigned char CYSFFICH::getBN() const +{ + return m_fich[0U] & 0x03U; +} + +unsigned char CYSFFICH::getBT() const +{ + return (m_fich[1U] >> 6) & 0x03U; +} + +unsigned char CYSFFICH::getFN() const +{ + return (m_fich[1U] >> 3) & 0x07U; } unsigned char CYSFFICH::getFT() const @@ -169,9 +189,39 @@ unsigned char CYSFFICH::getFT() const return m_fich[1U] & 0x07U; } -unsigned char CYSFFICH::getFN() const +unsigned char CYSFFICH::getDT() const { - return (m_fich[1U] & 0x38U) >> 3; + return m_fich[2U] & 0x03U; +} + +void CYSFFICH::setFI(unsigned char fi) +{ + m_fich[0U] &= 0x3FU; + m_fich[0U] |= (fi << 6) & 0xC0U; +} + +void CYSFFICH::setBN(unsigned char bn) +{ + m_fich[0U] &= 0xFCU; + m_fich[0U] |= bn; +} + +void CYSFFICH::setBT(unsigned char bt) +{ + m_fich[1U] &= 0x3FU; + m_fich[1U] |= (bt << 6) & 0xC0U; +} + +void CYSFFICH::setFN(unsigned char fn) +{ + m_fich[1U] &= 0xE7U; + m_fich[1U] |= (fn << 3) & 0x1CU; +} + +void CYSFFICH::setFT(unsigned char ft) +{ + m_fich[1U] &= 0xF8U; + m_fich[1U] |= ft; } void CYSFFICH::setMR(unsigned char mr) diff --git a/YSFFICH.h b/YSFFICH.h index 2471ef1..8804a9e 100644 --- a/YSFFICH.h +++ b/YSFFICH.h @@ -28,10 +28,19 @@ public: void encode(unsigned char* bytes); + unsigned char getFI() const; unsigned char getCM() const; - unsigned char getFT() const; + unsigned char getBN() const; + unsigned char getBT() const; unsigned char getFN() const; + unsigned char getFT() const; + unsigned char getDT() const; + void setFI(unsigned char fi); + void setBN(unsigned char bn); + void setBT(unsigned char bt); + void setFN(unsigned char fn); + void setFT(unsigned char ft); void setMR(unsigned char mr); void setVoIP(bool set); diff --git a/YSFPayload.cpp b/YSFPayload.cpp new file mode 100644 index 0000000..27120f9 --- /dev/null +++ b/YSFPayload.cpp @@ -0,0 +1,306 @@ +/* +* Copyright (C) 2016 Jonathan Naylor, G4KLX +* +* This program 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; version 2 of the License. +* +* This program 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. +*/ + +#include "YSFConvolution.h" +#include "YSFPayload.h" +#include "YSFDefines.h" +#include "Utils.h" +#include "CRC.h" +#include "Log.h" + +#include +#include +#include + +const unsigned int INTERLEAVE_TABLE_9_20[] = { + 0U, 40U, 80U, 120U, 160U, 200U, 240U, 280U, 320U, + 2U, 42U, 82U, 122U, 162U, 202U, 242U, 282U, 322U, + 4U, 44U, 84U, 124U, 164U, 204U, 244U, 284U, 324U, + 6U, 46U, 86U, 126U, 166U, 206U, 246U, 286U, 326U, + 8U, 48U, 88U, 128U, 168U, 208U, 248U, 288U, 328U, + 10U, 50U, 90U, 130U, 170U, 210U, 250U, 290U, 330U, + 12U, 52U, 92U, 132U, 172U, 212U, 252U, 292U, 332U, + 14U, 54U, 94U, 134U, 174U, 214U, 254U, 294U, 334U, + 16U, 56U, 96U, 136U, 176U, 216U, 256U, 296U, 336U, + 18U, 58U, 98U, 138U, 178U, 218U, 258U, 298U, 338U, + 20U, 60U, 100U, 140U, 180U, 220U, 260U, 300U, 340U, + 22U, 62U, 102U, 142U, 182U, 222U, 262U, 302U, 342U, + 24U, 64U, 104U, 144U, 184U, 224U, 264U, 304U, 344U, + 26U, 66U, 106U, 146U, 186U, 226U, 266U, 306U, 346U, + 28U, 68U, 108U, 148U, 188U, 228U, 268U, 308U, 348U, + 30U, 70U, 110U, 150U, 190U, 230U, 270U, 310U, 350U, + 32U, 72U, 112U, 152U, 192U, 232U, 272U, 312U, 352U, + 34U, 74U, 114U, 154U, 194U, 234U, 274U, 314U, 354U, + 36U, 76U, 116U, 156U, 196U, 236U, 276U, 316U, 356U, + 38U, 78U, 118U, 158U, 198U, 238U, 278U, 318U, 358U}; + +const unsigned int INTERLEAVE_TABLE_5_20[] = { + 0U, 40U, 80U, 120U, 160U, + 2U, 42U, 82U, 122U, 162U, + 4U, 44U, 84U, 124U, 164U, + 6U, 46U, 86U, 126U, 166U, + 8U, 48U, 88U, 128U, 168U, + 10U, 50U, 90U, 130U, 170U, + 12U, 52U, 92U, 132U, 172U, + 14U, 54U, 94U, 134U, 174U, + 16U, 56U, 96U, 136U, 176U, + 18U, 58U, 98U, 138U, 178U, + 20U, 60U, 100U, 140U, 180U, + 22U, 62U, 102U, 142U, 182U, + 24U, 64U, 104U, 144U, 184U, + 26U, 66U, 106U, 146U, 186U, + 28U, 68U, 108U, 148U, 188U, + 30U, 70U, 110U, 150U, 190U, + 32U, 72U, 112U, 152U, 192U, + 34U, 74U, 114U, 154U, 194U, + 36U, 76U, 116U, 156U, 196U, + 38U, 78U, 118U, 158U, 198U}; + +const unsigned char BIT_MASK_TABLE[] = {0x80U, 0x40U, 0x20U, 0x10U, 0x08U, 0x04U, 0x02U, 0x01U}; + +#define WRITE_BIT1(p,i,b) p[(i)>>3] = (b) ? (p[(i)>>3] | BIT_MASK_TABLE[(i)&7]) : (p[(i)>>3] & ~BIT_MASK_TABLE[(i)&7]) +#define READ_BIT1(p,i) (p[(i)>>3] & BIT_MASK_TABLE[(i)&7]) + +CYSFPayload::CYSFPayload() : +m_data(NULL) +{ + m_data = new unsigned char[90U]; +} + +CYSFPayload::~CYSFPayload() +{ + delete[] m_data; +} + +bool CYSFPayload::decode(const unsigned char* bytes, unsigned char fi, unsigned char fn, unsigned char ft, unsigned char dt) +{ + assert(bytes != NULL); + + ::memcpy(m_data, bytes + YSF_SYNC_LENGTH_BYTES + YSF_FICH_LENGTH_BYTES, 90U); + + // Header and trailer + if (fi == 0U || fi == 2U) + return decodeHeader(); + + // V/D Mode 1 + if (dt == 0U) + return decodeVDMode1(fn, ft); + + // V/D Mode 2 + if (dt == 2U) + return decodeVDMode2(fn, ft); + + // Data FR Mode + if (dt == 1U) + return decodeDataFRMode(fn, ft); + + // Voice FR Mode + return true; +} + +void CYSFPayload::encode(unsigned char* bytes) +{ + assert(bytes != NULL); + + ::memcpy(bytes + YSF_SYNC_LENGTH_BYTES + YSF_FICH_LENGTH_BYTES, m_data, 90U); +} + +bool CYSFPayload::decodeHeader() +{ + unsigned char dch1[45U]; + unsigned char dch2[45U]; + + unsigned char* p1 = m_data; + unsigned char* p2 = dch1; + unsigned char* p3 = dch2; + for (unsigned int i = 0U; i < 5U; i++) { + ::memcpy(p2, p1, 9U); + p1 += 9U; p2 += 9U; + ::memcpy(p3, p1, 9U); + p1 += 9U; p3 += 9U; + } + + CYSFConvolution conv; + conv.start(); + + // Deinterleave the FICH and send bits to the Viterbi decoder + for (unsigned int i = 0U; i < 180U; i++) { + unsigned int n = INTERLEAVE_TABLE_9_20[i]; + uint8_t s0 = READ_BIT1(dch1, n) ? 1U : 0U; + + n++; + uint8_t s1 = READ_BIT1(dch1, n) ? 1U : 0U; + + conv.decode(s0, s1); + } + + unsigned char output1[23U]; + conv.chainback(output1, 176U); + + bool ret1 = CCRC::checkCCITT162(output1, 22U); + if (ret1) + CUtils::dump("Header/Trailer, valid DCH1", output1, 22U); + + conv.start(); + + // Deinterleave the FICH and send bits to the Viterbi decoder + for (unsigned int i = 0U; i < 180U; i++) { + unsigned int n = INTERLEAVE_TABLE_9_20[i]; + uint8_t s0 = READ_BIT1(dch2, n) ? 1U : 0U; + + n++; + uint8_t s1 = READ_BIT1(dch2, n) ? 1U : 0U; + + conv.decode(s0, s1); + } + + unsigned char output2[23U]; + conv.chainback(output2, 176U); + + + bool ret2 = CCRC::checkCCITT162(output2, 22U); + if (ret2) + CUtils::dump("Header/Trailer, valid DCH2", output2, 22U); + + return true; +} + +bool CYSFPayload::decodeVDMode1(unsigned char fn, unsigned char ft) +{ + unsigned char dch[45U]; + + unsigned char* p1 = m_data; + unsigned char* p2 = dch; + for (unsigned int i = 0U; i < 5U; i++) { + ::memcpy(p2, p1, 9U); + p1 += 18U; p2 += 9U; + } + + CYSFConvolution conv; + conv.start(); + + // Deinterleave the FICH and send bits to the Viterbi decoder + for (unsigned int i = 0U; i < 180U; i++) { + unsigned int n = INTERLEAVE_TABLE_9_20[i]; + uint8_t s0 = READ_BIT1(dch, n) ? 1U : 0U; + + n++; + uint8_t s1 = READ_BIT1(dch, n) ? 1U : 0U; + + conv.decode(s0, s1); + } + + unsigned char output[23U]; + conv.chainback(output, 176U); + + bool ret = CCRC::checkCCITT162(output, 22U); + if (ret) + CUtils::dump("V/D Mode 1, valid DCH", output, 22U); + + return true; +} + +bool CYSFPayload::decodeVDMode2(unsigned char fn, unsigned char ft) +{ + unsigned char dch[25U]; + + unsigned char* p1 = m_data; + unsigned char* p2 = dch; + for (unsigned int i = 0U; i < 5U; i++) { + ::memcpy(p2, p1, 5U); + p1 += 18U; p2 += 5U; + } + + CYSFConvolution conv; + conv.start(); + + // Deinterleave the FICH and send bits to the Viterbi decoder + for (unsigned int i = 0U; i < 100U; i++) { + unsigned int n = INTERLEAVE_TABLE_5_20[i]; + uint8_t s0 = READ_BIT1(dch, n) ? 1U : 0U; + + n++; + uint8_t s1 = READ_BIT1(dch, n) ? 1U : 0U; + + conv.decode(s0, s1); + } + + unsigned char output[13U]; + conv.chainback(output, 96U); + + bool ret = CCRC::checkCCITT162(output, 12U); + if (ret) + CUtils::dump("V/D Mode 2, valid DCH", output, 12U); + + return true; +} + +bool CYSFPayload::decodeDataFRMode(unsigned char fn, unsigned char ft) +{ + unsigned char dch1[45U]; + unsigned char dch2[45U]; + + unsigned char* p1 = m_data; + unsigned char* p2 = dch1; + unsigned char* p3 = dch2; + for (unsigned int i = 0U; i < 5U; i++) { + ::memcpy(p2, p1, 9U); + p1 += 9U; p2 += 9U; + ::memcpy(p3, p1, 9U); + p1 += 9U; p3 += 9U; + } + + CYSFConvolution conv; + conv.start(); + + // Deinterleave the FICH and send bits to the Viterbi decoder + for (unsigned int i = 0U; i < 180U; i++) { + unsigned int n = INTERLEAVE_TABLE_9_20[i]; + uint8_t s0 = READ_BIT1(dch1, n) ? 1U : 0U; + + n++; + uint8_t s1 = READ_BIT1(dch1, n) ? 1U : 0U; + + conv.decode(s0, s1); + } + + unsigned char output1[23U]; + conv.chainback(output1, 176U); + + bool ret1 = CCRC::checkCCITT162(output1, 22U); + if (ret1) + CUtils::dump("Data FR Mode, valid DCH1", output1, 22U); + + conv.start(); + + // Deinterleave the FICH and send bits to the Viterbi decoder + for (unsigned int i = 0U; i < 180U; i++) { + unsigned int n = INTERLEAVE_TABLE_9_20[i]; + uint8_t s0 = READ_BIT1(dch2, n) ? 1U : 0U; + + n++; + uint8_t s1 = READ_BIT1(dch2, n) ? 1U : 0U; + + conv.decode(s0, s1); + } + + unsigned char output2[23U]; + conv.chainback(output2, 176U); + + + bool ret2 = CCRC::checkCCITT162(output2, 22U); + if (ret2) + CUtils::dump("Data FR Mode, valid DCH2", output2, 22U); + + return true; +} diff --git a/YSFPayload.h b/YSFPayload.h new file mode 100644 index 0000000..282351c --- /dev/null +++ b/YSFPayload.h @@ -0,0 +1,40 @@ +/* +* Copyright (C) 2016 by Jonathan Naylor G4KLX +* +* This program 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. +* +* This program 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 this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#if !defined(YSFPayload_H) +#define YSFPayload_H + +class CYSFPayload { +public: + CYSFPayload(); + ~CYSFPayload(); + + bool decode(const unsigned char* bytes, unsigned char fi, unsigned char fn, unsigned char ft, unsigned char dt); + + void encode(unsigned char* bytes); + +private: + unsigned char* m_data; + + bool decodeHeader(); + bool decodeVDMode1(unsigned char fn, unsigned char ft); + bool decodeVDMode2(unsigned char fn, unsigned char ft); + bool decodeDataFRMode(unsigned char fn, unsigned char ft); +}; + +#endif