1454 lines
43 KiB
C++
1454 lines
43 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.
|
|
*/
|
|
|
|
#include <ostream>
|
|
#include "GPRSL3Messages.h"
|
|
//#include "TBF.h"
|
|
#include "Sgsn.h"
|
|
#include "Ggsn.h"
|
|
#include "Utils.h"
|
|
|
|
namespace SGSN {
|
|
class SgsnError;
|
|
|
|
static bool sEnableMsRaCap = true; // MsRaCap had alot of bugs, so provide a way to disable it.
|
|
|
|
#define CASENAME(x) case x: return #x;
|
|
|
|
const char *L3GmmMsg::name(unsigned mt, bool ornull)
|
|
{
|
|
switch ((MessageType)mt) {
|
|
CASENAME(AttachRequest)
|
|
CASENAME(AttachAccept)
|
|
CASENAME(AttachComplete)
|
|
CASENAME(AttachReject)
|
|
CASENAME(DetachRequest)
|
|
CASENAME(DetachAccept)
|
|
CASENAME(RoutingAreaUpdateRequest)
|
|
CASENAME(RoutingAreaUpdateAccept)
|
|
CASENAME(RoutingAreaUpdateComplete)
|
|
CASENAME(RoutingAreaUpdateReject)
|
|
CASENAME(ServiceRequest)
|
|
CASENAME(ServiceAccept)
|
|
CASENAME(ServiceReject)
|
|
CASENAME(PTMSIReallocationCommand)
|
|
CASENAME(PTMSIReallocationComplete)
|
|
CASENAME(AuthenticationAndCipheringReq)
|
|
CASENAME(AuthenticationAndCipheringResp)
|
|
CASENAME(AuthenticationAndCipheringRej)
|
|
CASENAME(AuthenticationAndCipheringFailure)
|
|
CASENAME(IdentityRequest)
|
|
CASENAME(IdentityResponse)
|
|
CASENAME(GMMStatus)
|
|
CASENAME(GMMInformation)
|
|
default:
|
|
return ornull ? 0 : "unrecognized L3GmmMsg type";
|
|
}
|
|
}
|
|
|
|
void L3GmmMsg::text(std::ostream&os) const
|
|
{
|
|
os <<name(MTI());
|
|
textBody(os);
|
|
}
|
|
|
|
void L3GmmDlMsg::gWrite(ByteVector &msg)
|
|
{
|
|
// Note: nibbles are reversed.
|
|
msg.appendField(0,4); // Skip indicator.
|
|
msg.appendField(GSM::L3GPRSMobilityManagementPD,4); // protocol discriminator.
|
|
msg.appendByte(MTI());
|
|
gmmWriteBody(msg);
|
|
}
|
|
|
|
void L3GmmUlMsg::gmmParse(L3GmmFrame &frame)
|
|
{
|
|
try {
|
|
size_t rp = frame.getBodyOffset();
|
|
gmmParseBody(frame,rp);
|
|
} catch (ByteVectorError) {
|
|
SGSNERROR("Premature end of GPRS GMM message type "<<MTI()<<" "<<mtname());
|
|
throw(SgsnError());
|
|
}
|
|
}
|
|
|
|
|
|
void L3SmMsg::text(std::ostream&os) const {
|
|
os <<name(MTI())<<LOGVAR2("TransactionId",mTransactionId);
|
|
textBody(os);
|
|
}
|
|
|
|
void L3SmDlMsg::gWrite(ByteVector &msg) {
|
|
assert(mTransactionId != -1); // Must be specified in initializer.
|
|
appendTiPd(msg); // Append Transaction Id and PD to msg.
|
|
msg.appendByte(MTI());
|
|
smWriteBody(msg);
|
|
}
|
|
|
|
void L3SmUlMsg::smParse(L3SmFrame &frame) {
|
|
try {
|
|
mTransactionId = frame.getTI();
|
|
size_t rp = frame.getBodyOffset();
|
|
smParseBody(frame,rp);
|
|
} catch (ByteVectorError) {
|
|
SGSNERROR("Premature end of GPRS SM message type "<<MTI()<<" "<<mtname());
|
|
throw(SgsnError());
|
|
}
|
|
}
|
|
|
|
#define NAMEQ(name) " "<<name<<"="
|
|
|
|
// obsolete:
|
|
class GPRSL3Dumper {
|
|
public:
|
|
ByteVector vec;
|
|
std::ostream &os;
|
|
size_t wp;
|
|
GPRSL3Dumper(ByteVector &wVec, std::ostream &wos) : vec(wVec), os(wos), wp(0) {}
|
|
void dumpval(int len) {
|
|
os << hex;
|
|
for (int i = 0; i < len; i++) {
|
|
os <<((unsigned) vec.getByte(wp++));
|
|
}
|
|
os << dec;
|
|
wp += len;
|
|
}
|
|
void dumpV(const char *name, int len) {
|
|
os << NAMEQ(name) << "(";
|
|
dumpval(len);
|
|
os << ")";
|
|
wp += len;
|
|
}
|
|
void dumpLV(const char *name) {
|
|
int len = vec.readByte(wp);
|
|
os << NAMEQ(name) <<"(";
|
|
os << NAMEQ("l")<<len <<NAMEQ("v"); dumpval(len);
|
|
os << ")";
|
|
wp += len;
|
|
}
|
|
// The specs give the low nibble first, then the high nibble.
|
|
void dumpNibble(const char *name,int hi) {
|
|
if (name) {
|
|
os << NAMEQ(name)<< ((int)vec.getNibble(wp,hi));
|
|
}
|
|
if (hi) { wp++; }
|
|
}
|
|
void dumpOT(int iei, const char *name) {
|
|
// Just the IEI existence is all there is.
|
|
if (vec.getByte(wp) == iei) { wp++; os<<name<<"=true ";}
|
|
}
|
|
void dumpOTV(int iei, const char *name, int len) {
|
|
if (vec.getByte(wp) == iei) { wp++; dumpV(name,len); }
|
|
}
|
|
void dumpOTLV(int iei, const char *name) {
|
|
if (vec.getByte(wp) == iei) { wp++; dumpLV(name); }
|
|
}
|
|
void finish() {}
|
|
};
|
|
|
|
|
|
void L3GmmMsg::dump(L3GmmFrame &frame,std::ostream &os)
|
|
{
|
|
if (frame.size() < 2) { os <<"L3 Message size too small ="<<frame.size(); return; }
|
|
try {
|
|
// Standard L3 header:
|
|
unsigned mt = frame.getMsgType();
|
|
os <<" type="<<name(mt);
|
|
os <<LOGVAR2("pd",frame.getPD()) << LOGVAR2("skip",frame.getSkip());
|
|
//<<" skip="<<((int)getNibble(0,1));
|
|
//os <<" pd="<<((int)getNibble(0,0))
|
|
//<<" skip="<<((int)getNibble(0,1));
|
|
|
|
GPRSL3Dumper p(frame,os);
|
|
p.wp += 2;
|
|
switch (mt) {
|
|
case AttachRequest:
|
|
p.dumpLV("MS Network Capability");
|
|
p.dumpNibble("AttachType",0);
|
|
p.dumpNibble("CipheringKeySeq",1);
|
|
p.dumpV("DRXParameter",2);
|
|
p.dumpLV("P-TMSIorIMSI");
|
|
p.dumpV("Old RAId",6);
|
|
p.dumpLV("MS RadioAccessCap");
|
|
p.dumpOTV(0x19,"Old P-TMSI signature",4);
|
|
p.dumpOTV(0x17,"Requested READY timer value",2);
|
|
p.dumpOTV(0x9,"TMSI status",1);
|
|
p.dumpOTLV(0x33,"PS LCS Capability");
|
|
p.dumpOTLV(0x11,"MS classmark2");
|
|
p.dumpOTLV(0x20,"MS classmark3");
|
|
p.dumpOTLV(0x40,"Supported Codecs");
|
|
p.dumpOTLV(0x58,"UE Network Capability");
|
|
p.dumpOTLV(0x1a,"Additional Mobile Id");
|
|
p.dumpOTLV(0x1b,"Additional Old RAId");
|
|
p.dumpOTLV(0x5d,"Voice domain preference");
|
|
p.finish();
|
|
break;
|
|
case AttachAccept:
|
|
p.dumpNibble("Attach Result",0);
|
|
p.dumpNibble("Force to Standby",1);
|
|
p.dumpV("Periodic RA Update Timer",1);
|
|
p.dumpNibble("Radio Priority for SMS",0);
|
|
p.dumpNibble("Radio Priority for TOM8",1);
|
|
p.dumpV("RA Id",6);
|
|
p.dumpOTV(0x19,"P-TMSI signature",4);
|
|
p.dumpOTV(0x17,"Negotiated READY timer value",2);
|
|
p.dumpOTLV(0x18,"Allocated P-TMSI");
|
|
p.dumpOTLV(0x23,"MS Id");
|
|
p.dumpOTV(0x25,"GMM cause",2);
|
|
p.dumpOTLV(0x2A,"T3302 value");
|
|
p.dumpOT(0x8C,"Cell Notification");
|
|
p.dumpOTLV(0x4A,"Equiv PLMNs");
|
|
// This might Bx where x is a dont care.
|
|
p.dumpOTV(0xB,"Network feature support",1);
|
|
p.dumpOTLV(0x34,"Emergency number list");
|
|
p.dumpOTV(0xA,"Requested MS Info",1);
|
|
p.dumpOTLV(0x37,"T3319 value");
|
|
p.dumpOTLV(0x38,"T3323 value");
|
|
p.finish();
|
|
break;
|
|
case AttachComplete:
|
|
// nothing interesting
|
|
break;
|
|
case AttachReject:
|
|
p.dumpV("GMM cause",1);
|
|
p.dumpOTLV(0x2a,"T3302 value");
|
|
p.finish();
|
|
case PTMSIReallocationCommand:
|
|
p.dumpLV("Allocated P-TMSI");
|
|
p.dumpV("RA Id",6);
|
|
p.dumpNibble("Force to standby",0);
|
|
p.dumpNibble("",1);
|
|
p.dumpOTV(0x19,"P-TMSI signature",4);
|
|
p.finish();
|
|
break;
|
|
case IdentityResponse:
|
|
p.dumpLV("MS Id");
|
|
p.finish();
|
|
break;
|
|
case RoutingAreaUpdateRequest:
|
|
// todo
|
|
break;
|
|
case RoutingAreaUpdateAccept:
|
|
// todo
|
|
break;
|
|
}
|
|
} catch (ByteVectorError) {
|
|
os <<"ERROR: premature end of message";
|
|
}
|
|
}
|
|
|
|
void L3SmMsg::dump(L3SmFrame &frame, std::ostream &os)
|
|
{
|
|
// Standard L3 header:
|
|
unsigned mt = frame.getMsgType();
|
|
os <<LOGVAR2("type",name(mt));
|
|
os <<LOGVAR2("pd",frame.getPD()) << LOGVAR2("TI",frame.getTI());
|
|
|
|
// not implemented, and never will be because we did a full sgsn instead.
|
|
}
|
|
|
|
std::ostream& operator<<(std::ostream& os, L3GmmMsg::MessageType mt)
|
|
{
|
|
os << L3GmmMsg::name(mt);
|
|
return os;
|
|
}
|
|
|
|
const char *L3SmMsg::name(unsigned mt, bool ornull)
|
|
{
|
|
switch ((MessageType)mt) {
|
|
CASENAME(ActivatePDPContextRequest)
|
|
CASENAME(ActivatePDPContextAccept)
|
|
CASENAME(ActivatePDPContextReject)
|
|
CASENAME(RequestPDPContextActivation)
|
|
CASENAME(RequestPDPContextActivationReject)
|
|
CASENAME(DeactivatePDPContextRequest)
|
|
CASENAME(DeactivatePDPContextAccept)
|
|
CASENAME(ModifyPDPContextRequest)
|
|
CASENAME(ModifyPDPContextAccept)
|
|
CASENAME(ModifyPDPContextRequestMS)
|
|
CASENAME(ModifyPDPContextAcceptMS)
|
|
CASENAME(ModifyPDPContextReject)
|
|
CASENAME(ActivateSecondaryPDPContextRequest)
|
|
CASENAME(ActivateSecondaryPDPContextAccept)
|
|
CASENAME(ActivateSecondaryPDPContextReject)
|
|
|
|
// These are old names, no longer sanctioned.
|
|
CASENAME(ActivateAAPDPContextRequest)
|
|
CASENAME(ActivateAAPDPContextAccept)
|
|
CASENAME(ActivateAAPDPContextReject)
|
|
CASENAME(DeactivateAAPDPContextRequest)
|
|
CASENAME(DeactivateAAPDPContextAccept)
|
|
|
|
CASENAME(SMStatus)
|
|
|
|
CASENAME(ActivateMBMSContextRequest)
|
|
CASENAME(ActivateMBMSContextAccept)
|
|
CASENAME(ActivateMBMSContextReject)
|
|
CASENAME(RequestMBMSContextActivation)
|
|
CASENAME(RequestMBMSContextActivationReject)
|
|
|
|
CASENAME(RequestSecondaryPDPContextActivation)
|
|
CASENAME(RequestSecondaryPDPContextActivationReject)
|
|
CASENAME(Notification)
|
|
default:
|
|
return ornull ? 0 : "urecgnized L3SmMsg type";
|
|
}
|
|
}
|
|
|
|
std::ostream& operator<<(std::ostream& os, L3SmMsg::MessageType mt)
|
|
{
|
|
os << L3SmMsg::name(mt);
|
|
return os;
|
|
}
|
|
|
|
const char *L3GprsMsgType2Name(unsigned pd, unsigned mt)
|
|
{
|
|
static char buf[40]; // Beware: will fail if multithreaded; GPRS is single threaded.
|
|
switch ((GSM::L3PD) pd) {
|
|
case GSM::L3GPRSMobilityManagementPD: // Couldnt we shorten this?
|
|
return L3GmmMsg::name(mt);
|
|
case GSM::L3GPRSSessionManagementPD: // Couldnt we shorten this?
|
|
return L3SmMsg::name(mt);
|
|
default:
|
|
sprintf(buf,"unsupported PD: %d\n",pd);
|
|
return buf;
|
|
}
|
|
}
|
|
|
|
const char *L3GprsMsgType2Name(ByteVector &vec)
|
|
{
|
|
unsigned pd = vec.getNibble(0,0);
|
|
unsigned mt = vec.getByte(1);
|
|
return L3GprsMsgType2Name(pd,mt);
|
|
}
|
|
|
|
void L3GprsFrame::dump(std::ostream &os)
|
|
{
|
|
//L3GprsFrame frame(vec);
|
|
os << "(";
|
|
GSM::L3PD pd = getPD();
|
|
switch ((GSM::L3PD) pd) {
|
|
case GSM::L3GPRSMobilityManagementPD: { // Couldnt we shorten this?
|
|
L3GmmFrame gmframe(*this);
|
|
L3GmmMsg::dump(gmframe,os);
|
|
break;
|
|
}
|
|
case GSM::L3GPRSSessionManagementPD: { // Couldnt we shorten this?
|
|
L3SmFrame smframe(*this);
|
|
L3SmMsg::dump(smframe,os);
|
|
break;
|
|
}
|
|
default:
|
|
os << "unsupported PD:"<<pd;
|
|
}
|
|
os << ")";
|
|
}
|
|
|
|
void GMMAttach::gmParseIEs(L3GmmFrame &src, size_t &rp, const char *culprit)
|
|
{
|
|
// All subsequent IEs are optional.
|
|
while (rp < src.size()) {
|
|
unsigned iei = src.readIEI(rp);
|
|
//SGSNLOG(format("debug: gmParseIes %s iei=0x%x rp=%d size=%d\n",culprit,iei,rp,src.size()));
|
|
if ((iei & 0xf0) == 0x90) {
|
|
// 10.5.5.4 TMSI status: high nibble is 9, low bit is TMSI status.
|
|
mTmsiStatus = iei & 1;
|
|
continue;
|
|
}
|
|
switch (iei) {
|
|
case 0x19: // TV Old P-TMSI signature.
|
|
// Dont have a 3 byte 'read' function so use getField then advance rp by 3.
|
|
mOldPtmsiSignature = src.getField(rp,24);
|
|
rp += 3;
|
|
continue;
|
|
case 0x17: // TV
|
|
mRequestedReadyTimerValue = src.readByte(rp);
|
|
continue;
|
|
case 0x27: // TV drx parameter
|
|
mDrxParameter = src.readUInt16(rp);
|
|
continue;
|
|
}
|
|
|
|
// All the rest are TLV
|
|
// The specified length is of the ie itself, excluding the iei type and length byte.
|
|
// Get the length, but dont move rp - let the IEs do that, because
|
|
// some of them need the length byte.
|
|
|
|
// check that we actually have a TLV, e.g. iPhone 5 is send bogus "E0" IEI at end of Attach Request
|
|
if (rp >= src.size()) {
|
|
SGSNERROR("invalid IEI in " << culprit << " bytes=" << src.hexstr());
|
|
return;
|
|
}
|
|
int len = src.getByte(rp);
|
|
size_t nextrp = rp + len + 1;
|
|
if (nextrp > src.size()) { // last one will have nextrp == src.size()
|
|
SGSNERROR("invalid message size in "<<culprit <<" bytes="<<src.hexstr());
|
|
return;
|
|
}
|
|
switch (iei) {
|
|
default:
|
|
rp = nextrp;
|
|
break;
|
|
case 0x18: // P-TMSI, hard coded in Attach Request, optional in RAUpdate
|
|
mMobileId.parseLV(src,rp);
|
|
break;
|
|
case 0x31: // This has a hard-code position in Attach Request,
|
|
// but is an optional IE in RA Update Request.
|
|
mMsNetworkCapability = src.readLVasBV(rp);
|
|
break;
|
|
case 0x32: // PDP Context status.
|
|
rp++; // skip length
|
|
mPdpContextStatus.mStatus[0] = src.readByte(rp);
|
|
mPdpContextStatus.mStatus[1] = src.readByte(rp);
|
|
break;
|
|
case 0x33: // Location services; just dump it.
|
|
src.skipLV(rp,1,1,"PS LCS Capability");
|
|
break;
|
|
case 0x35: // MBMS context status in RAUpdateRequest
|
|
src.skipLV(rp,3,3,"MBMS context status");
|
|
case 0x11:
|
|
src.skipLV(rp,3,3,"Mobile station classmark 2");
|
|
break;
|
|
case 0x20:
|
|
src.skipLV(rp,0,32,"Mobile station classmark 3");
|
|
break;
|
|
case 0x40:
|
|
src.skipLV(rp,3,127,"Supported codecs");
|
|
break;
|
|
case 0x58:
|
|
src.skipLV(rp,2,13,"UE nework capability");
|
|
break;
|
|
case 0x1A:
|
|
mAdditionalMobileId.parseLV(src,rp);
|
|
break;
|
|
case 0x1B:
|
|
src.skipLV(rp,6,6,"Additional old routing area id");
|
|
break;
|
|
case 0x5D:
|
|
src.skipLV(rp,1,1,"Voice domain preference");
|
|
break;
|
|
}
|
|
assert(rp == nextrp);
|
|
}
|
|
}
|
|
|
|
// Decode an IMSI, IMEI or IMEISV type, which are all encoded the same.
|
|
// The result ByteVector size is a multiple of 4 bits, so use sizeBits() not size().
|
|
void GmmMobileIdentityIE::decodeIM(ByteVector &result) const
|
|
{
|
|
result.setAppendP(0);
|
|
bool isodd = (mIdData[0] & 0x8);
|
|
result.appendField(mIdData[0]>>4,4); // First nibble is in the high nibble of first byte.
|
|
for (unsigned i = 1; i < mLen; i++) {
|
|
// The dang nibbles are backwards. What nimrods.
|
|
result.appendField(mIdData[i]&0xf,4);
|
|
// Last byte may only have one nibble:
|
|
// Note that the even/odd indication includes the single nibble
|
|
// in the first byte, so we counter-intuitively check isodd here
|
|
// instead of iseven.
|
|
if (i < mLen-1 || isodd) {
|
|
result.appendField((mIdData[i]>>4)&0xf,4);
|
|
}
|
|
}
|
|
}
|
|
|
|
// 24.008 10.5.1.4 Mobile Identity
|
|
//void GmmMobileIdentityIE::setIMSI(ByteVector &imsi)
|
|
//{
|
|
// int len = imsi.size();
|
|
// if (len == 0) {
|
|
// mPresent = false;
|
|
// return;
|
|
// }
|
|
// mPresent = true;
|
|
// mTypeOfId = 1; // IMSI
|
|
//}
|
|
|
|
ByteVector GmmMobileIdentityIE::getImsi() const
|
|
{
|
|
assert(isImsi()); // Caller was supposed to check this.
|
|
ByteVector imsi(8);
|
|
decodeIM(imsi);
|
|
return imsi;
|
|
}
|
|
|
|
const string GmmMobileIdentityIE::getAsBcd() const
|
|
{
|
|
ByteVector tmp(12); // 12 is overkill.
|
|
decodeIM(tmp);
|
|
return tmp.hexstr();
|
|
|
|
//unsigned i;
|
|
//for (i=0; i<mLen && i<8;i++) { sprintf(&buf[2*i],"%02x",mIdData[i]); }
|
|
//buf[i*2] = 0;
|
|
//return buf;
|
|
}
|
|
|
|
void GmmMobileIdentityIE::parseLV(ByteVector &pp, size_t &rp)
|
|
{
|
|
mPresent = true;
|
|
mLen = pp.readByte(rp); // length of value part, should be 5.
|
|
if (mLen > 8) {
|
|
LLCWARN( "unexpected Mobile Identity length:"<<mLen);
|
|
}
|
|
unsigned typeIdByte = pp.getByte(rp);
|
|
mTypeOfId = typeIdByte & 7;
|
|
|
|
if (isTmsi()) { // It is a tmsi/ptmsi
|
|
if (mLen != 5) { // one byte for type of identity and flags, 4 for tmsi.
|
|
LLCWARN("unexpected Mobile Identity TMSI length:"<<mLen);
|
|
}
|
|
mTmsi = pp.getUInt32(rp+1);
|
|
} else {
|
|
// We dont care what it is, so just copy the entire thing.
|
|
if (mLen > 8) {
|
|
LLCWARN("invalid Mobile Identity TMSI length (must be <=8):"<<mLen);
|
|
mLen = 8;
|
|
}
|
|
//LLCDEBUG << "mobileid:"<<*(pp.begin()+rp)<<*(pp.begin()+rp+1)<<*(pp.begin()+rp+2)<<"\n";
|
|
memcpy(mIdData,pp.begin()+rp,mLen);
|
|
//LLCDEBUG << "mIdData:"<<(int)mIdData[0]<<(int)mIdData[1]<<(int)mIdData[2]<<(int)mIdData[3]<<"\n";
|
|
}
|
|
#if 0
|
|
int datalen = rp-1;
|
|
if (datalen > 8) datalen = 8;
|
|
unsigned char *datap = pp.begin() + rp + 1;
|
|
|
|
mTypeOfId = typeIdByte & 7;
|
|
int isOdd = typeIdByte & 8;
|
|
switch (mTypeOfId) {
|
|
case 0: // No identity
|
|
break;
|
|
case 4: // TMSI or P-TMSI
|
|
if (len != 4) {
|
|
LLCWARN("unexpected Mobile Identity TMSI length:"<<len);
|
|
}
|
|
mTmsi = pp.getUInt32(rp+1);
|
|
break;
|
|
case 1: // IMSI
|
|
case 2: // IMEI
|
|
case 3: // IMEISV
|
|
mIdData[0] = typeIdByte>>4;
|
|
memcpy(&mIdData[1],datap,datalen);
|
|
mNumDigits = (len-1)*2 - (isOdd?1:0);
|
|
break;
|
|
case 5: // TMGI and MBMS session id.
|
|
memcpy(&mIdData[0],datap,datalen);
|
|
break;
|
|
default:
|
|
LLCWARN("unexpecited Mobile Identity type:" << mTypeOfId);
|
|
break;
|
|
}
|
|
// Assume it is p-tmsi and get it.
|
|
#endif
|
|
rp += mLen;
|
|
}
|
|
|
|
void GmmMobileIdentityIE::text(std::ostream&os) const
|
|
{
|
|
if (!mPresent) { os << "not present"; return; }
|
|
switch (mTypeOfId) {
|
|
case 1: os << LOGVAR2("IMSI",getAsBcd()); break;
|
|
case 2: os << LOGVAR2("IMEI",getAsBcd()); break;
|
|
case 3: os << LOGVAR2("IMEISV",getAsBcd()); break;
|
|
case 4: os << LOGHEX2("(P)TMSI",mTmsi); break;
|
|
case 5: os << LOGVAR2("TMGI",ByteVectorTemp((char*)mIdData,mLen).hexstr()); break;
|
|
case 6: os << LOGVAR2("type6",ByteVectorTemp((char*)mIdData,mLen).hexstr()); break;
|
|
case 7: os << LOGVAR2("type7",ByteVectorTemp((char*)mIdData,mLen).hexstr()); break;
|
|
}
|
|
}
|
|
|
|
// write the length and value, but not the IEI type.
|
|
void GmmMobileIdentityIE::appendLV(ByteVector &msg)
|
|
{
|
|
if (mTypeOfId == 4) {
|
|
msg.appendByte(5); // Length of IE for TMSI/PTMSI
|
|
msg.appendByte(0xf4);
|
|
msg.appendUInt32(mTmsi);
|
|
} else {
|
|
msg.appendByte(mLen);
|
|
msg.append(mIdData,mLen);
|
|
}
|
|
}
|
|
|
|
// 3GPP 24.008 10.5.6.5 in octets/sec
|
|
static const unsigned sPeakThroughputTableSize = 10;
|
|
static unsigned sPeakThroughputTable[sPeakThroughputTableSize] = {
|
|
/* 0 */ 0, // 0 means 'subscribed peak throughput'
|
|
/* 1 */ 1000,
|
|
/* 2 */ 2000,
|
|
/* 3 */ 4000,
|
|
/* 4 */ 8000,
|
|
/* 5 */ 16000,
|
|
/* 6 */ 32000,
|
|
/* 7 */ 64000,
|
|
/* 8 */ 128000,
|
|
/* 9 */ 256000,
|
|
};
|
|
|
|
void SmQoS::setPeakThroughput(unsigned bytepSec) // In Bytes/sec
|
|
{
|
|
for (unsigned code = 1; code < sPeakThroughputTableSize; code++) {
|
|
if (bytepSec <= sPeakThroughputTable[code]) { setPeakThroughputCode(code); }
|
|
}
|
|
setPeakThroughputCode(sPeakThroughputTableSize-1);
|
|
}
|
|
|
|
|
|
// Result is Bytes/sec
|
|
unsigned SmQoS::getPeakThroughput()
|
|
{
|
|
unsigned code = getPeakThroughputCode();
|
|
return code <= 9 ? sPeakThroughputTable[code] : 0;
|
|
}
|
|
|
|
// 3GPP 24.008 10.5.6.5 in octets/hour
|
|
static const unsigned sMeanThroughputTableSize = 19;
|
|
static unsigned sMeanThroughputTable[sMeanThroughputTableSize] = {
|
|
/* 0 */ 0,
|
|
/* 1 */ 100,
|
|
/* 2 */ 200,
|
|
/* 3 */ 500,
|
|
/* 4 */ 1*1000,
|
|
/* 5 */ 2*1000,
|
|
/* 6 */ 5*1000,
|
|
/* 7 */ 10*1000,
|
|
/* 8 */ 20*1000,
|
|
/* 9 */ 50*1000,
|
|
/* 10 */ 100*1000,
|
|
/* 11 */ 200*1000,
|
|
/* 12 */ 500*1000,
|
|
/* 13 */ 1*1000000,
|
|
/* 14 */ 2*1000000,
|
|
/* 15 */ 5*1000000,
|
|
/* 16 */ 10*1000000,
|
|
/* 17 */ 20*1000000,
|
|
/* 18 */ 40*1000000
|
|
};
|
|
|
|
// Result is in Bytes/hour
|
|
// 0 means best effort.
|
|
unsigned SmQoS::getMeanThroughput()
|
|
{
|
|
unsigned code = getMeanThroughputCode();
|
|
return code <= 18 ? sMeanThroughputTable[code] : 0;
|
|
}
|
|
|
|
// We probably will not use this, just set 'best effort' using setMeanThroughputCode()
|
|
void SmQoS::setMeanThroughput(unsigned bytepHour) // KBytes/sec
|
|
{
|
|
for (unsigned code = 1; code < sMeanThroughputTableSize; code++) {
|
|
if (bytepHour <= sMeanThroughputTable[code]) { setMeanThroughputCode(code); }
|
|
}
|
|
setMeanThroughputCode(sMeanThroughputTableSize-1);
|
|
}
|
|
|
|
// In kilobits/s. 24.008 table 10.5.165
|
|
void SmQoS::setMaxBitRate(unsigned kbitps, bool uplink)
|
|
{
|
|
unsigned code;
|
|
if (kbitps == 0) { code = 0xff; }
|
|
else if (kbitps <= 64) { code = kbitps; }
|
|
else if (kbitps <= 568) { code = 0x40 + ((kbitps-64)/8); }
|
|
else if (kbitps <= 8640) { code = 0x80 + ((kbitps-576)/64); }
|
|
else {
|
|
// There is a way to encode this using extended bytes, but we wont need it.
|
|
// Just use the max value:
|
|
code = 0xfe;
|
|
}
|
|
if (uplink) {
|
|
setMaxBitRateUplinkCode(code);
|
|
} else {
|
|
setMaxBitRateDownlinkCode(code);
|
|
}
|
|
}
|
|
|
|
// Return -1 if not defined.
|
|
int SmQoS::getMaxBitRate(bool uplink)
|
|
{
|
|
// The IE is variable sized and may not include these fields.
|
|
if (size() <= 6) {return -1;}
|
|
unsigned code = uplink ? getMaxBitRateUplinkCode() : getMaxBitRateDownlinkCode();
|
|
if (code == 0xff) { return 0; }
|
|
switch (code & 0xc0) {
|
|
case 0: return code;
|
|
case 0x40: return 64 + 8*(code & 0x3f);
|
|
default: return 576 + 64*(code & 0x3f);
|
|
}
|
|
}
|
|
|
|
|
|
// 3GPP 24.008 10.5.6.5
|
|
// Set defaults for PS [Packet Switched] services.
|
|
// For streaming video, set udp to true.
|
|
// Specify rates in KBytes/sec, using K=1000 not 1024.
|
|
void SmQoS::defaultPS(unsigned rateDownlink, unsigned rateUplink)
|
|
{
|
|
setDelayClass(4); // best effort
|
|
// Reliability Class:
|
|
// 3 => unacknowledged GTP and LLC, acknowledged RLC, protected data.
|
|
// 4 => unacknowledged GTP and LLC, RLC, protected data.
|
|
// 5 => unacknowledged GTP and LLC, RLC, unprotected data.
|
|
setReliabilityClass(3);
|
|
//mPeakThroughput = 6); // 32k/s 1 is lowest peak throughput
|
|
unsigned peakThroughput = 1000* max(rateUplink,rateDownlink);
|
|
setPeakThroughput(peakThroughput);
|
|
setPrecedenceClass(2); // normal
|
|
setMeanThroughput(0x1f); // best effort
|
|
// Traffic class: 3 => Interactive, 4=background, 2=streaming, 1=conversational
|
|
setTrafficClass(3);
|
|
setDeliveryOrder(2); // unordered
|
|
setDeliveryOfErrSdu(2); // yes, or could use 1 = nodetect
|
|
setMaxSduSize(0x99); // 1520 bytes
|
|
setMaxBitRate(8*rateDownlink,0);
|
|
setMaxBitRate(8*rateUplink,1);
|
|
//setMaxBitRateForUplinkCode(0x3f); // 63kbps; anything from 1 to 0x3f is value * 1k
|
|
//setMaxBitRateForDownlinkCode(0x3f); // 63kbps
|
|
setResidualBER(1); // 5e-2
|
|
setSduErrorRatio(1); // 1e-2
|
|
setTransferDelay(0x10); // 200ms
|
|
setTrafficHandlingPriority(3); // value 1-3.
|
|
setGuaranteedBitRateUplinkCode(0xff); // 0k, ignored for traffic class interactive.
|
|
setGuaranteedBitRateDownlinkCode(0xff); // 0k, ignored for traffic class interactive.
|
|
setSignalingIndication(0); // not optimized
|
|
// Source statistics: 0=unknown, 1 =speech, but:
|
|
// "The Source Statistics Descriptor value is ignored if the Traffic Class
|
|
// is Interactive class or Background Class"
|
|
setSourceStatisticsDescriptor(0);
|
|
}
|
|
|
|
const char *AccessTechnologyType2Name(AccessTechnologyType type)
|
|
{
|
|
switch (type) {
|
|
CASENAME(GSM_P)
|
|
CASENAME(GSM_E)
|
|
CASENAME(GSM_R)
|
|
CASENAME(GSM_1800)
|
|
CASENAME(GSM_1900)
|
|
CASENAME(GSM_450)
|
|
CASENAME(GSM_480)
|
|
CASENAME(GSM_850)
|
|
CASENAME(GSM_750)
|
|
CASENAME(GSM_T380)
|
|
CASENAME(GSM_T410)
|
|
CASENAME(GSM_UNUSED)
|
|
CASENAME(GSM_710)
|
|
CASENAME(GSM_T810)
|
|
default: return "unknown AccessTechnologyType";
|
|
}
|
|
}
|
|
|
|
const char *AccessCapabilities::CapName(CapType type) const
|
|
{
|
|
switch (type) {
|
|
//CASENAME(eAccessTechnologyType)
|
|
CASENAME(RFPowerCapability)
|
|
CASENAME(A5Bits)
|
|
CASENAME(ESInd)
|
|
CASENAME(PS)
|
|
CASENAME(VGCS)
|
|
CASENAME(VBS)
|
|
// multislot capabilities:
|
|
CASENAME(HSCSDMultislotClass)
|
|
CASENAME(GPRSMultislotClass)
|
|
CASENAME(GPRSExtendedDynamicAllocationCapability)
|
|
CASENAME(SMS_VALUE)
|
|
CASENAME(SM_VALUE)
|
|
CASENAME(ECSDMultislotClass)
|
|
CASENAME(EGPRSMultislotClass)
|
|
CASENAME(EGPRSExtendedDynamicAllocationCapability)
|
|
CASENAME(DTMGPRSMultiSlotClass)
|
|
CASENAME(SingleSlotDTM)
|
|
CASENAME(DTMEGPRSMultiSlotClass)
|
|
// Additions in release 99:
|
|
CASENAME(EightPSKPowerCapability)
|
|
CASENAME(COMPACTInterferenceMeasurementCapability)
|
|
CASENAME(RevisionLevelIndicator)
|
|
CASENAME(UMTSFDDRadioAccessTechnologyCapability)
|
|
CASENAME(UMTS384McpsTDDRadioAccessTechnologyCapability)
|
|
CASENAME(CDMA2000RadioAccessTechnologyCapability)
|
|
// Additions in release 4:
|
|
CASENAME(UMTS128McpsTDDRadioAccessTechnologyCapability)
|
|
CASENAME(GERANFeaturePackage1)
|
|
CASENAME(ExtendedDTMGPRSMultiSlotClass)
|
|
CASENAME(ExtendedDTMEGPRSMultiSlotClass)
|
|
CASENAME(ModulationBasedMultislotClassSupport)
|
|
// Additions in release 5:
|
|
CASENAME(HighMultislotCapability)
|
|
//eGMSKPowerClass
|
|
//EightPSKPowerClass
|
|
default: return "unknown CapName";
|
|
}
|
|
}
|
|
|
|
void AccessCapabilities::parseAccessCapabilities(
|
|
ByteVector &bv, // bytevector we are parsing
|
|
size_t &rp, // location in bytevector
|
|
AccessCapabilities *prev, // Previous capabilities or null.
|
|
size_t end) // end of capabilities list
|
|
{
|
|
mCaps[RFPowerCapability] = bv.readField(rp,3);
|
|
if (bv.readField(rp,1)) { mCaps[A5Bits] = bv.readField(rp,7); }
|
|
else if (prev) { mCaps[A5Bits] = prev->mCaps[A5Bits]; }
|
|
mCaps[ESInd] = bv.readField(rp,1);
|
|
mCaps[PS] = bv.readField(rp,1);
|
|
mCaps[VGCS] = bv.readField(rp,1);
|
|
mCaps[VBS] = bv.readField(rp,1);
|
|
if (bv.readField(rp,1)) { // multislot capability struct present
|
|
if (bv.readField(rp,1)) {
|
|
mCaps[HSCSDMultislotClass] = bv.readField(rp,5);
|
|
}
|
|
if (bv.readField(rp,1)) {
|
|
mCaps[GPRSMultislotClass] = bv.readField(rp,5);
|
|
mCaps[GPRSExtendedDynamicAllocationCapability] = bv.readField(rp,1);
|
|
}
|
|
if (bv.readField(rp,1)) {
|
|
mCaps[SMS_VALUE] = bv.readField(rp,4);
|
|
mCaps[SM_VALUE] = bv.readField(rp,4);
|
|
}
|
|
// Additions in release 99: Just scan past some of these, dont bother saving.
|
|
if (rp >= end) return;
|
|
if (bv.readField(rp,1)) { // ECSD multslot class
|
|
bv.readField(rp,5); // Toss it.
|
|
}
|
|
if (rp >= end) return;
|
|
if (bv.readField(rp,1)) { // EGPRS multslot class
|
|
mCaps[EGPRSMultislotClass] = bv.readField(rp,5);
|
|
mCaps[EGPRSExtendedDynamicAllocationCapability] = bv.readField(rp,1);
|
|
}
|
|
if (rp >= end) return;
|
|
if (bv.readField(rp,1)) { // DTM GPRS Multi Slot Class present
|
|
mCaps[DTMGPRSMultiSlotClass] = bv.readField(rp,2);
|
|
mCaps[SingleSlotDTM] = bv.readField(rp,1);
|
|
if (bv.readField(rp,1)) { // DTM EGPRS Multi Slot Class present
|
|
mCaps[DTMEGPRSMultiSlotClass] = bv.readField(rp,2);
|
|
}
|
|
}
|
|
} else if (prev) { // No multislot struct means same as previous.
|
|
for (int i = HSCSDMultislotClass; i <= DTMEGPRSMultiSlotClass; i++) {
|
|
mCaps[i] = prev->mCaps[i];
|
|
}
|
|
}
|
|
|
|
// Additions in release 99
|
|
if (rp >= end) return;
|
|
if (bv.readField(rp,1)) { mCaps[EightPSKPowerCapability] = bv.readField(rp,2); }
|
|
if (rp >= end) return;
|
|
mCaps[COMPACTInterferenceMeasurementCapability] = bv.readField(rp,1);
|
|
mCaps[RevisionLevelIndicator] = bv.readField(rp,1);
|
|
mCaps[UMTSFDDRadioAccessTechnologyCapability] = bv.readField(rp,1);
|
|
mCaps[UMTS384McpsTDDRadioAccessTechnologyCapability] = bv.readField(rp,1);
|
|
mCaps[CDMA2000RadioAccessTechnologyCapability] = bv.readField(rp,1);
|
|
// Additions in release 4:
|
|
if (rp >= end) return;
|
|
mCaps[UMTS128McpsTDDRadioAccessTechnologyCapability] = bv.readField(rp,1);
|
|
if (rp >= end) return;
|
|
mCaps[GERANFeaturePackage1] = bv.readField(rp,1);
|
|
if (rp >= end) return;
|
|
if (bv.readField(rp,1)) {
|
|
mCaps[ExtendedDTMGPRSMultiSlotClass] = bv.readField(rp,2);
|
|
mCaps[ExtendedDTMEGPRSMultiSlotClass] = bv.readField(rp,2);
|
|
}
|
|
if (rp >= end) return;
|
|
mCaps[ModulationBasedMultislotClassSupport] = bv.readField(rp,1);
|
|
// Additions in release 5:
|
|
if (rp >= end) return;
|
|
if (bv.readField(rp,1)) {
|
|
mCaps[HighMultislotCapability] = bv.readField(rp,2);
|
|
}
|
|
// Rest ignored.
|
|
}
|
|
|
|
AccessCapabilities::CapType AccessCapabilities::mPrintList[] = {
|
|
GPRSMultislotClass,
|
|
GPRSExtendedDynamicAllocationCapability,
|
|
GERANFeaturePackage1
|
|
};
|
|
|
|
void AccessCapabilities::text2(std::ostream &os,bool verbose) const
|
|
{
|
|
if (!sEnableMsRaCap ) {return;}
|
|
if (!verbose) { // TODO: how to switch to get the full list?
|
|
// Short list:
|
|
for (unsigned j = 0; j < sizeof(mPrintList)/sizeof(CapType); j++) {
|
|
unsigned cap = mPrintList[j];
|
|
if (mCaps[cap] != -1) { os <<LOGVAR2(CapName((CapType)cap),mCaps[cap]); }
|
|
}
|
|
} else {
|
|
// Full list:
|
|
for (int i = 0; i < CapsMax; i++) {
|
|
if (mCaps[i] != -1) { os <<LOGVAR2(CapName((CapType)i),mCaps[i]); }
|
|
}
|
|
}
|
|
}
|
|
|
|
void AccessCapabilities::text(std::ostream &os) const { text2(os,0); }
|
|
|
|
void MsRaCapability::parseMsRaCapability()
|
|
{
|
|
if (!sEnableMsRaCap ) {return;}
|
|
size_t rp = 0;
|
|
for (int numTechs = 0; numTechs < sMsRaCapMaxTypes; numTechs++) {
|
|
try {
|
|
mCList[numTechs].mTechType = (AccessTechnologyType) readField(rp,4);
|
|
unsigned len = readField(rp,7);
|
|
mCList[numTechs].mValid = true;
|
|
if (mCList[numTechs].mTechType == 0xf) {
|
|
// Special case means same as previous.
|
|
mCList[numTechs].mSameAsPrevious = true;
|
|
readField(rp,1); // Extraneous bit, not included in the length?
|
|
size_t trp = rp;
|
|
mCList[numTechs].mTechType = (AccessTechnologyType) readField(trp,4);
|
|
if (numTechs > 0) { // Otherwise it is an error on the part of the MS.
|
|
*mCList[numTechs].mCaps = *mCList[numTechs-1].mCaps;
|
|
}
|
|
// There are two more fields: GMSK Power Class and 8PSK Power Class
|
|
// which are included in the length and so skipped over without any more code here.
|
|
} else {
|
|
size_t trp = rp;
|
|
AccessCapabilities *prev = numTechs ? &mCList[numTechs-1] : 0;
|
|
mCList[numTechs].parseAccessCapabilities(*this,trp,prev,rp+len);
|
|
}
|
|
// Advance rp.
|
|
rp += len;
|
|
if (rp + 15 >= sizeBits()) { break; } // Not enough room left for anything useful.
|
|
if (readField(rp,1) == 0) { break; } // End of list marker.
|
|
} catch(ByteVectorError) { // oops!
|
|
break; // End of that.
|
|
}
|
|
}
|
|
}
|
|
|
|
void MsRaCapability::text2(std::ostream &os, bool verbose) const
|
|
{
|
|
if (!sEnableMsRaCap ) {return;}
|
|
for (int numTechs = 0; numTechs < sMsRaCapMaxTypes; numTechs++) {
|
|
if (! mCList[numTechs].mValid) {continue;}
|
|
// Dont bother to print the types that Range does not support.
|
|
AccessTechnologyType atype = mCList[numTechs].mTechType;
|
|
switch (atype) {
|
|
case GSM_E: case GSM_850: case GSM_1800: case GSM_1900:
|
|
os << (verbose ? "\t" : " ");
|
|
os <<" MsRaCapability[" << AccessTechnologyType2Name(atype) << "]=(";
|
|
if (mCList[numTechs].mSameAsPrevious) {
|
|
os <<"same";
|
|
} else {
|
|
mCList[numTechs].text2(os,verbose);
|
|
}
|
|
os <<")\n";
|
|
default: continue;
|
|
}
|
|
}
|
|
}
|
|
|
|
void MsRaCapability::text(std::ostream &os) const { text2(os,0); }
|
|
|
|
void L3GmmMsgServiceRequest::gmmParseBody(L3GmmFrame &src, size_t &rp)
|
|
{
|
|
// NOTE: These two nibbles are actually reverse from what's declared in spec!
|
|
mServiceType = src.getNibble(rp,1);
|
|
mCypheringKeySequenceNumber = src.getNibble(rp,0);
|
|
rp++;
|
|
mMobileId.parseLV(src,rp);
|
|
if (rp < src.size()) {
|
|
unsigned iei = src.readIEI(rp);
|
|
if (iei == 0x32) {
|
|
rp++; // skip length
|
|
mPdpContextStatus.mStatus[0] = src.readByte(rp);
|
|
mPdpContextStatus.mStatus[1] = src.readByte(rp);
|
|
}
|
|
}
|
|
}
|
|
|
|
void L3GmmMsgServiceAccept::gmmWriteBody(ByteVector &msg)
|
|
{
|
|
msg.appendByte(0x32);
|
|
msg.appendByte(0x02);
|
|
msg.appendByte(mPdpContextStatus.mStatus[0]);
|
|
msg.appendByte(mPdpContextStatus.mStatus[1]);
|
|
}
|
|
|
|
void L3GmmMsgServiceReject::gmmWriteBody(ByteVector &msg)
|
|
{
|
|
msg.appendByte(mGmmCause);
|
|
}
|
|
|
|
void L3GmmMsgRAUpdateRequest::gmmParseBody(L3GmmFrame &src, size_t &rp)
|
|
{
|
|
unsigned update_type = src.getNibble(rp,0);
|
|
mUpdateType = update_type & 7;
|
|
mFollowOnRequestPending = !!(update_type&8);
|
|
mCypheringKeySequenceNumber = src.getNibble(rp,1);
|
|
rp++;
|
|
mOldRaId.parseElement(src,rp);
|
|
mMsRadioAccessCapability = src.readLVasBV(rp);
|
|
gmParseIEs(src,rp,"RAUpdateRequest");
|
|
}
|
|
|
|
void L3GmmMsgRAUpdateAccept::gmmWriteBody(ByteVector &msg)
|
|
{
|
|
//msg.appendByte(RoutingAreaUpdateAccept);
|
|
msg.appendField(mUpdateResult,4); // nibbles reversed.
|
|
msg.appendField(mForceToStandby,4);
|
|
//msg.appendByte(mPeriodicRAUpdateTimer.getIEValue());
|
|
mPeriodicRAUpdateTimer.appendElement(msg);
|
|
GMMRoutingAreaIdIE mRaId;
|
|
mRaId.raLoad();
|
|
mRaId.appendElement(msg);
|
|
// End of mandatory IEs.
|
|
|
|
// Add the allocated P-TMSI:
|
|
if (mAllocatedPTmsi) {
|
|
msg.appendByte(0x18); // Allocate P-TMSI IEI
|
|
GmmMobileIdentityIE midtmp;
|
|
midtmp.setTmsi(mAllocatedPTmsi);
|
|
midtmp.appendLV(msg);
|
|
}
|
|
|
|
// Add the mobile identity that it sent to us:
|
|
//msg.appendByte(0x23); // MS identity IEI.
|
|
//mMobileId.appendLV(msg);
|
|
|
|
if (mTmsi) {
|
|
msg.appendByte(0x23); // MS identity IEI.
|
|
GmmMobileIdentityIE idtmp;
|
|
idtmp.setTmsi(mTmsi);
|
|
idtmp.appendLV(msg);
|
|
}
|
|
|
|
// 10.5.7.1 PDP Context Status
|
|
// And I quote: "This IE shall be included by the Network". Hmm.
|
|
// If you set this to zeros the MS relinquishes its PDP contexts.
|
|
{
|
|
msg.appendByte(0x32);
|
|
msg.appendByte(2); // length is 2 bytes
|
|
msg.appendByte(mPdpContextStatusCurrent.mStatus[0]);
|
|
msg.appendByte(mPdpContextStatusCurrent.mStatus[1]);
|
|
}
|
|
}
|
|
|
|
void L3GmmMsgRAUpdateReject::gmmWriteBody(ByteVector &msg)
|
|
{
|
|
//msg.appendByte(RoutingAreaUpdateReject);
|
|
msg.appendByte(mGmmCause);
|
|
msg.appendByte(0); // spare half octet and force-to-standby
|
|
}
|
|
|
|
void L3GmmMsgAttachAccept::gmmWriteBody(ByteVector &msg)
|
|
{
|
|
mPeriodicRAUpdateTimer.setSeconds(gConfig.getNum("SGSN.Timer.RAUpdate"));
|
|
mReadyTimer.setSeconds(gConfig.getNum("SGSN.Timer.Ready"));
|
|
|
|
//msg.appendByte(AttachAccept); // message type
|
|
msg.appendField(mForceToStandby,4); // high nibble first.
|
|
msg.appendField(mAttachResult,4);
|
|
//msg.appendByte(mPeriodicRAUpdateTimer.getIEValue());
|
|
mPeriodicRAUpdateTimer.appendElement(msg);
|
|
// Next byte is SMS and TOM8 message priority.
|
|
// I am hard coding them to the lowest value, which is 4.
|
|
msg.appendByte(0x44);
|
|
GMMRoutingAreaIdIE mRaId;
|
|
mRaId.raLoad();
|
|
mRaId.appendElement(msg);
|
|
// End of mandatory elements.
|
|
|
|
// Add the allocated P-TMSI:
|
|
if (mPTmsi) {
|
|
// TLV Allocated P-TMSI, but only if we send it.
|
|
GmmMobileIdentityIE idtmp;
|
|
idtmp.setTmsi(mPTmsi);
|
|
msg.appendByte(0x18); // Allocated P-TMSI IEI
|
|
idtmp.appendLV(msg);
|
|
}
|
|
|
|
// 6-7-2012: Removed this. Per 24.008 9.4.2: Attach Accept Description,
|
|
// I now believe the second mobile identity is included only to set
|
|
// TMSI in case of a combined attach, so we should not include
|
|
// this IE unless using NMO 1 and we support the combined attach.
|
|
//if (mMobileId.mPresent) {
|
|
// msg.appendByte(0x23); // MS identity IEI.
|
|
// mMobileId.appendLV(msg);
|
|
//}
|
|
// Note: you can also set timer T3302, T3319, T3323 values.
|
|
// These are all MM timers in Table 11.3a page 545, and not too interesting.
|
|
}
|
|
|
|
void L3GmmMsgAttachRequest::gmmParseBody(L3GmmFrame &src, size_t &rp)
|
|
{
|
|
mMsNetworkCapability = src.readLVasBV(rp);
|
|
mAttachType = src.getNibble(rp,0);
|
|
mCypheringKeySequenceNumber = src.getNibble(rp,1);
|
|
rp++; // skip to end of nibbles we just read, above.
|
|
mDrxParameter = src.readUInt16(rp);
|
|
mMobileId.parseLV(src,rp);
|
|
mOldRaId.parseElement(src,rp);
|
|
mMsRadioAccessCapability = src.readLVasBV(rp);
|
|
gmParseIEs(src,rp,"GmmAttachRequest");
|
|
}
|
|
|
|
// 9.5.4.2 Mobile Originated Detach Request.
|
|
void L3GmmMsgDetachRequest::gmmParseBody(L3GmmFrame &src, size_t &rp)
|
|
{
|
|
mDetachType = 0xf & src.readByte(rp); // Low nibble is detach type, high nibble is unused.
|
|
mMobileId.parseLV(src,rp);
|
|
while (rp < src.size()) {
|
|
unsigned iei = src.readIEI(rp);
|
|
switch (iei) {
|
|
case 0x18: // P-TMSI
|
|
mMobileId.parseLV(src,rp);
|
|
mMobileIdPresent = true;
|
|
break;
|
|
case 0x19: // P-TMSI signature. Skip it.
|
|
src.skipLV(rp,3,3,"P-TMSI signature");
|
|
break;
|
|
default: // Unknown IEI.
|
|
src.skipLV(rp);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// 9.5.4.1 Network Originated Detach Request.
|
|
void L3GmmMsgDetachRequest:: gmmWriteBody(ByteVector &msg)
|
|
{
|
|
msg.appendByte((mForceToStandby<<4) | mDetachType);
|
|
if (mGmmCausePresent) {
|
|
msg.appendByte(0x25);
|
|
msg.appendByte(mGmmCause);
|
|
}
|
|
}
|
|
|
|
void L3GmmMsgDetachRequest::textBody(std::ostream &os) const
|
|
{
|
|
// The contents are different in uplink and downlink directions.
|
|
// We just print anything that has a 'present' flag set.
|
|
os<<LOGVAR(mDetachType)<<LOGVAR(mForceToStandby);
|
|
if (mGmmCausePresent) { os<<LOGVAR(mGmmCause)<<"="<<GmmCause::name(mGmmCause); }
|
|
if (mMobileIdPresent) { os<<LOGVAR2("mobileId",mMobileId.str()); }
|
|
}
|
|
|
|
void L3GmmMsgDetachAccept::gmmWriteBody(ByteVector &msg)
|
|
{
|
|
msg.appendByte(mForceToStandby&0xf); // low nibble is ForceToStandby, high nibble is unused.
|
|
}
|
|
void L3GmmMsgDetachAccept::textBody(std::ostream &os) const
|
|
{
|
|
// The ForceToStandby is only in the dowlink direction, but oh well...
|
|
os << LOGVAR(mForceToStandby);
|
|
}
|
|
|
|
void L3GmmMsgIdentityRequest::gmmWriteBody(ByteVector &msg)
|
|
{
|
|
msg.appendByte(((int)mForceToStandby<<4) | (int)mIdentityType);
|
|
}
|
|
|
|
void L3GmmMsgIdentityRequest::textBody(std::ostream &os) const
|
|
{
|
|
os<<LOGVAR(mIdentityType) <<LOGVAR(mForceToStandby);
|
|
}
|
|
|
|
void L3GmmMsgIdentityResponse::gmmParseBody(L3GmmFrame &src, size_t &rp)
|
|
{
|
|
mMobileId.parseLV(src,rp);
|
|
}
|
|
|
|
void L3GmmMsgIdentityResponse::textBody(std::ostream &os) const
|
|
{
|
|
os<<LOGVAR2("mobileId",mMobileId.str());
|
|
}
|
|
|
|
void L3GmmMsgAuthentication::gmmWriteBody(ByteVector &msg)
|
|
{
|
|
// Ciphering algorithm nibble - all zero = no ciphering
|
|
// IMEISV request nibble - all zero = not requested.
|
|
msg.appendByte(0);
|
|
// Force to standby nibble - zero = no.
|
|
// A&C reference number - zero is a find reference number.
|
|
msg.appendByte(0);
|
|
// The optional rand IE, because we are trying to authtenticate.
|
|
msg.appendByte(0x21);
|
|
// 128 bit rand.
|
|
msg.append(mRand);
|
|
// CKSN must be included
|
|
msg.appendByte(0x80 | 0x00); //IE;
|
|
}
|
|
|
|
void L3GmmMsgAuthenticationResponse::gmmParseBody(L3GmmFrame &src, size_t &rp)
|
|
{
|
|
// 9.4.10 of 24.008
|
|
unsigned char ACrefnum = src.readByte(rp); // ignore for now
|
|
ByteVector SRES(4);
|
|
|
|
// optional ieis:
|
|
while (rp < src.size()) {
|
|
unsigned iei = src.readIEI(rp);
|
|
switch (iei) {
|
|
case 0x22: // SRES/RES 4 bytes
|
|
for (int i = 0; i < 4; i++)
|
|
SRES.setByte(i,src.readByte(rp));
|
|
mSRES = SRES;
|
|
break;
|
|
case 0x23: // IMEISV TLV of 11 bytes
|
|
// ignore for now;
|
|
case 0x29: // Authentication Response parameter extension (AUTN) TLV 3-14
|
|
// ignore for now;
|
|
{
|
|
unsigned int len = src.readByte(rp);
|
|
for (unsigned int i = 0; i < len; i++) src.readByte(rp);
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// 24.008 9.5.1
|
|
void L3SmMsgActivatePdpContextRequest::smParseBody(L3SmFrame &src, size_t &rp)
|
|
{
|
|
mRequestType = 0;
|
|
//size_t rp = parseSmHeader(src);
|
|
// PD, transaction id, message type already parsed.
|
|
mNSapi = src.readByte(rp);
|
|
mLlcSapi = src.readByte(rp);
|
|
// In 4.008 QoS [Quality of Service] is 3 bytes long.
|
|
// In 24.008 QoS is specified as 13-17 bytes long in the message description
|
|
// but if you look in the 10.5.6.5. IE description it says 3 bytes is ok.
|
|
// The blackberry sends 3 bytes in 2G gprs mode.
|
|
mQoS = src.readLVasBV(rp); // 10.5.6.5
|
|
|
|
// We're going to ignore almost everything else.
|
|
mPdpAddress = src.readLVasBV(rp); // 10.5.6.4
|
|
|
|
// optional ieis:
|
|
while (rp < src.size()) {
|
|
unsigned iei = src.readIEI(rp);
|
|
if ((iei & 0xf0) == 0xa0) {
|
|
mRequestType = iei & 0xf; // 10.5.6.17 Request type
|
|
// 1 is initial request, 2 is handover, 4 is emergency.
|
|
continue;
|
|
}
|
|
int len = src.getByte(rp);
|
|
size_t nextrp = rp + len + 1;
|
|
if (nextrp > src.size()) { // last one will have nextrp == src.size()
|
|
SGSNERROR("invalid message size in ActivatePdpContextRequest");
|
|
return;
|
|
}
|
|
switch (iei) {
|
|
case 0x28:
|
|
mApName = src.readLVasBV(rp); // 10.5.6.1
|
|
continue;
|
|
case 0x27:
|
|
mPco = src.readLVasBV(rp); // 10.5.6.3
|
|
continue;
|
|
default:
|
|
rp = nextrp;
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
|
|
// 3GPP 24.007 11.2.3.1.3 Transaction Identifier.
|
|
// First bit is the TIflag that must be set for a message reply.
|
|
// And I quote: "A message has a TI flag set to "0" when it belongs to transaction initiated by its sender,
|
|
// and to "1" otherwise."
|
|
// Transaction id values > 7 requre an extension byte.
|
|
void L3SmDlMsg::appendTiPd(ByteVector &msg)
|
|
{
|
|
unsigned tiFlag = isSenseCmd() ? 0 : 0x8;
|
|
// Note: nibbles are reversed, as always for L3 messages.
|
|
if (mTransactionId < 7) {
|
|
msg.appendField(tiFlag|mTransactionId,4); // Transaction id. The 0x8 indicates this is a command.
|
|
msg.appendField(GSM::L3GPRSSessionManagementPD,4); // protocol discriminator.
|
|
} else {
|
|
msg.appendField(tiFlag|0x7,4); // Magic value indicates additional transaction id field present.
|
|
msg.appendField(GSM::L3GPRSSessionManagementPD,4); // protocol discriminator.
|
|
msg.appendByte(0x80|mTransactionId); // The 0x80 is a required but meaningless extension indicator.
|
|
}
|
|
}
|
|
|
|
|
|
void L3SmMsgActivatePdpContextAccept::smWriteBody(ByteVector &msg)
|
|
{
|
|
//msg.appendByte(L3SmMsg::ActivatePDPContextAccept);
|
|
msg.appendByte(mLlcSapi);
|
|
// LV: QoS.
|
|
msg.appendByte(mQoS.size());
|
|
msg.append(mQoS);
|
|
msg.appendField(0,4); // "spare half octet"
|
|
msg.appendField(mRadioPriority,4);
|
|
// TLV: Pdp Address
|
|
msg.appendByte(0x2b); // IEI
|
|
msg.appendByte(mPdpAddress.size());
|
|
msg.append(mPdpAddress);
|
|
|
|
// TLV: Protocol Configuration Options.
|
|
// 12-18-2012: The multitech umts modem sends a second PdpContextRequest with an empty PCO,
|
|
// in which the incoming mPco.size is 0.
|
|
// I think this is because the modem heard the previous PdpContextAccept
|
|
// and got all the PCO info it needed, but rejected the command and resent the request for other reasons,
|
|
// so we dont need to send any PCO.
|
|
if (mPco.size()) {
|
|
msg.appendByte(0x27); // IEI
|
|
msg.appendByte(mPco.size());
|
|
msg.append(mPco);
|
|
}
|
|
}
|
|
|
|
// 24.008 9.5.14
|
|
void L3SmMsgDeactivatePdpContextRequest::smParseBody(L3SmFrame &src, size_t &rp)
|
|
{
|
|
//size_t rp = parseSmHeader(src);
|
|
if (rp >= src.size()) {
|
|
SGSNERROR("DeactivatePdpContextRequest too short according to spec");
|
|
mCause = 0;
|
|
return; // But we dont care.
|
|
}
|
|
mCause = src.readByte(rp);
|
|
// optional ieis:
|
|
while (rp < src.size()) {
|
|
unsigned iei = src.readIEI(rp);
|
|
if ((iei & 0xf0) == 0x90) {
|
|
mTearDownIndicator = iei & 0x1; // 10.5.6.10
|
|
continue;
|
|
}
|
|
if (rp >= src.size()) {break;} // This is an error, but we dont care.
|
|
int len = src.getByte(rp);
|
|
if (iei == 0x27) {
|
|
mPco = src.readLVasBV(rp); // 10.5.6.3
|
|
continue;
|
|
}
|
|
// Ignoring; optional MBMS protocol configuration options
|
|
// Ignoring: any IEs not in our spec.
|
|
rp = rp + len + 1;
|
|
}
|
|
}
|
|
|
|
void L3SmMsgDeactivatePdpContextRequest::smWriteBody(ByteVector &msg)
|
|
{
|
|
//msg.appendByte(L3SmMsg::DeactivatePDPContextRequest);
|
|
msg.appendByte(mCause);
|
|
if (mTearDownIndicator) {
|
|
msg.appendByte(0x91);
|
|
}
|
|
// Ignoring:
|
|
// optional protcol configuration options
|
|
// optional MBMS protocol configuration options
|
|
}
|
|
void L3SmMsgDeactivatePdpContextRequest::textBody(std::ostream &os) const
|
|
{
|
|
os<<LOGVAR(mTearDownIndicator) <<LOGVAR(mCause)<<"="<<SmCause::name(mCause)
|
|
<<" PCO="<<mPco.str();
|
|
}
|
|
|
|
void L3SmMsgSmStatus::smWriteBody(ByteVector &msg)
|
|
{
|
|
//msg.appendByte(L3SmMsg::SMStatus);
|
|
msg.appendByte(mCause);
|
|
}
|
|
|
|
void L3GmmMsgRAUpdateReject::textBody(std::ostream &os) const
|
|
{
|
|
os <<LOGVAR2("GmmCause",(int)mGmmCause)<<"="<<GmmCause::name(mGmmCause);
|
|
}
|
|
|
|
void L3SmMsgActivatePdpContextReject::smWriteBody(ByteVector &msg)
|
|
{
|
|
//msg.appendByte(L3SmMsg::ActivatePDPContextReject);
|
|
msg.appendByte((unsigned)mCause);
|
|
}
|
|
|
|
void L3SmMsgActivatePdpContextReject::textBody(std::ostream &os) const
|
|
{
|
|
os <<LOGVAR(mCause)<<"="<<GmmCause::name(mCause);
|
|
}
|
|
|
|
void L3GmmMsgGmmStatus::textBody(std::ostream &os) const
|
|
{
|
|
os <<LOGVAR(mCause)<<"="<<GmmCause::name(mCause);
|
|
}
|
|
|
|
|
|
void L3SmMsgSmStatus::textBody(std::ostream &os) const
|
|
{
|
|
os <<LOGVAR(mCause)<<"="<<SmCause::name(mCause);
|
|
}
|
|
|
|
// When virtual functions are used, C++ requires at least one function to be declared
|
|
// outside the class definition, even if empty.
|
|
void L3SmMsgDeactivatePdpContextAccept::textBody(std::ostream &os) const { /*nothing*/ }
|
|
|
|
void L3GmmMsgRAUpdateComplete::textBody(std::ostream &os) const {/*nothing*/}
|
|
|
|
void GMMRoutingAreaIdIE::parseElement(ByteVector &pp, size_t &rp)
|
|
{
|
|
mMCC[1] = pp.getNibble(rp,1);
|
|
mMCC[0] = pp.getNibble(rp,0);
|
|
mMNC[2] = pp.getNibble(rp+1,1);
|
|
mMCC[2] = pp.getNibble(rp+1,0);
|
|
mMNC[1] = pp.getNibble(rp+2,1);
|
|
mMNC[0] = pp.getNibble(rp+2,0);
|
|
mLAC = pp.getUInt16(rp+3);
|
|
mRAC = pp.getByte(rp+5);
|
|
rp += 6;
|
|
}
|
|
|
|
void GMMRoutingAreaIdIE::appendElement(ByteVector &msg)
|
|
{
|
|
msg.appendByte((mMCC[1]<<4) | mMCC[0]);
|
|
msg.appendByte((mMNC[2]<<4) | mMCC[2]);
|
|
msg.appendByte((mMNC[1]<<4) | mMNC[0]);
|
|
msg.appendUInt16(mLAC);
|
|
msg.appendByte(mRAC);
|
|
}
|
|
|
|
GMMRoutingAreaIdIE::GMMRoutingAreaIdIE()
|
|
{
|
|
mMCC[0] = mMCC[1] = mMCC[2] = 0;
|
|
mMNC[0] = mMNC[1] = mMNC[2] = 0;
|
|
}
|
|
|
|
void GMMRoutingAreaIdIE::text(std::ostream&os) const
|
|
{
|
|
os <<format("MCC=%c%c%c MNC=%c%c%c",
|
|
DEHEXIFY(mMCC[0]), DEHEXIFY(mMCC[1]), DEHEXIFY(mMCC[2]),
|
|
DEHEXIFY(mMNC[0]), DEHEXIFY(mMNC[1]), DEHEXIFY(mMNC[2]));
|
|
os <<LOGVAR2("LAC",mLAC+0)<<LOGVAR2("RAC",mRAC+0);
|
|
}
|
|
|
|
void GMMRoutingAreaIdIE::raLoad()
|
|
{
|
|
const char* wMCC, *wMNC;
|
|
if (Sgsn::isUmts()) {
|
|
wMCC = gConfig.getStr("UMTS.Identity.MCC").c_str();
|
|
wMNC = gConfig.getStr("UMTS.Identity.MNC").c_str();
|
|
mLAC = gConfig.getNum("UMTS.Identity.LAC");
|
|
} else {
|
|
wMCC = gConfig.getStr("GSM.Identity.MCC").c_str();
|
|
wMNC = gConfig.getStr("GSM.Identity.MNC").c_str();
|
|
mLAC = gConfig.getNum("GSM.Identity.LAC");
|
|
}
|
|
mRAC = gConfig.getNum("GPRS.RAC");
|
|
|
|
mMCC[0] = wMCC[0]-'0';
|
|
mMCC[1] = wMCC[1]-'0';
|
|
mMCC[2] = wMCC[2]-'0';
|
|
mMNC[0] = wMNC[0]-'0';
|
|
mMNC[1] = wMNC[1]-'0';
|
|
// 24.008 10.5.5.15 says if only two digits, MNC[2] is 0xf.
|
|
mMNC[2] = wMNC[2] ? (wMNC[2]-'0') : 0xf;
|
|
}
|
|
|
|
}; // namespace
|