OpenBTS-UMTS/SGSNGGSN/LLC.cpp

750 lines
22 KiB
C++

/*
* OpenBTS provides an open source alternative to legacy telco protocols and
* traditionally complex, proprietary hardware systems.
*
* Copyright 2011, 2014 Range Networks, Inc.
*
* This software is distributed under the terms of the GNU Affero General
* Public License version 3. See the COPYING and NOTICE files in the main
* directory for licensing information.
*
* This use of this software may be subject to additional restrictions.
* See the LEGAL file in the main directory for details.
*/
#define LLC_IMPLEMENTATION 1
#include "GPRSL3Messages.h"
#include "Sgsn.h"
#include "Ggsn.h"
#include "LLC.h"
#define CASENAME(x) case x: return #x;
namespace SGSN {
const char *LlcSapi::name(type sapi)
{
switch (sapi) {
CASENAME(GPRSMM)
CASENAME(TOM2)
CASENAME(UserData3)
CASENAME(UserData5)
CASENAME(SMS)
CASENAME(TOM8)
CASENAME(UserData9)
CASENAME(UserData11)
default: return "unrecognized LlcSapi";
}
}
const char *LLCFormat::name(type format)
{
switch (format) {
CASENAME(Invalid)
CASENAME(I)
CASENAME(S)
CASENAME(UI)
CASENAME(U)
CASENAME(ISack)
CASENAME(SSack)
default: return "unrecognized LLCFormat";
}
}
LLCFormat::type LlcFrame::getFormat()
{
if (size() < 2) { return LLCFormat::Invalid; }
unsigned tag = getControl(0);
if ((tag & 0x80) == 0) {
return (getControl(2) & 3) == 3 ? LLCFormat::ISack : LLCFormat::I;
} else if ((tag & 0x40) == 0) {
return (getControl(1) & 3) == 3 ? LLCFormat::SSack : LLCFormat::S;
} else if ((tag & 0x20) == 0) {
return LLCFormat::UI;
} else {
return LLCFormat::U;
}
}
// C++ note: You cannot just return an LlcMsg here, or the result is back-converted to LlcMsg.
// You must return a pointer, even though they are barely supported by C++. Gotta love it.
LlcMsg *LlcFrame::switchFrame()
{
switch (getFormat()) {
case LLCFormat::U:
return new LlcFrameU(*this);
case LLCFormat::UI: {
return new LlcFrameUI(*this);
//LlcFrameUI result = LlcFrameUI(*this);
//LLCDEBUG << "switchFrame result="<<result.typeName()<<"\n";
//return result;
}
case LLCFormat::ISack:
LLCDEBUG("LLC ISack frame");
return new LlcFrameSack(*this);
case LLCFormat::I:
return new LlcFrameI(*this);
case LLCFormat::SSack:
LLCDEBUG("LLC SSack frame");
return new LlcFrameSack(*this);
case LLCFormat::S:
return new LlcFrameS(*this);
default: assert(0);
}
}
void LlcFrame::llcProcess1(LlcEntity *lle)
{
switch (getFormat()) {
case LLCFormat::U:
LlcFrameU(*this).llcProcess(lle);
break;
case LLCFormat::UI: {
LlcFrameUI(*this).llcProcess(lle);
break;
}
case LLCFormat::ISack:
LLCDEBUG("LLC ISack frame");
LlcFrameSack(*this).llcProcess(lle);
break;
case LLCFormat::I:
LlcFrameI(*this).llcProcess(lle);
break;
case LLCFormat::SSack:
LLCDEBUG("LLC SSack frame");
LlcFrameSack(*this).llcProcess(lle);
break;
case LLCFormat::S:
LlcFrameS(*this).llcProcess(lle);
break;
default: assert(0);
}
}
// Dont bother with the stupid thing, just return it and hope it didnt screw us up.
// The multitech modem sends: 03FB/16.01.F4.2C/634CCA header/xids/checksum
// The xids are:
// XID xidtype=5 xidlen=2 value=500
// XID xidtype=11 xidlen=0 value=0
// I replied with: 43FB/16.01.F4.2C and with: 43FB and with 03FB
// but none worked for the multitech modem.
static void handleXid(LlcEntity *lle, ByteVector &xids)
{
try {
int totlen = xids.size(); // 3 to remove the FCS checksum.
// Create an outbound xid command
LlcFrameXid uframe(totlen+5); // Add room for 2 byte header, 3 byte FCS checksum.
uframe.setAppendP(0);
// 6.2.2: This is a downlink response, so the C/R bit is 0.
uframe.appendAddrHeader(lle->getLlcSapi(),false);
uframe.appendUHeader(LlcDefs::UCMD_XID,true); // Leaves append pointer at data.
// And I quote: "As an optimisation, parameters confirming the requested
// value smay be omitted from the XID response."
// So you can just return the xid header.
// However, the multitech modem did not accept that, but does succeed
// if you send the Full XID response string.
//if (gConfig.getNum("GPRS.XID.Full",1)) // There is no reason for this to be an option
if (1) {
for (int n = 0; n < totlen;) {
bool xl = xids.getBit2(n,0);
int xidtype = xids.getField2(n,1,5);
int xidlen = xl ? xids.getField2(n,6,8) : xids.getField2(n,6,2);
n += (xl ? 2 : 1);
unsigned value = 0;
if (xidlen <= 4) {
value = xids.getField2(n,0,8*xidlen);
uframe.appendXidItem(xidtype, xidlen,value);
LLCWARN("LLC XID"<<LOGVAR(xidtype)<<LOGVAR(xidlen)<<LOGVAR(value));
} else {
// The only xid item with length > 4 is the L3 params, just hope we dont get those.
LLCWARN("LLC ignoring over-length XID parameter:"
<<LOGVAR(xidtype)<<LOGVAR(xidlen)
<<LOGVAR2("bytes",xids.segment(n,xidlen).hexstr()));
}
n += xidlen;
}
}
// Send it.
LLCWARN("LLC Sending XID command:"<<uframe.hexstr());
lle->lleWriteRaw(uframe,"xid cmd");
} catch (ByteVectorError) {
LLCWARN("over-run error parsing LLC XID command");
}
}
void LlcFrameU::llcProcess(LlcEntity *lle)
{
// Note: The U frame exists to send a command via the S and M fields,
// which are defined in 04.64 6.4; See enum U_M_Commands
// Sec 8.5.4 says we should discard unrecognized if we are in TLL Assigned/ADM state.
// Sec 8.8.4 has a table that says the same.
// Sec 8.2 describes the P/F bit, says we should return a U command,
// so I tried returning DM, but it did not make the multitech modem work.
int cmd = getUM();
if (cmd == UCMD_XID) {
ByteVector xids = ByteVector(*this);
xids.trimLeft(2); // Chop off the U frame header.
LLCWARN("LLC XID frame received"<<LOGVAR2("size",xids.size())<<LOGVAR2("llcsapi",lle->getLlcSapi()));
handleXid(lle,xids);
} else {
const char *cmdname = "?";
switch (cmd) {
case UCMD_SABM: cmdname = "SABM"; break;
case UCMD_XID: cmdname = "XID"; break;
case UCMD_DM: cmdname = "DM"; break;
case UCMD_DISC: cmdname = "DISC"; break;
case UCMD_UA: cmdname = "UA"; break;
case UCMD_FRMR: cmdname = "FRMR"; break;
case UCMD_NULL: cmdname = "null"; break;
}
LLCWARN("LLC U frame ignored"<<LOGVAR(cmdname)<<LOGVAR2("PF",getUPF())<<LOGVAR2("M",getUM())<<LOGVAR2("llcsapi",lle->getLlcSapi()));
}
}
// The checksum has already been chopped off.
void LlcFrameUI::llcProcess(LlcEntity *lle)
{
// This is a data frame.
LLCDEBUG("UI::llcProcess");
ByteVector payload(tail(UIHeaderLength));
lle->lleUplinkData(payload);
}
void LlcFrameUI::writeUIHeader(unsigned wNU /*, bool pf*/)
{
bool wE = 0; // no encryption.
bool wPM = 1; // Checksum FCS is over everything.
setField2(controlOffset,0,0x18,5); // UI format tag and unused bits.
setField2(controlOffset,5,wNU,9); // frame number.
setField2(controlOffset+1,6,wE,1);
setField2(controlOffset+1,7,wPM,1);
}
void LlcEngine::allocSndcp(SgsnInfo *si, unsigned nsapi, unsigned llcsapi)
{
//LlcEntityUserData *userdatalle = si->mLlcEngine->getLlcEntityUserData(llcsapi);
LlcEntityUserData *userdatalle = getLlcEntityUserData(llcsapi);
new Sndcp(nsapi,llcsapi,userdatalle);
}
#if 0==SNDCP_IN_PDP
void LlcEngine::freeSndcp(unsigned nsapi)
{
Sndcp *sndcp = mSndcp[nsapi];
mSndcp[nsapi] = 0;
if (sndcp) delete sndcp;
// TODO: This is wrong - LlcEntity is by llc sapi, not nsapi.
// So I am just commenting it out.
// Must reset the LLC state machine also.
//LlcEntity *lle = getLlcEntity(nsapi);
//if (lle) {lle->reset();} // Better not be 'if'
}
#endif
void LlcEngine::llcWriteHighSide(ByteVector &sdu,int nsapi)
{
#if SNDCP_IN_PDP
PdpContext *pdp = mLleGmm.mSI->getPdp(nsapi);
if (!pdp) {
LLCWARN("llcWriteHighSide to unconfigured nsapi:"<<nsapi); // cant happen?
return;
}
Sndcp *sndcp = pdp->mSndcp1;
#else
Sndcp *sndcp = mSndcp[nsapi];
#endif
if (sndcp) {
sndcp->sndcpWriteHighSide(sdu);
} else {
assert(0); // not possible because Sndcp and PdpContext allocated/deallocated together.
}
}
void LlcEngine::llcWriteLowSide(ByteVector &bv,SgsnInfo *si)
{
if (bv.size() < 2) { return; }
LlcFrame lframe(bv);
int llcsapi = lframe.getSapi();
LLCDEBUG("llcWriteLowSide sapi="<<llcsapi);
LlcEntity *lle = getLlcEntity(llcsapi);
if (lle == 0) {
LLCWARN("LLC received PDU with unexpected SAPI="<<llcsapi);
// This is an "invalid frame" and shall be ignored without indication.
return;
}
// Chop off the parity.
// TODO: Check it.
lframe.trimRight(3);
lle->lleWriteLowSide(lframe);
}
//LlcEntity * SgsnInfo::getLlcEntity(unsigned llcSapi)
//{
// return mllcEngine->getLlcEntity(llcSapi);
//}
LlcEntityUserData * LlcEngine::getLlcEntityUserData(unsigned llcSapi)
{
return dynamic_cast<LlcEntityUserData*>(getLlcEntity(llcSapi));
}
LlcEntityGmm *LlcEngine::getLlcGmm()
{
return dynamic_cast<LlcEntityGmm*>(getLlcEntity(LlcSapi::GPRSMM));
}
void LlcEntity::lleWriteLowSide(LlcFrame &frame)
{
mVUR++;
frame.llcProcess1(this);
}
void LlcEntity::lleWriteRaw(ByteVector &frame, const char *descr)
{
gLlcParity.appendFCS(frame);
mSI->sgsnSend2MsHighSide(frame,descr,0);
//GPRS::DownlinkQPdu *dlpdu = new GPRS::DownlinkQPdu();
//dlpdu->mDlData = uiframe;
//LLCDEBUG("llewriteHighSide:"<<(ByteVector)frame);
//dlpdu->mDescr = std::string(descr);
//mSI->getMS()->msDownlinkQueue.write(dlpdu);
}
// Write a UI frame for unacknowledged information.
void LlcEntity::lleWriteHighSide(LlcDlFrame &frame, bool isCmd, const char *descr)
{
// Prepend the LLC header; the bv already has room allocated.
frame.growLeft(LlcFrame::UIHeaderLength);
frame.writeAddrHeader(getLlcSapi(),isCmd);
//LlcFrameUI uiframe(frame.begin());
LlcFrameUI uiframe(frame);
uiframe.writeUIHeader(mVU++);
lleWriteRaw(frame,descr);
}
void LlcEntityGmm::lleUplinkData(ByteVector &payload)
{
LLCDEBUG("LlcEntityGmm lleUplinkData");
// This is an l3 message.
handleL3Msg(mSI,payload);
}
// Warning: The MS can send uplink data before attaching or creating pdpcontext,
// for example, if the bts is rebooted.
void LlcEntityUserData::lleUplinkData(ByteVector &payload)
{
// okey dokey, this goes to the sndcp.
SndcpFrame sframe(payload);
unsigned nsapi = sframe.getNSapi();
// The NSAPI is pre-configured by an L3 PDP Context Activation message.
// If it does not exist, the MS and BTS are out of sync, possible after a crash,
// or invalid RA-Update.
Sndcp *sndcp = getSndcp(nsapi);
if (sndcp == 0) {
if (! mSI->isRegistered()) {
// The MS has not done an Attach.
// This happens if the BTS comes on and the MS was previously talking to us.
// 24.008 Annex G: We can send "ImplicitlyDeattached" and I quote:
// "This cause is sent ..., or if the GMM context data related
// to the subscription dose (sic) not exist in the SGSN e.g.
// because of a SGSN restart."
// Update: This does not appear to do the job on the Blackberry.
LLCINFO("received packet to detached MS on nsapi="<<nsapi<<" Sending Implicitly_Detached message "<<mSI);
sendImplicitlyDetached(mSI);
} else {
// This is a serious problem.
// We cant send a PdpDeactivateRequest message because the stupid thing
// is by TI [Transaction Identifier] instead of NSAPI, and we dont have one.
// Not sure what to do.
//Tried this anyway, did nothing:
// sendPdpDeactivate(getSgsnInfo(),nsapi,SmCause::Unknown_PDP_context);
LLCWARN("received packet to unconfigured nsapi="<<nsapi<<" "<<mSI);
}
return; // Thats the end of that.
}
sndcp->sndcpWriteLowSide(sframe);
}
//Sndcp *LlcEntityUserData::getSndcp(unsigned nsapi) { return getSgsnInfo()->mSndcp[nsapi]; }
//void LlcEntityUserData::setSndcp(unsigned nsapi, Sndcp*ptr) { getSgsnInfo()->mSndcp[nsapi] = ptr; }
#if SNDCP_IN_PDP
Sndcp *LlcEntityUserData::getSndcp(unsigned nsapi)
{
// The pdp will be NULL if the MS sends uplink data before allocating a PdpContext.
PdpContext *pdp = mSI->getPdp(nsapi);
return pdp ? pdp->mSndcp1 : NULL;
}
void LlcEntityUserData::setSndcp(unsigned nsapi, Sndcp*ptr) { mSI->getPdp(nsapi)->mSndcp1 = ptr; }
#else
Sndcp *LlcEntityUserData::getSndcp(unsigned nsapi) { return mSI->mLlcEngine->mSndcp[nsapi]; }
void LlcEntityUserData::setSndcp(unsigned nsapi, Sndcp*ptr) { mSI->mLlcEngine->mSndcp[nsapi] = ptr; }
#endif
// We dont know the length of S SACK format, so return the minimum length.
int LlcFrameDump::headerLength()
{
switch (mFormat) {
case LLCFormat::I: return 4;
case LLCFormat::S: return 3;
case LLCFormat::UI: return 3;
case LLCFormat::U: return 2;
case LLCFormat::ISack: return 5 + 1 + (mK+1+7)/8;
case LLCFormat::SSack: return 3; // Minimum length is probably 4, not 3.
case LLCFormat::Invalid: return -1;
default: assert(0);
}
}
void LlcFrameDump::llcParseDump()
{
mK = 0; // headerLength uses mK, so set to 0 for first test here.
switch (mFormat) {
case LLCFormat::U:
mPF = getBitR1(controlOffset,5);
mM = getByte(controlOffset) & 0xf;
break;
case LLCFormat::UI:
mNU = getFieldR1(controlOffset,3,9);
mE = getBitR1(controlOffset+1,2);
mPM = getBitR1(controlOffset+1,1);
break;
case LLCFormat::ISack:
mK = getByte(controlOffset+3) & 0x1f;
// Check again, now that we know the real mK
if ((int)size() < headerLength()) { mFormat = LLCFormat::Invalid; return; }
// Fall through
case LLCFormat::I:
mA = getBitR1(controlOffset,7);
mNS = getFieldR1(controlOffset,5,9);
mNR = getFieldR1(controlOffset+1,3,9);
mS = getByte(controlOffset+2) & 0x3;
break;
case LLCFormat::SSack:
case LLCFormat::S:
mA = getBitR1(controlOffset,6);
mNR = getFieldR1(controlOffset,3,9);
break;
default: assert(0);
}
}
void LlcFrameDump::textHeader(std::ostream &os)
{
os << "format=" <<LLCFormat::name(mFormat)
<<LOGVAR2("SAPI",getSapi()) <<LOGVAR2("LlcPD",getLlcPD()) <<LOGVAR2("CR",getCR());
switch (mFormat) {
case LLCFormat::I: case LLCFormat::ISack:
os <<LOGVAR(mNS) <<LOGVAR(mNR) << LOGVAR(mS);
break;
case LLCFormat::S: case LLCFormat::SSack:
os <<LOGVAR(mNR) <<LOGVAR(mA) << LOGVAR(mS);
break;
case LLCFormat::UI:
os <<LOGVAR(mNU) <<LOGVAR(mE) << LOGVAR(mPM);
break;
case LLCFormat::U:
os <<LOGVAR(mPF) <<LOGVAR(mM);
break;
default: break;
}
}
void LlcFrameDump::textContent(std::ostream &os,bool verbose)
{
if (mFormat == LLCFormat::S || mFormat == LLCFormat::SSack) {
return; // There is no data field.
}
// What the data is depends on the SAPI; could be an L3Message or user data.
int pos = headerLength();
switch (getSapi()) {
case LlcSapi::GPRSMM: {
// The contents are an L3 Message, prefixed by an LLC header
// and followed by the FCS checksum.
//ByteVector l3msg = segment(pos,size()-pos-3);
ByteVector payload = segment(pos,size()-pos-3);
os << " L3="<<L3GprsMsgType2Name(payload);
if (verbose) {
L3GprsFrame frame(payload);
frame.dump(os);
}
return;
}
case LlcSapi::UserData3:
case LlcSapi::UserData5:
case LlcSapi::UserData9:
case LlcSapi::UserData11:
// The contents are some user data.
os << " user PDU size=" << ((int)size() - pos);
break;
//SMS = 7
//TOM2 = 2,
//TOM8 = 8,
default:
os << "unrecognized SAPI="<<getSapi();
break;
}
}
void LlcFrameDump::text(std::ostream &os)
{
os << "LlcFrame:(";
textHeader(os);
textContent(os,true);
os << ")";
}
//bool Sndcp::isPdpInactive() { return mPdp==0 || mPdp->isPdpInactive(); }
unsigned Sndcp::getMaxPduSize() { return mlle->getMaxPduSize(); }
SgsnInfo *Sndcp::getSgsnInfo() { return mlle->mSI; }
// If we have all the segments for pdu num, send it off.
// If force, delete it even if incomplete.
void Sndcp::flush(unsigned num, bool force)
{
OneSdu *sp = &mSegs[num%sMemory];
unsigned i;
if (sp->mSegCount) { // We have received the final segment.
unsigned totsize = 0;
// Do we have all the segments yet?
for (i = 0; i < sp->mSegCount; i++) {
unsigned size = sp->segs[i].size();
if (size == 0) { break; } // failure.
totsize += size;
}
if (i == sp->mSegCount) { // success.
SNDCPDEBUG("flush"<<LOGVAR(num)<<LOGVAR(sp->mSegCount));
ByteVector result(totsize);
result.setAppendP(0);
for (i = 0; i < sp->mSegCount; i++) {
result.append(sp->segs[i]);
sp->segs[i].clear();
}
sp->mSegCount = 0;
//mPdp->pdpWriteLowSide(result);
getSgsnInfo()->sgsnSend2PdpLowSide(mNSapi,result);
//PdpContext *pdp = mlle->getSgsnInfo()->getPdp(mNSapi);
//assert(pdp);
//pdp->pdpWriteLowSide(result);
return;
}
SNDCPDEBUG("flush still pending"<<LOGVAR(num)<<LOGVAR(sp->mSegCount));
} else {
// Anything there at all? This is just for a message.
for (i = 0; i < 16; i++) {
if (sp->segs[i].size()) {
SNDCPDEBUG("flush still pending"<<LOGVAR(num)<<LOGVAR2("segment",i));
break;
}
}
}
if (force) {
// Delete all segments.
for (i = 0; i < 16; i++) {
sp->segs[i].clear();
}
sp->mSegCount = 0;
}
}
int Sndcp::diffSNS(int v1, int v2)
{
int diff = v1 - v2;
if (diff < (int)mSNS/2) diff += mSNS;
if (diff > (int)mSNS/2) diff -= mSNS;
return diff;
}
// uplink data from MS comes in here.
void Sndcp::sndcpWriteLowSide(SndcpFrame &frame)
{
// Todo: segment it.
unsigned segnum = frame.getSegmentNumber();
unsigned pdunum = frame.getPduNumber();
ByteVector payload(frame.getPayload());
SNDCPDEBUG("uplink packet"<<LOGVAR(pdunum)<<LOGVAR(segnum)<<LOGVAR2("size",payload.size())
<<" header="<<frame.head(MIN(20,frame.size())));
int diff = diffSNS(pdunum,mRecvNPdu);
if (diff >= 0) { // Is pdunum greater than or eql mRecvNPdu?
// If pdunum is totally off, dont move it?
if (diff < 16)
while (mRecvNPdu != pdunum) { // Advance mRecvNPdu
// flush does a % mSNS, and these are unsigned, so we can subtract without
// fear of the negative number botching it up.
flush((mRecvNPdu - sMemory)%mSNS,true);
mRecvNPdu = (mRecvNPdu + 1) % mSNS;
}
} else if (-diff >= (int)sMemory) {
// Too old to be in our window.
LLCWARN("SNDCP packet too old, discarded (number="<<pdunum<<",current="<<mRecvNPdu<<")");
return; // discard incoming frame.
}
// Save the pdu.
if (frame.getF()) { // first segment; flag marks that PCOMP/DCOMP byte is present.
if (segnum != 0) {
LOG(ERR) <<"invalid Sndcp pdu with F and seg number != 0";
segnum = 0; // Lets pretend.
}
}
mSegs[pdunum%sMemory].segs[segnum] = payload;
if (!frame.getM()) {
mSegs[pdunum%sMemory].mSegCount = segnum+1;
flush(pdunum,false);
}
}
// Send the pdu segment on its way.
// TODO: we are assuming unacknowledged mode.
void Sndcp::sndcpWriteSegment(ByteVector &pduSeg, unsigned segnum, unsigned flags)
{
LlcDlFrame result(pduSeg.size()+4); // May be overkill by one or more bytes.
result.appendByte(flags);
if (flags & F_BIT) {
// 6.7.1.1: First segment has DCOMP and PCOMP parameters.
// Amusingly, only make the pdu bigger.
result.appendByte(0); // No compression.
}
result.appendField(segnum,4); // segment number.
result.appendField(mSendNPdu % mSNS,12); // pdu number.
result.append(pduSeg);
// TODO: Is this a command or a response?
mlle->lleWriteHighSide(result,true,"user pdu");
}
// downlink data from internet comes in here.
// It needs to be segmented and sent to LLC Entity for yet another header.
void Sndcp::sndcpWriteHighSide(ByteVector &sdu)
{
// Set the first byte flags.
unsigned flags = mNSapi;
flags |= T_BIT; // UNITDATA PDU
flags |= F_BIT; // First segment.
// Segment the pdu.
unsigned segnum = 0;
unsigned segsize = getMaxPduSize();
segsize -= 12; // be safe. If you dont do this, the blackberry rejects the packets.
for (; sdu.size() > segsize; segnum++) {
flags |= M_BIT; // Not last segment.
ByteVector seg(sdu.segment(0,segsize));
sndcpWriteSegment(seg,segnum,flags);
sdu.trimLeft(segsize);
flags &= ~F_BIT; // Not first segment.
}
flags &= ~M_BIT; // Now it is the last segment.
sndcpWriteSegment(sdu,segnum,flags);
mSendNPdu = (mSendNPdu+1) % mSNS;
}
// invert the low width bits of x.
static uint32_t revbits(uint32_t x, unsigned width)
{
x &= ((uint32_t)1<<width)-1;
uint32_t result = 0;
for (unsigned i = 0; i < width; i++) {
result = result << 1;
result |= (x&1);
x = x >> 1;
}
return result;
}
// Pre-compute the CRC divisors for each possible byte.
static void genParityTab(uint32_t invGen, uint32_t *tab)
{
for (int i = 0; i < 256; i++) {
uint32_t crc = i;
for (int b = 7; b >= 0; b--) {
unsigned bit = crc & 1;
crc >>= 1;
if (bit) { crc ^= invGen; }
}
tab[i] = crc;
}
}
Parity32::Parity32(uint32_t generator, unsigned width, bool invertFirst)
{
mMask = (((uint32_t)1<<width) - 1);
mInitialRemainder = invertFirst ? mMask : 0;
// If it is a 32-bit generator, dont bother passing in bit 33,
// which gets shifted off the top of the 32-bit generator argument.
if (width == 32) {
// untested: The 33rd bit is off the top, so put it back.
// Note that this would work for both cases, but clearer to separate it.
mInvertedGenerator = (revbits(generator,32) >> 1) | (1<<31);
} else {
mInvertedGenerator = revbits(mMask & generator,24);
}
genParityTab(mInvertedGenerator,mTab);
}
uint32_t Parity32::computeCrc(unsigned char *str, int len)
{
uint32_t crc=mInitialRemainder;
unsigned char *bp = str, *ep = str + len;
while (bp < ep) {
crc = (crc >> 8) ^ mTab[(crc ^ *bp++) & 0xff];
}
return (~crc) & mMask;
// As a comment, this is the identical algorithm to the above, without the table lookup:
/***
for (int l = 0; l < len; l++) {
crc = crc ^ str[l];
for (int b = 7; b >= 0; b--)
{
unsigned bit = crc & 1;
crc >>= 1;
if (bit) { crc ^= lsbgen; }
}
}
***/
}
uint32_t Parity32::computeCrc(ByteVector &bv)
{
return computeCrc(bv.begin(),bv.size());
}
extern "C" { int gprs_llc_fcs(uint8_t *data, unsigned int len); };
void LlcParity::appendFCS(ByteVector &bv)
{
uint32_t fcs = computeCrc(bv);
// append 24-bit fcs LSB first.
bv.appendByte(fcs&0xff);
bv.appendByte((fcs>>8)&0xff);
bv.appendByte((fcs>>16)&0xff);
// Double check:
#if 0
uint32_t oldcrc = gprs_llc_fcs(bv.begin(),bv.size()-3);
if (fcs != oldcrc) {
printf("CRC ERROR: old=%d new=%d\n",oldcrc,fcs);
} else {
printf("CRC matches\n");
}
#endif
}
// Check the FCS in the last 3 bytes of bytevector.
bool LlcParity::checkFCS(ByteVector &bv)
{
unsigned len = bv.size();
uint32_t fcs = (bv.getByte(len-1)<<16) | (bv.getByte(len-2)<<8) | bv.getByte(len-3);
uint32_t computedFCS = computeCrc(bv.begin(),len-3);
return fcs == computedFCS;
}
LlcParity gLlcParity; // The one and only parity generator needed.
}; // namespace