/**@file LLC objects, from GSM 04.64. */ /* * 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. */ //#include "GPRSInternal.h" // for LLCWARN #ifndef LLC_H #define LLC_H #include #include "SgsnBase.h" #include "GPRSL3Messages.h" #include //#include "TBF.h" namespace GPRS { class MSInfo; } namespace SGSN { struct LlcEntity; class SgsnInfo; struct Sgsn; class PdpContext; struct Sndcp; // GSM04.64 6.2.3 table 2. struct LlcSapi { enum type { GPRSMM = 1, // receives all control messages, both GMM an SM protocols. TOM2 = 2, UserData3 = 3, UserData5 = 5, SMS = 7, TOM8 = 8, UserData9 = 9, UserData11 = 11, }; static const char *name(type sapi); }; struct LLCFormat { enum type { Invalid, I, S, U, UI, ISack, SSack }; static const char *name(type format); }; // (pat) This is a generic parity generator for up to 32 bit parity. // Pats Notes: The generator-based algorithm in BitVector.h did not work // for the LLC FCS when I tried to pre-invert the remainder. // Here is a new one based on table lookups, which is better for ByteVectors. // An N-bit CRC is always N+1 bits long where the first and last bits are 1. // The CRC is the remainder of division of the input by the generator. // The complicated description of the LLC FCS is describing a normal 24-bit CRC // but they are setting the remainder to all ones beforehand (to catch the input // error of leading 0s) and inverting it after (to catch the input error of trailing 0s.) // Algorithm here is based on LSB-first algorithm from wikipedia "Computation of CRC" // So we have to pre-reverse the bits of the CRC generator. // The top bit of the CRC sets the shift-register output to 0 for the division, // so the division result comes out 0, but those bits are not relevant to the CRC, // which is the remainder, because they are all shifted away. // So we chop the top bit off. class Parity32 { uint32_t mTab[256]; // Precomputed crc remainders. uint32_t mInvertedGenerator; uint32_t mInitialRemainder; uint32_t mMask; public: Parity32(uint32_t generator, unsigned width, bool invertFirst); uint32_t computeCrc(unsigned char *str, int len); uint32_t computeCrc(ByteVector &bv); }; // 04.64 5.5 FCS [Frame Check Sequence] field, aka parity. // The CRC shall be the ones complement of the sum (modulo 2) of: // the remainder of xk (x23 + x22 + x21 +... + x2 + x + 1) divided (modulo 2) by // the generator polynomial, where k is the number of bits of the dividend; // plus the remainder of the division (modulo 2) by the generator polynomial // of the product of x24 by the dividend. // The CRC-24 generator polynomial is: // G(x) = x24 + x23 + x21 + x20 + x19 + x17 + x16 + x15 + x13 + x8 + x7 + x5 + x4 + x2 + 1 class LlcParity : public Parity32 { static const uint32_t sFCSGenerator = (1<<24) + (1<<23) + (1<<21) + (1<<20) + (1<<19) + (1<<17) + (1<<16) + (1<<15) + (1<<13) + (1<<8) + (1<<7) + (1<<5) + (1<<4) + (1<<2) + 1; public: LlcParity() : Parity32(sFCSGenerator,24,true) {}; void appendFCS(ByteVector &bv); bool checkFCS(ByteVector &bv); // true if parity ok. }; extern LlcParity gLlcParity; struct LlcDefs { // sec 6.4 LLC commands for U-format frames, passed in LlcFrame::mM above. enum U_M_Commands { // Some are commands and some are responses. UCMD_NULL = 0, // null command UCMD_DM = 1, // DM response: Disconnected Mode Response. // "An LLE shall transmit a DM response to any valid command // received that it cannot action." // However, I think the multitech modem sends this as a command. UCMD_DISC = 4, // DISC command: Disconnect - terminate ABM mode UCMD_UA = 6, // UA response: acknowledge SABM or DISC command UCMD_SABM = 7, // SABM command: Set Async Balanced (aka acknowledged) Mode UCMD_FRMR = 8, // FRMR response: Frame Reject response - includes a bunch of info; see spec. UCMD_XID = 0xb // XID command or response: Exchange Identification - used to set parameters. }; // sec 6.4 LLC commands for S-format frames, passed in LlcFrame::mS above. enum U_S_Commands { SCMD_RR = 0, SCMD_ACK = 1, SCMD_RNR = 2, SCMD_SACK = 3 }; }; struct LlcMsg { virtual void llcProcess(LlcEntity *lle) = 0; virtual const char *typeName() { return "generic"; } }; // Note: The frame formats are in 04.64 6.3. struct LlcFrame : public LlcDefs, public ByteVector { static const unsigned addrOffset = 0; static const unsigned controlOffset = 1; static const unsigned UIHeaderLength = 3; // 1 byte for addr, 2 for header. // Address fields: unsigned getSapi() { return getByte(addrOffset) & 0xf; } bool getLlcPD() { return getBitR1(addrOffset,8); } // 1 means LLC frame. Good grief. bool getCR() { return getBitR1(addrOffset,7); } LLCFormat::type getFormat(); // get the format from the ByteVector. LlcFrame(const ByteVector &vec) : ByteVector(vec) { } LlcFrame(unsigned size) : ByteVector(size) { } //LlcFrame(const LlcFrame &other) { *this = other; } // Return nth control byte. 0 is the first byte after the address byte, // ie, LLC header byte 1. unsigned getControl(unsigned nth) { unsigned w = nth + controlOffset; return size() <= w ? 0 : getByte(w); } // Write the LLC header. void writeAddrHeader(unsigned sapi, bool isCmd) { setField(0,0,1); // PD bit always 0. setField(1,isCmd,1); // Command/Response set to 1 for a downlink command. setField(2,0,2); // 2 unused bits. setField(4,sapi,4); } void appendAddrHeader(unsigned sapi, bool isCmd) { writeAddrHeader(sapi, isCmd); setAppendP(1); } LlcMsg *switchFrame(); void llcProcess1(LlcEntity *lle); }; struct LlcDlFrame : public LlcFrame { // Allocate room for all the downlink headers that will be needed: // The size is the needed payload size. // Add room for all the downstream headers and trailers: // sndcp header up to 4 bytes, but it allocates its own, so all we need are: // llc header 3 bytes // fcs trailer 3 bytes // We will overkill it a bit, and and let the downstream prepend their headers. LlcDlFrame(unsigned size) : LlcFrame(size+12) { trimLeft(8); // Room for headers. setAppendP(0); } }; // 05.64 6.3 struct LlcFrameI : public LlcFrame, public LlcMsg { LlcFrameI(ByteVector &f) : LlcFrame(f) {} bool getA() { return getBit2(controlOffset,1); } unsigned getNS() { return getField2(controlOffset,3,9); } unsigned getNR() { return getField2(controlOffset+1,5,9); } unsigned getS() { return getField2(controlOffset+2,6,2); } // S1 and S2 bits. void llcProcess(LlcEntity *lle) { // We dont handle them. LLCWARN("LLC unexpected I frame ignored"<= 4. appendField(xidtype,5); appendField(len,8); appendField(0,2); // 2 unused bits. appendField(value,8*len); } } }; // 3GPP 04.64 Logical Link Entity part of LLC. // There is one of these for each data LLC SAPI for each MS. // The LLC SAPIs are supposed to correspond to QoS [Quality of Service] classes; // the final NSAPI [Network SAPIs] that are connected to PDPContexts // are the on the high side of the SNDCP entity for user-data LLC SAPIs only. // There can be a many-to-one mapping of SNDCP entity to LLC SAPI. // The pdu number in the header is used to discard duplicates (04.64 8.4.2), // with a memory of 32 frames, which is redundant for data SAPIs because // SNDCP also discards PDUs, as well as assembling and reordering. // LLC has three states which affect LLC Entity: // o TLLI Unassigned state. Can only use UI and XID frames for SAPI = 1. // o TLLI assigned (by the SGSN, from higher layers) - affects all entities. // o ABM state - acknowledged data state - per entity. // Unacknowledged frames may also be sent in ABM mode. // The SAP [Service Access Points] are defined in LlcSapi above. struct LlcEntity { // 6.3.5.5 Unacknowledged mode state variables: static const unsigned mSNS = 512; // modulo arithment unsigned mVU; // Unconfirmed send state variable unsigned mVUR; // Unconfirmed receive state variable. unsigned mN201U; // Max number of bytes in UI data field. // This is used by the SNDCP to split the data. //GPRS::MSInfo *mMS; // The MS who ultimately owns us. //LlcEntity(GPRS::MSInfo *ms) : mMS(ms) { reset(); } //GPRS::MSInfo *getMS() { return mMS; } SgsnInfo *mSI; // The SgsnInfo in which we reside. LlcEntity(SgsnInfo *wSI) : mSI(wSI) {} //SgsnInfo *getSgsnInfo(); void reset() { mVU = mVUR = 0; // 8.9.8: LLC layer parameter default values. // It varies by SAPI, but for user data default is 500, max 1520. // For other sapis length wont be exceeded anyway so dont worry about them. mN201U = 500; } virtual void lleUplinkData(ByteVector &payload) = 0; virtual unsigned getLlcSapi() = 0; //Sndcp *getSndcp(unsigned nsapi); //void setSndcp(unsigned nsapi,Sndcp*); void lleWriteLowSide(LlcFrame &frame); void lleWriteHighSide(LlcDlFrame &frame, bool isCmd, const char *descr); void lleWriteHighSide(L3GprsDlMsg &msg); void lleWriteRaw(ByteVector &frame, const char *descr); }; #if LLC_IMPLEMENTATION //SgsnInfo *LlcEntity::getSgsnInfo() { return mSI; } #endif // Attached to one of the user-data SAPIs for an MS struct LlcEntityUserData : public LlcEntity { unsigned mLlcSapi; // The LLC sapi of this entity. unsigned mN201U; LlcEntityUserData(unsigned wLlcSapi, SgsnInfo *si) : LlcEntity(si), mLlcSapi(wLlcSapi) { mN201U = 500; // Default max size for pdus. } unsigned getLlcSapi() { return mLlcSapi; } unsigned getMaxPduSize() { return mN201U; } Sndcp *getSndcp(unsigned nsapi); void setSndcp(unsigned nsapi, Sndcp*ptr); void lleUplinkData(ByteVector &payload); }; #if LLC_IMPLEMENTATION #endif // Attached to the LLC GPRSMM sapi for an MS. struct LlcEntityGmm : public LlcEntity { LlcEntityGmm(SgsnInfo *si) : LlcEntity(si) {} // The payload is in L3 message. void lleUplinkData(ByteVector &payload); // calls: Sgsn::handleL3Msg(this,&payload); unsigned getLlcSapi() { return 1; } }; // 3GPP 04.64: SNDCP, with yet another stupid header. // It is a miracle any data gets through at all. // The NSAPI are on the high (network) side, and the SAPI are the low side at LLC. // There can be multiple NSSAPI, each with a PDP context, attached to each SAPI. // The L3 Activate PDP context message specifies both NSAPI and LLC SAPI. struct SndcpFrame : public ByteVector { SndcpFrame(ByteVector &bv) : ByteVector(bv) { } bool getF() { return getBit(1); } // First segment indicator. bool getT() { return getBit(2); } // 0 - DATA(acked) 1 - UNITDATA bool getM() { return getBit(3); } // More bit: 1 => more segments. unsigned getNSapi() { return getField(4,4); } // NSAPI on sndcp network high side. // Dcomp and Pcomp are only extent if F bit is set. unsigned getDcomp() { return getField2(1,0,4); } // data compression unsigned getPcomp() { return getField2(1,4,4); } // protocol compression ByteVector getPayload() { return tail((getT() ? 3 : 2)+(getF()?1:0)); } // For UNITDATA (unacknowledged mode) - T bit == 1 unsigned getSegmentNumber() { return getField2(getF()?2:1,0,4); } unsigned getPduNumber() { if (getT()) { // unacknowledged mode. return getField2(getF()?2:1,4,12); } else { // acknowledged mode. return getField2(getF()?2:1,0,8); } } // For DATA (acknowled mode) - T bit == 0 //unsigned getAMPduNumber() { return getField2(2,0,8); } }; class Sndcp { enum { // Address field bits F_BIT = 0x40, T_BIT = 0x20, M_BIT = 0x10 }; // Our identifiers: static const unsigned sUmSNS = 4096; // Module for Unacknowledged Mode, sequence number space. static const unsigned sAmSNS = 256; // Module for Acknowledged Mode, sequence number space. unsigned mSNS; // Current modulo unsigned mNSapi; // 5..16 unsigned mLlcSapi; // One of the LLC UserData sapis. UInt_z mRecvNPdu; UInt_z mSendNPdu; LlcEntityUserData *mlle; public: //PdpContext *mPdp; //void setPdp(PdpContext *pdp) { mPdp = pdp; } //PdpContext *getPdp() { return mPdp; } SgsnInfo *getSgsnInfo(); // I dont think it is possible in our case for an sndcp to exist in pdp-inactive state. //bool isPdpInactive(); // Data reassembly Queues: // We are supposed to reorder pdus. // We dont care about the order, since they are going to the internet, but the packets may // arrive in multiple segments that have become unordered, possibly due to multi-slot transmission, // so we need to handle it. We will only reorder the last sMemory pdus. static const unsigned sMemory = 32; struct OneSdu { UInt_z mSegCount; // Number of segs, derived from 'm' bit. ByteVector segs[16]; // The segments. }; OneSdu mSegs[sMemory]; // This stuff is all deleted automatically. Sndcp(unsigned wNSapi, unsigned wLlcSapi, LlcEntityUserData *mlle); ~Sndcp(); private: // If we have all the segments for pdu num, send it off. // If force, delete it even if incomplete. void flush(unsigned num, bool force); int diffSNS(int v1, int v2); // SDU segmented to this size. May be negotiated using XID command, which we dont implement. unsigned getMaxPduSize(); void sndcpWriteSegment(ByteVector &pduSeg, unsigned segnum, unsigned flags); public: // downlink data from internet comes in here. // It needs to be segmented and sent to LLC. void sndcpWriteHighSide(ByteVector &sdu); // uplink data from MS comes in here. void sndcpWriteLowSide(SndcpFrame &frame); }; #if LLC_IMPLEMENTATION Sndcp::Sndcp(unsigned wNSapi, unsigned wLlcSapi, LlcEntityUserData *wlle) : mSNS(sUmSNS), mNSapi(wNSapi), mLlcSapi(wLlcSapi), mlle(wlle) //,mPdp(0) { mlle->setSndcp(mNSapi,this); } Sndcp::~Sndcp() { // Test mlle and mPdp and setting to 0 after are cautious overkill. // The setSndcp() is also redundant, since our caller does it too. if (mlle) {mlle->setSndcp(mNSapi,0); mlle = 0;} //if (mPdp) {delete mPdp; mPdp = 0;} } #endif // A connection is identified by DLCI [Data Link Connection Identifier] which is a TLLI+SAP. // However, our LlcEngine corresponds directly with an SgsnInfo which corresponds // directly with the MSInfo, so we dont use this. //struct LlcDlci { // MSInfo *ms; // unsigned char mSapi; //}; // An LLC engine for use with a single MS. struct LlcEngine { // A set of LLC LLE [logical link entities] for use in an MS. // These use predefined SAPIs, and here they are. // The different user-data ones are supposed to correspond to different priorities, // but we ignore the distinction. LlcEntityGmm mLleGmm; LlcEntityUserData mLleUserData3; LlcEntityUserData mLleUserData5; LlcEntityUserData mLleUserData9; LlcEntityUserData mLleUserData11; // An sndcp entity for each NSAPI that is in use, ie, for each allocated pdpcontext. // Allocated/deallocated on demand and at the same time as the PdpContext they connect with. // The LlcEntityUserData are connected to these in a one-to-many mapping, // ie, each LlcEntity may be tied to one or more Sndcp. #if 0==SNDCP_IN_PDP Sndcp *mSndcp[16]; // 0-4 are reserved (same as UMTS), but we just allocate the whole array // and index it directly with nsapi [Network SAPI] #endif LlcEngine(SgsnInfo *si) : mLleGmm(si), mLleUserData3(3,si), mLleUserData5(5,si), mLleUserData9(9,si), mLleUserData11(11,si) { RN_MEMCHKNEW(LlcEngine) #if 0==SNDCP_IN_PDP memset(mSndcp,0,sizeof(mSndcp)); #endif } ~LlcEngine() { RN_MEMCHKDEL(LlcEngine) } static bool isValidDataSapi(unsigned sapi) { switch (sapi) { case LlcSapi::UserData3: case LlcSapi::UserData5: case LlcSapi::UserData9: case LlcSapi::UserData11: return true; default: return false; } } LlcEntity *getLlcEntity(unsigned sapi) { switch (sapi) { case LlcSapi::GPRSMM: return &mLleGmm; case LlcSapi::UserData3: return &mLleUserData3; case LlcSapi::UserData5: return &mLleUserData5; case LlcSapi::UserData9: return &mLleUserData9; case LlcSapi::UserData11: return &mLleUserData11; default: return 0; } } // The LLC Entities. The GMM ones process L3 messages and the UserData ones are for...guess what? // The LlcEntityUserData communicates to one or more Sndcp on its high side. LlcEntityGmm *getLlcGmm(); LlcEntityUserData *getLlcEntityUserData(unsigned llcSapi); // The bv is an LLC message. // It may cause the LLC to send some download messages, // or it may be an information frame on a UserData sapi // whose payload goes to GGSN via an Sndcp entity. void llcWriteLowSide(ByteVector &bv,SgsnInfo *si); void llcWriteHighSide(ByteVector &sdu,int nsapi); void allocSndcp(SgsnInfo *si, unsigned nsapi, unsigned llcsapi); void freeSndcp(unsigned nsapi); }; // GSM04.64 6.2 // (pat) The RLC/MAC layer knows nothing about the contents of an LlcFrame; it just passes data // back and forth between the MS and SGSN. // This is used to dump out the complete header. struct LlcFrameDump : public LlcFrame { LLCFormat::type mFormat; // I, I+S format is for acknowledged mode. // UI format is for unacknowledged mode. // U format is for LLC control only, includes no data. // Control fields. Which are valid depends on format: bool mE; // UI format only bool mPM; // UI format only bool mA; // I or S format bool mPF; // U format only unsigned mM; // U format only unsigned mS; // I or S formats. unsigned mK; // I Sack format bitmap length. unsigned mNS, mNR, mNU; // Return -1 if we dont know how long, which is S SACK format. int headerLength(); //LlcFrameDump(const ByteVector &vec) : LlcFrame(vec) LlcFrameDump(const ByteVector &vec) : LlcFrame(vec) { mFormat = getFormat(); llcParseDump(); } void llcParseDump(); void textHeader(std::ostream &os); void textContent(std::ostream &os, bool verbose); void text(std::ostream &os); }; }; // namespace GPRS #endif