Files
openbts/GPRS/BSSGMessages.cpp
2014-07-16 23:57:22 +02:00

620 lines
16 KiB
C++

/*
* Copyright 2011, 2014 Range Networks, Inc.
*
* This software is distributed under multiple licenses;
* see the COPYING file in the main directory for licensing
* information for this specific distribution.
*
* This use of this software may be subject to additional restrictions.
* See the LEGAL file in the main directory for details.
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.
*/
#define LOG_GROUP LogGroup::GPRS // Can set Log.Level.GPRS for debugging
#include "BSSG.h"
#include "BSSGMessages.h"
#include "GPRSInternal.h"
#include "Globals.h"
#include "LLC.h"
#define CASENAME(x) case x: return #x;
namespace BSSG {
const char *BSPDUType::name(int val)
{
switch ((type)val) {
CASENAME(DL_UNITDATA)
CASENAME(UL_UNITDATA)
CASENAME(RA_CAPABILITY)
CASENAME(PTM_UNITDATA)
CASENAME(PAGING_PS)
CASENAME(PAGING_CS)
CASENAME(RA_CAPABILITY_UPDATE)
CASENAME(RA_CAPABILITY_UPDATE_ACK)
CASENAME(RADIO_STATUS)
CASENAME(SUSPEND)
CASENAME(SUSPEND_ACK)
CASENAME(SUSPEND_NACK)
CASENAME(RESUME)
CASENAME(RESUME_ACK)
CASENAME(RESUME_NACK)
CASENAME(BVC_BLOCK)
CASENAME(BVC_BLOCK_ACK)
CASENAME(BVC_RESET)
CASENAME(BVC_RESET_ACK)
CASENAME(BVC_UNBLOCK)
CASENAME(BVC_UNBLOCK_ACK)
CASENAME(FLOW_CONTROL_BVC)
CASENAME(FLOW_CONTROL_BVC_ACK)
CASENAME(FLOW_CONTROL_MS)
CASENAME(FLOW_CONTROL_MS_ACK)
CASENAME(FLUSH_LL)
CASENAME(FLUSH_LL_ACK)
CASENAME(LLC_DISCARDED)
CASENAME(SGSN_INVOKE_TRACE)
CASENAME(STATUS)
CASENAME(DOWNLOAD_BSS_PFC)
CASENAME(CREATE_BSS_PFC)
CASENAME(CREATE_BSS_PFC_ACK)
CASENAME(CREATE_BSS_PFC_NACK)
CASENAME(MODIFY_BSS_PFC)
CASENAME(MODIFY_BSS_PFC_ACK)
CASENAME(DELETE_BSS_PFC)
CASENAME(DELETE_BSS_PFC_ACK)
}
return "unrecognized PDU";
}
std::ostream& operator<<(std::ostream& os, const BSPDUType::type val)
{
os << "PDU_Type=" <<(int)val <<"=" <<BSPDUType::name(val);
return os;
}
// GSM 08.18 table 5.4 says what BVCI to use for each message.
// This is kinda dopey, but we have to do it.
// We are ignoring PTM messages.
// There is a note the PAGING_PS and PAGING_CS may be BVCI::SIGNALLING
const unsigned BSPDUType::LookupBVCI(BSPDUType::type bstype)
{
switch (bstype) {
case BSPDUType::SUSPEND:
case BSPDUType::SUSPEND_ACK:
case BSPDUType::SUSPEND_NACK:
case BSPDUType::RESUME:
case BSPDUType::RESUME_ACK:
case BSPDUType::RESUME_NACK:
case BSPDUType::FLUSH_LL:
case BSPDUType::FLUSH_LL_ACK:
case BSPDUType::LLC_DISCARDED:
case BSPDUType::BVC_BLOCK:
case BSPDUType::BVC_BLOCK_ACK:
case BSPDUType::BVC_UNBLOCK:
case BSPDUType::BVC_UNBLOCK_ACK:
case BSPDUType::BVC_RESET:
case BSPDUType::BVC_RESET_ACK:
case BSPDUType::SGSN_INVOKE_TRACE:
return BVCI::SIGNALLING;
default:
return gBSSG.mbsBVCI;
}
}
const char *IEIType::name(int val)
{
switch ((type)val) {
CASENAME(AlignmentOctets)
CASENAME(BmaxDefaultMS)
CASENAME(BSSAreaIndication)
CASENAME(BucketLeakRate)
CASENAME(BVCI)
CASENAME(BVCBucketSize)
CASENAME(BVCMeasurement)
CASENAME(Cause)
CASENAME(CellIdentifier)
CASENAME(ChannelNeeded)
CASENAME(DRXParameters)
CASENAME(eMLPPPriority)
CASENAME(FlushAction)
CASENAME(IMSI)
CASENAME(LLCPDU)
CASENAME(LLCFramesDiscarded)
CASENAME(LocationArea)
CASENAME(MobileId)
CASENAME(MSBucketSize)
CASENAME(MSRadioAccessCapability)
CASENAME(OMCId)
CASENAME(PDUInError)
CASENAME(PDULifetime)
CASENAME(Priority)
CASENAME(QoSProfile)
CASENAME(RadioCause)
CASENAME(RACapUPDCause)
CASENAME(RouteingArea)
CASENAME(RDefaultMS)
CASENAME(SuspendReferenceNumber)
CASENAME(Tag)
CASENAME(TLLI)
CASENAME(TMSI)
CASENAME(TraceReference)
CASENAME(TraceType)
CASENAME(TransactionId)
CASENAME(TriggerId)
CASENAME(NumberOfOctetsAffected)
CASENAME(LSAIdentifierList)
CASENAME(LSAInformation)
CASENAME(PacketFlowIdentifier)
CASENAME(PacketFlowTimer)
CASENAME(AggregateBSSQoSProfile)
CASENAME(FeatureBitmap)
CASENAME(BucketFullRatio)
CASENAME(ServiceUTRANCCO)
}
return "unrecognized IEI";
}
std::ostream& operator<<(std::ostream& os, const IEIType::type val)
{
os << "IEI_Type=" <<(int)val <<"=" <<IEIType::name(val);
return os;
}
const char *NSPDUType::name(int val)
{
switch ((type)val) {
CASENAME(NS_UNITDATA)
CASENAME(NS_RESET)
CASENAME(NS_RESET_ACK)
CASENAME(NS_BLOCK)
CASENAME(NS_BLOCK_ACK)
CASENAME(NS_UNBLOCK)
CASENAME(NS_UNBLOCK_ACK)
CASENAME(NS_STATUS)
CASENAME(NS_ALIVE)
CASENAME(NS_ALIVE_ACK)
}
return "unrecognized NSPDUType";
}
std::ostream& operator<<(std::ostream& os, const NSPDUType::type val)
{
os << "NSPDU_Type=" <<(int)val <<"=" <<NSPDUType::name(val);
return os;
}
#if 0
/** GSM 04.60 11.2 */
BSSGDownlinkMsg* BSSGDownlinkParse(ByteVector &src)
{
BSSGDownlinkMsg *result = NULL;
// NS PDUType is byte 0.
NSPDUType::type nstype = (NSPDUType::type) src.getByte(0);
if (nstype != NSPDUType::NS_UNITDATA) {
LOG(INFO) << "Unrecognized NS PDU Type=" << nstype ;
return NULL;
}
// BSSG PDUType is byte 5.
BSPDUType::type msgType = (BSPDUType::type) src.getByte(5);
switch (msgType) {
case BSPDUType::DL_UNITDATA:
result = new BSSGMsgDLUnitData(src);
break;
case BSPDUType::RA_CAPABILITY:
case BSPDUType::PAGING_PS:
case BSPDUType::PAGING_CS:
case BSPDUType::RA_CAPABILITY_UPDATE_ACK:
case BSPDUType::SUSPEND_ACK:
case BSPDUType::SUSPEND_NACK:
case BSPDUType::RESUME_ACK:
case BSPDUType::RESUME_NACK:
default:
LOG(INFO) << "unimplemented BSSG downlink message, type=" << msgType;
return NULL;
}
return result;
};
BSSGDownlinkMsg* BSSGDownlinkMessageParse(ByteVector&src)
{
BSSGDownlinkMsg *result = NULL;
unsigned pdutype = src.getByte(sizeof(NSMsg::UnitDataHeaderLen));
switch (pdutype) {
case BSPDUType::DL_UNITDATA:
result = new BSSGMsgDLUnitData(src);
break;
case BSPDUType::RA_CAPABILITY_UPDATE_ACK:
case BSPDUType::RA_CAPABILITY:
case BSPDUType::FLUSH_LL:
// todo
LOG(INFO) << "unsuppported BSSG downlink PDU type=" << pdutype;
break;
default:
LOG(INFO) << "unsuppported BSSG downlink PDU type=" << pdutype;
break;
}
return result;
}
#endif
void BSSGMsgDLUnitData::parseDLUnitDataBody(ByteVector &src, size_t &wp)
{
wp++; // Skip over the pdutype.
mbdTLLI = src.getUInt32(wp); wp+=4;
mbdQoS.qosRead(src,wp);
// The rest of the fields use TLV format:
unsigned len = src.size() - 1; // wp cant actually get anywhere near size() because
// the TLV headers take at least 2 bytes.
while (wp < len) {
unsigned iei = src.getByte(wp++);
unsigned length = src.readLI(wp);
unsigned nextwp = wp + length;
switch (iei) {
case IEIType::PDULifetime:
mbdPDULifetime = src.getUInt16(wp);
break;
case IEIType::IMSI:
mbdIMSI.set(src.segment(wp,length));
break;
case IEIType::MSRadioAccessCapability:
mbdRACap.set(src.segment(wp,length));
break;
case IEIType::LLCPDU:
// Finally, here is the data:
mbdPDU.set(src.segment(wp,length));
goto done;
case IEIType::TLLI: // old TLLI
mbdHaveOldTLLI = true;
mbdOldTLLI = src.getUInt32(wp);
break;
case IEIType::Priority:
case IEIType::DRXParameters:
case IEIType::PacketFlowIdentifier:
case IEIType::LSAInformation:
case IEIType::AlignmentOctets:
default:
// ignored.
break;
}
wp = nextwp;
}
done:;
//...
}
//std::ostream& operator<<(std::ostream& os, const BSSGMsgDLUnitData &val)
//{
//val.text(os);
//return os;
//}
static void NsAddCause(ByteVector *vec, unsigned cause)
{
vec->appendByte(NsIEIType::IEINsCause);
vec->appendLI(1);
vec->appendByte(cause);
}
static void NsAddBVCI(ByteVector *vec, BVCI::type bvci)
{
vec->appendByte(NsIEIType::IEINsBVCI);
vec->appendLI(2);
vec->appendUInt16(bvci);
}
static void NsAddVCI(ByteVector *vec)
{
vec->appendByte(NsIEIType::IEINsVCI);
vec->appendLI(2);
vec->appendUInt16(gBSSG.mbsNSVCI);
}
static void NsAddNSEI(ByteVector *vec)
{
vec->appendByte(NsIEIType::IEINsNSEI);
vec->appendLI(2);
vec->appendUInt16(gBSSG.mbsNSEI);
}
NSMsg *NsFactory(NSPDUType::type nstype, int cause)
{
NSMsg *vec = new NSMsg(80); // Big enough for any message.
vec->setAppendP(0); // Setup vec for appending.
vec->appendByte(nstype); // First byte of message is NS PDUType.
switch (nstype) {
case NSPDUType::NS_RESET:
NsAddCause(vec,cause);
NsAddVCI(vec);
NsAddNSEI(vec);
break;
case NSPDUType::NS_RESET_ACK:
NsAddVCI(vec);
NsAddNSEI(vec);
break;
case NSPDUType::NS_BLOCK:
NsAddCause(vec,cause);
NsAddVCI(vec);
break;
case NSPDUType::NS_BLOCK_ACK:
NsAddVCI(vec);
break;
case NSPDUType::NS_UNBLOCK:
break; // 1 byte messages is finished.
case NSPDUType::NS_UNBLOCK_ACK:
break; // 1 byte messages is finished.
case NSPDUType::NS_STATUS:
NsAddCause(vec,cause);
switch (cause) {
case NsCause::NSVCBlocked:
case NsCause::NSVCUnknown:
NsAddVCI(vec);
break;
case NsCause::SemanticallyIncorrectPDU:
case NsCause::PduNotCompatible:
case NsCause::ProtocolError:
case NsCause::InvalidEssentialIE:
case NsCause::MissingEssentialIE:
// unimplemented.
assert(0); // In these cases need to append PDU.
case NsCause::BVCIUnknown:
// We dont really know what the cause was,
// and this wont happen, so just make up a BVCI
NsAddBVCI(vec,BVCI::PTP);
break;
case NsCause::TransitNetworkFailure:
case NsCause::OAndMIntervention:
case NsCause::EquipmentFailure:
break; // nothing more needed.
}
break;
case NSPDUType::NS_ALIVE:
break; // 1 byte messages is finished.
case NSPDUType::NS_ALIVE_ACK:
break; // 1 byte messages is finished.
case NSPDUType::NS_UNITDATA:
assert(0); // Not handled by this routine.
default: assert(0);
}
return vec;
}
static void BVCAddBVCI(ByteVector *vec, BSPDUType::type bstype)
{
vec->appendByte(IEIType::BVCI);
vec->appendLI(2);
vec->appendUInt16(BSPDUType::LookupBVCI(bstype));
}
static void BVCAddCause(ByteVector *vec, int cause)
{
vec->appendByte(IEIType::Cause);
vec->appendLI(1);
vec->appendByte(cause);
}
static void BVCAddTag(ByteVector *vec, int tag)
{
vec->appendByte(IEIType::Tag);
vec->appendLI(1);
vec->appendByte(tag);
}
// GSM 08.18 10.4.12 and 11.3.9
static void BVCAddCellIdentifier(ByteVector *vec)
{
vec->appendByte(IEIType::CellIdentifier);
vec->appendLI(8);
// Add Routing Area Identification IE from GSM 04.08 10.5.5.15, excluding IEI type and length bytes.
// Another fine example of Object Oriented programming preventing sharing of code:
// this information is wrapped up in the middle of an inapplicable class hierarchy.
const char*mMCC = gConfig.getStr("GSM.Identity.MCC").c_str();
const char*mMNC = gConfig.getStr("GSM.Identity.MNC").c_str();
unsigned mLAC = gConfig.getNum("GSM.Identity.LAC");
unsigned mRAC = gConfig.getNum("GPRS.RAC");
vec->appendByte(((mMCC[1]-'0')<<4) | (mMCC[0]-'0')); // MCC digit 2, MCC digit 1
vec->appendByte(((mMNC[2]-'0')<<4) | (mMCC[2]-'0')); // MNC digit 3, MCC digit 3
vec->appendByte(((mMNC[1]-'0')<<4) | (mMNC[0]-'0')); // MNC digit 2, MNC digit 1
vec->appendUInt16(mLAC);
vec->appendByte(mRAC);
// Add Routing Area Identification IE from GSM 04.08 10.5.5.15, excluding IEI type and length bytes.
// Add Cell Identity IE GSM 04.08 10.5.1.1, excluding IEI type
unsigned mCI = gConfig.getNum("GSM.Identity.CI");
vec->appendUInt16(mCI);
}
// GSM 08.18 sec 10 describes the PDU messages that the SGSN can send to the BSS.
// Cause values: 08.18 sec 11.3.8
BSSGUplinkMsg *BVCFactory(BSPDUType::type bstype,
int arg1) // For reset, the bvci to reset; for others may be cause or tag .
{
BSSGUplinkMsg *vec = new BSSGUplinkMsg(80); // Big enough for any message.
BVCI::type bvci;
vec->setAppendP(0); // Setup vec for appending.
// Add the NS header.
vec->appendByte(NSPDUType::NS_UNITDATA);
vec->appendByte(0); // unused byte.
vec->appendUInt16(gBSSG.mbsNSEI);
// Add the BSSG message type
vec->appendByte((ByteType)bstype);
switch (bstype) {
case BSPDUType::BVC_RESET:
// See GSM 08.18 sec 8.4: BVC-RESET procedure; and 10.4.12: BVC-RESET message.
bvci = (BVCI::type) arg1;
vec->appendByte(IEIType::BVCI);
vec->appendLI(2);
vec->appendUInt16(bvci);
BVCAddCause(vec,0x8); // Cause 8: O&M Intervention
if (bvci != BVCI::SIGNALLING) {
BVCAddCellIdentifier(vec);
}
// We dont use the feature bitmap.
break;
case BSPDUType::BVC_RESET_ACK:
BVCAddBVCI(vec,bstype);
// There could be a cell identifier
// There coulde be a feature bitmap.
break;
case BSPDUType::BVC_BLOCK:
BVCAddBVCI(vec,bstype);
BVCAddCause(vec,arg1);
break;
case BSPDUType::BVC_BLOCK_ACK: // fall through
case BSPDUType::BVC_UNBLOCK: // fall through
case BSPDUType::BVC_UNBLOCK_ACK:
BVCAddBVCI(vec,bstype);
break;
case BSPDUType::FLOW_CONTROL_BVC_ACK:
BVCAddTag(vec,arg1);
break;
default: assert(0);
}
return vec;
}
// Length Indicator GSM 08.16 10.1.2
// And I quote:
// "The BSS or SGSN shall not consider the presence of octet 2a in a received IE
// as an error when the IE is short enough for the length to be coded
// in octet 2 only."
// (pat) If the length is longer than 127, it is written simply
// as a 16 bit number in network order, which is high byte first,
// so the upper most bit is 0 if the value is <= 32767
static unsigned IEILength(unsigned int len)
{
if (len < 127) return 0x80 + len;
assert(0);
}
void NSMsg::textNSHeader(std::ostream&os) const
{
int nstype = (int)getNSPDUType();
if (nstype == NSPDUType::NS_UNITDATA) {
os <<"NSPDUType="<<nstype <<"="<<NSPDUType::name(nstype)<<" BVCI="<<getUInt16(2);
} else {
os <<"NSPDUType="<<nstype <<"="<<NSPDUType::name(nstype);
// The rest of the message is not interesting to us, so dont bother.
}
}
void NSMsg::text(std::ostream&os) const
{
os <<" NSMsg:"; textNSHeader(os);
}
void BSSGMsg::text(std::ostream&os) const
{
textNSHeader(os);
os << " BSPDUType="<<getPDUType() <<" size="<<size();
}
std::string BSSGMsg::briefDescription() const
{
return BSPDUType::name(getPDUType());
}
BSSGMsgULUnitData::BSSGMsgULUnitData(unsigned wLen, // Length of the PDU to be sent.
uint32_t wTLLI)
: BSSGUplinkMsg(wLen + HeaderLength)
{
QoSProfile qos; // Both we and the SGSN ignore the contents of this.
// Note there is a different QoS format included in
// PDP Context activation messages.
setAppendP(0); // Setup vec for appending.
// Write the NS header.
appendByte(NSPDUType::NS_UNITDATA);
appendByte(0); // unused byte.
// The NS UNITDATA message puts the BVCI in the NS header where the NSEI normally goes.
appendUInt16(gBSSG.mbsBVCI);
// End of NS Header, start of BSSG message.
appendByte(BSPDUType::UL_UNITDATA);
appendUInt32(wTLLI);
qos.qosAppend(this);
BVCAddCellIdentifier(this);
// We need a two byte alignment IEI to get to a 32 bit boundary.
appendByte(IEIType::AlignmentOctets);
appendByte(IEILength(0));
// The PDU IEI itself comes next.
appendByte(IEIType::LLCPDU);
// We are allowed to use a 16 bit length IEI even for elements
// less than 128 bytes long, so we will.
mLengthPosition = size(); // Where the length goes.
appendUInt16(0); // A spot for the length.
// Followed by the PDU data.
assert(size() == HeaderLength);
}
// Call this when the PDU is finished to set the length in the BSSG header.
void BSSGMsgULUnitData::setLength()
{
// The PDU begins immediately after the 2 byte length.
assert(size() >= HeaderLength);
unsigned pdulen = size() - HeaderLength;
GPRSLOG(1) << "setLength:"<<LOGVAR(pdulen) << LOGVAR(mLengthPosition) << LOGVAR(size());
setUInt16(mLengthPosition,pdulen);
}
void BSSGMsgULUnitData::text(std::ostream&os) const
{
os <<"BSSGMsgULUnitData=(";
os <<"tbf=TBF#"<<mTBFId<<" ";
BSSGUplinkMsg::text(os);
unsigned TLLI = getTLLI();
os << LOGHEX(TLLI);
// The payload is 0 length only during debugging when we send in empty messages.
ByteVector payload(tail(HeaderLength));
if (payload.size()) {
//GPRS::LLCFrame llcmsg(*const_cast<BSSGMsgULUnitData*>(this));
os << " LLC UL payload="<<payload;
SGSN::LlcFrameDump llcmsg(payload);
os << " ";
llcmsg.text(os);
}
//os << " payload=" <<payload;
os <<")";
}
std::string BSSGMsgDLUnitData::briefDescription() const
{
std::ostringstream ss;
if (mbdPDU.size()) {
SGSN::LlcFrameDump llcmsg(mbdPDU);
llcmsg.textContent(ss,false);
}
return ss.str();
}
void BSSGMsgDLUnitData::text(std::ostream &os) const
{
os << "BSSGMsgDLUnitData:";
BSSGDownlinkMsg::text(os);
os << " BSPDUType="<<getPDUType();
os<<LOGHEX(mbdTLLI) <<LOGVAR(mbdPDULifetime);
os <<" QoS skipped"; // Skip qos for now.
if (mbdHaveOldTLLI) { os << LOGHEX(mbdOldTLLI); }
os <<" RACap=(" <<mbdRACap <<")";
os <<" IMSI=(" <<mbdIMSI <<")";
//os <<" PDU=(" <<mbdPDU <<")";
if (mbdPDU.size()) {
os << " LLC DL payload="<<mbdPDU;
SGSN::LlcFrameDump llcmsg(mbdPDU);
os << " ";
llcmsg.text(os);
}
os <<"\n";
}
};