OpenBTS-UMTS/UMTS/UMTSCLI.cpp

538 lines
18 KiB
C++

/*
* OpenBTS provides an open source alternative to legacy telco protocols and
* traditionally complex, proprietary hardware systems.
*
* Copyright 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 "UMTSConfig.h"
#include "UMTSTransfer.h"
#include "URRCTrCh.h"
#include "URRCRB.h"
#include "MACEngine.h"
#include "URRCMessages.h"
#include "URLC.h"
#include "URRC.h"
#include "GPRSL3Messages.h" // For SmQoS
#include <stdlib.h> // for rand
#include "asn_system.h"
namespace ASN {
//#include "BIT_STRING.h"
#include "UL-CCCH-Message.h"
#include "DL-CCCH-Message.h"
#include "UL-DCCH-Message.h"
#include "DL-DCCH-Message.h"
#include "InitialUE-Identity.h"
//#include "asn_SEQUENCE_OF.h"
//#include "RRCConnectionRequest.h"
//#include "RRCConnectionSetup.h"
};
extern FILE *gLogToFile;
namespace UMTS {
void handleRrcConnectionRequest(ASN::RRCConnectionRequest_t *msg); // This kinda sucks.
// Return rand in the range 0..maxval inclusive.
static int rangerand(int minval, int maxval, unsigned *pseed)
{
int range = maxval - minval;
// Note: Should be /(RAND_MAX+1) but that overflows, but we will post-bound.
double randfrac = ((double)rand_r(pseed) / RAND_MAX); // range 0..1
int result = minval + (int) round(randfrac * range);
return RN_BOUND(result,minval,maxval); // Double check that it is in range.
}
extern int gFecTestMode;
class MacTester : public MacEngine
{
void writeLowSide(const TransportBlock&tb) {
printf("Received uplink TB size %d\n",tb.size());
std::cout <<"tb="<<(ByteVector)tb;
}
void macService(int fn) {}
};
void testCreateFakeUE_Identity(ASN::InitialUE_Identity_t *ueIdentity)
{
// I dont think the UE will send an IMSI right off, but it was the easiest encoding.
memset(ueIdentity,0,sizeof(ASN::InitialUE_Identity_t));
ueIdentity->present = ASN::InitialUE_Identity_PR_imsi;
setASN1SeqOfDigits((void*)&ueIdentity->choice.imsi.list,"1234567890");
}
// This is an uplink message, so we only ever create this message for testing.
static void testCreateRRCConnectionRequest(ASN::RRCConnectionRequest_t *ccrmsg)
{
memset(ccrmsg,0,sizeof(ASN::RRCConnectionRequest_t));
// The only part of this message we need to fake is the UE id.
testCreateFakeUE_Identity(&ccrmsg->initialUE_Identity);
// Here is some other goo we dont care about but that has to be set to encode it:
asn_long2INTEGER(&ccrmsg->establishmentCause,ASN::EstablishmentCause_originatingConversationalCall);
asn_long2INTEGER(&ccrmsg->protocolErrorIndicator,ASN::ProtocolErrorIndicator_noError);
}
static UEInfo *testCreateFakeUe()
{
// Test Radio Bearer Setup message stand-alone.
//ASN::InitialUE_Identity_t imsi;
//testCreateFakeUE_Identity(&imsi);
ByteVector fakeimsi=ByteVector("1234567890");
AsnUeId imsiId(fakeimsi);
return new UEInfo(&imsiId);
}
static const unsigned sMaxTestVectors = 100;
static ByteVector *svectors[sMaxTestVectors];
static unsigned sTestRecvVectorNum;
static unsigned sTestFailCnt;
static unsigned sseed;
static unsigned sNumTestVectors;
// Receives vectors from recv2 during rlc test.
void testRlcRecv(URlcUpSdu &sdu, RbId rbid)
{
assert(rbid == 1 || rbid == 2);
if (rbid == 1) {
// No data vectors are supposed to pop out of recv1.
// It should only be getting control pdus.
printf("ERROR: recv1 got:%s\n",sdu.hexstr().c_str());
}
if (sTestRecvVectorNum >= sNumTestVectors) {
assert(0);
}
assert(svectors[sTestRecvVectorNum]);
ByteVector *expect = svectors[sTestRecvVectorNum];
if (sdu != *expect) {
sTestFailCnt++;
printf("RLC test vector %d did not match: expected=%s got=%s\n",
sTestRecvVectorNum,sdu.hexstr().c_str(),expect->hexstr().c_str());
} else {
printf("testRlcRecv: test vector %d matched\n",sTestRecvVectorNum);
//printf("testRlcRecv: test vector %d matched %s=%s\n",
//sTestRecvVectorNum,sdu.hexstr().c_str(),expect->hexstr().c_str());
}
sTestRecvVectorNum++;
}
// For testing, copy data from the trans to the recv.
// Return how many.
static unsigned rlcTransfer(URlcTrans *trans, URlcRecv *recv, int percentloss,int dir)
{
printf("rlcTransfer dir=%d\n",dir);
ByteVector *pdu;
unsigned pducnt = 0;
while ((pdu = trans->rlcReadLowSide())) {
pducnt++;
if (percentloss && rangerand(1,100,&sseed) < percentloss) {
//URlcPdu *updu = dynamic_cast<URlcPdu*>(pdu);
//printf("Tossing pdu number %d %s\n",pducnt,updu->ByteVector::str().c_str());
continue;
}
// Turn the bytevector into a bitvector:
BitVector bits(pdu->sizeBits());
bits.unpack(pdu->begin());
delete pdu;
recv->rlcWriteLowSide(bits);
}
return pducnt;
}
static unsigned rlcRun(URlcPair *pair1,URlcPair *pair2,int percentloss, bool statuslossless)
{
int pducnt = 0;
URlcTrans *trans1 = pair1->mDown, *trans2 = pair2->mDown;
URlcRecv *recv1 = pair1->mUp, *recv2 = pair2->mUp;
while (1) {
int cnt = rlcTransfer(trans1,recv2,percentloss,1);
if (statuslossless) percentloss = 0;
cnt += rlcTransfer(trans2,recv1,percentloss,2);
if (cnt == 0) break;
pducnt += cnt;
}
return pducnt;
}
//static void testRlc(const char*subcmd, int argc, char **argv)
int rlcTest(int argc, char** argv, ostream& os)
{
gLogToConsole = true;
if (gLogToFile == NULL) { gLogToFile = fopen("debug.log","w"); }
int argi = 1; // The number of arguments consumed so far; argv[0] was name of cmd
URlcMode mode = URlcModeAm; //URlcModeTm, URlcModeUm,
int percentloss = 0;
int reset1 = 0;
int reset2 = 0;
bool ps = 0;
sseed = 1;
sNumTestVectors = sMaxTestVectors;
bool statuslossless = 0; // Let status pdus go through lossless.
while (argi < argc) {
if (0 == strcmp(argv[argi],"-loss") && argi+1<argc) {
percentloss = atoi(argv[argi+1]);
argi += 2;
} else if (0 == strcmp(argv[argi],"-um")) {
mode = URlcModeUm;
argi++;
} else if (0 == strcmp(argv[argi],"-tm")) {
mode = URlcModeTm;
argi++;
} else if (0 == strcmp(argv[argi],"-am")) {
mode = URlcModeAm;
argi++;
} else if (0 == strcmp(argv[argi],"-ps")) {
ps = 1;
argi++;
} else if (0 == strcmp(argv[argi],"-s")) {
statuslossless = 1;
argi++;
} else if (0 == strcmp(argv[argi],"-d")) {
rrcDebugLevel = 0xffff; // Debugging this now...
argi++;
} else if (0 == strcmp(argv[argi],"-n") && argi+1<argc) {
sNumTestVectors = atoi(argv[argi+1]);
argi += 2;
} else if (0 == strcmp(argv[argi],"-seed") && argi+1<argc) {
sseed = atoi(argv[argi+1]);
argi += 2;
} else if (0 == strcmp(argv[argi],"-reset1") && argi+1<argc) {
// Start a reset at the indicated sdu number.
reset1 = atoi(argv[argi+1]);
argi += 2;
} else if (0 == strcmp(argv[argi],"-reset2") && argi+1<argc) {
// Start a reset at the indicated sdu number.
reset2 = atoi(argv[argi+1]);
argi += 2;
} else {
printf("unrecognized: %s\n",argv[argi]);
help:
printf("rlctest -am|-tm|-um -s -d -ps -n <numvectors> -loss <percentloss> -seed <randomseed> -reset[12] <pdunum>\n");
printf("note: -ps = packet-switched-config -d = debug; -s = lossless transmission for status\n");
return 0;
}
}
if (sNumTestVectors > sMaxTestVectors) {
printf("too many test vectors\n");
goto help;
}
if (percentloss < 0 || percentloss > 90) {
printf("rrctest invalid percentloss arg\n");
goto help;
}
URlcPair *pair1, *pair2;
{
UEInfo *uep = testCreateFakeUe();
RBInfo rb;
if (mode == URlcModeAm) { // strstr(subcmd,"am"))
// I tried using an existing RLC but it doesnt work because
// the MAC service loop is running on it, so manufacture our own:
if (ps) {
rb.defaultConfigRlcAmPs();
} else {
rb.defaultConfigSrbRlcAm();
}
} else if (mode == URlcModeUm) { //strstr(subcmd,"um"))
rb.defaultConfig0CFRb(1); // This is an RLC-UM configuration
} else { // mode TM
rb.ul_RLC_Mode(URlcModeTm);
rb.dl_RLC_Mode(URlcModeTm);
}
//else {
//printf("Invalid rrctest rlc subcommand\n");
//goto help;
//}
RrcTfs *dltfs = gRrcDcchConfig->getDlTfs(0); // this will do
rb.TimerPoll(20); // Speed this way up for testing.
rb.rb_Identity(1);
pair1 = new URlcPair(&rb,dltfs,uep,0);
rb.rb_Identity(2);
pair2 = new URlcPair(&rb,dltfs,uep,0);
}
// Test RLCs are hooked up like this:
// generated vectors -> trans1 (AMT1) -> code below -> recv2 (AMR2) -> testRlcRecv()
// recv1 (AMR1) <- code below <- trans2 (AMT2),
// Since we are sending data only one way, the latter will only
// be control pdus generated inside trans2 and only for AM mode.
URlcTrans *trans1 = pair1->mDown, *trans2 = pair2->mDown;
URlcRecv *recv1 = pair1->mUp, *recv2 = pair2->mUp;
// Data from the high side of the receiver goes here.
// see URlcRecv::rlcSendHighSide()
recv1->rlcSetHighSide(testRlcRecv);
recv2->rlcSetHighSide(testRlcRecv);
sTestRecvVectorNum = 0;
sTestFailCnt = 0;
// We want to randomize: vector content, size, and how many incoming vectors per TTI.
int pducnt = 0;
//int loss;
for (unsigned n = 0; n < sNumTestVectors; n++) {
// Generate a random sized test vector.
int len = rangerand(2,100,&sseed); // 2..100 bytes.
svectors[n] = new ByteVector(len);
svectors[n]->setField(0,n,16);
for (int j = 2; j < len; j++) {
svectors[n]->setByte(j,j); // Fill the rest of the test vec with numbers.
}
// Write the test vec to the RLC-AM transmitter.
trans1->rlcWriteHighSide(*svectors[n],false,0,string("testvec"));
// Pull data out of either transmitter low side and send back to recv.
// Keep doing this until they stop talking to each other.
pducnt += rlcRun(pair1,pair2,percentloss,statuslossless);
if (reset1 && (int)n == reset1) { trans1->triggerReset(); }
if (reset2 && (int)n == reset2) { trans2->triggerReset(); }
}
/***
loss = percentloss;
while (1) {
int cnt = rlcTransfer(trans1,recv2,loss,1);
if (statuslossless) loss = 0;
cnt += rlcTransfer(trans2,recv1,loss,2);
if (cnt == 0) break;
pducnt += cnt;
}
***/
// After the last test, if the PDU carrying the Poll bit was lost,
// it should be resent when the Timer_Poll expires, which is 1 second.
int activity;
do {
printf("Sleeping...\n");
sleepf(0.03);
activity = rlcRun(pair1,pair2,statuslossless?0:percentloss,statuslossless);
pducnt += activity;
// The resets have long timers and we have to wait for them...
if (activity == 0 && (reset1 | reset2)) {
printf("Sleeping 0.4...\n");
sleepf(0.4);
activity = rlcRun(pair1,pair2,statuslossless?0:percentloss,statuslossless);
}
} while (activity);
/***
loss = statuslossless ? 0 : percentloss;
int activity;
do {
activity = 0;
printf("Sleeping...\n");
sleep(2);
while (1) {
int cnt = rlcTransfer(trans1,recv2,loss,1) + rlcTransfer(trans2,recv1,loss,2);
if (cnt == 0) break;
activity=1;
}
} while (activity);
***/
printf("testRlc: received %d sdus, %d pdus , expected=%d failed=%d\n",
sTestRecvVectorNum, pducnt, sNumTestVectors, sTestFailCnt);
os <<"\nTrans1:"; trans1->text(os);
os <<"\nRecv1:"; recv1->text(os);
os <<"\nTrans2:"; trans2->text(os);
os <<"\nRecv2:"; recv2->text(os);
os <<"\n";
return 0;
}
// This can be called by the OpenBTS-UMTS command line interface via the CLI module.
int rrcTest(int argc, char** argv, ostream& os)
{
if (argc <= 1) { return 1; }
//gLogToConsole = true;
//if (gLogToFile == NULL) { gLogToFile = fopen("debug.log","w"); }
int argi = 1; // The number of arguments consumed so far; argv[0] was name of cmd
char *subcmd = argv[argi++];
char *arg1 = (argc > argi) ? argv[argi] : NULL;
if (0==strcmp(subcmd,"rbrelease")) {
UEInfo *uep = testCreateFakeUe();
DCHFEC *dch = gChannelTree.chChooseByBW(12000);
RrcMasterChConfig *newConfig = &uep->mUeDchConfig;
bool useTurbo = true;
RbId rbid = 5;
newConfig->rrcConfigDchPS(dch, rbid, useTurbo);
sendRadioBearerRelease(uep, 1<<rbid, false);
sendRadioBearerRelease(uep, 1<<rbid, true);
} else if (0==strcmp(subcmd,"cc")) {
extern void testCCProgramming();
testCCProgramming();
} else if (0==strcmp(subcmd,"level")) {
if (!arg1) { printf("missing argument\n"); return 0; }
rrcDebugLevel = strtol(arg1,NULL,0); // strtol allows hex
} else if (0==strncmp(subcmd,"fec",3)) {
#if USE_OLD_FEC
// Test the L1FEC encoder/decoder.
int testnum = 1;
if (arg1) { testnum = atoi(arg1); }
printf("rrctest fec %d\n",testnum);
gFecTestMode = testnum; // bursts sent on FACH will come back on RACH.
MacTester macTester;
RACHFEC *originalRach = gNodeB.mRachFec;
if (0==strcmp(subcmd,"fec2")) {
// The RACH and FACH are not the same size any more, so create a temp rach.
// Make a dummy RACH to match the FACH: SF=64,PB=12,TB=512,TTI=10ms.
gNodeB.mRachFec = new RACHFEC(64,1,12,512,TTI10ms);
}
macTester.macSetDownstream(gNodeB.mFachFec);
MacEngine *saveme = gNodeB.mRachFec->l1SetUpstream(&macTester,true);
//TransportBlock tb(gNodeB.mRachFec->decoder()->trBkSz());
TransportBlock tb(gNodeB.mFachFec->encoder()->trBkSz());
printf("Sending %d bits\n",tb.size());
tb.zero();
gNodeB.mFachFec->l1WriteHighSide(tb);
tb.fillField(0,0x1234,16);
gNodeB.mFachFec->l1WriteHighSide(tb);
// Put it back the way we found it:
gNodeB.mRachFec = originalRach;
gNodeB.mRachFec->l1SetUpstream(saveme,true);
#endif
} else if (0==strcmp(subcmd,"2")) {
// Test the RRC Connection Request/Setup, which will create a UEInfo and send
// an RRC Connection Setup message down it on SRB0, where it disappears because
// no one is listening, but you can see it get fragmented by the CCCH RLC,
// have the MAC header attached, and go through the encoder.
gFecTestMode = 1; // bursts sent on FACH will come back on RACH.
ASN::RRCConnectionRequest_t ccrmsg;
testCreateRRCConnectionRequest(&ccrmsg);
handleRrcConnectionRequest(&ccrmsg);
// Do it again, to see the sequence number stays the same.
handleRrcConnectionRequest(&ccrmsg);
} else if (0==strcmp(subcmd,"3")) {
// Test the RRC Connection Request/Setup more thoroughly,
// by encoding the RRC Connection Request as an uplink message,
// and sending it all the way down through L1 and back, so that
// it appears as though it had arrived on the air, and will
// cause an RRC Connection Setup to be sent.
gFecTestMode = 1; // FEC encoder bursts are reflected back on decoder channel.
ASN::UL_CCCH_Message ulmsg;
memset(&ulmsg,0,sizeof(ulmsg));
ulmsg.message.present = ASN::UL_CCCH_MessageType_PR_rrcConnectionRequest;
testCreateRRCConnectionRequest(&ulmsg.message.choice.rrcConnectionRequest);
char errbuf[200];
size_t errlen = 200;
errbuf[0] = 0;
int stat = asn_check_constraints(&ASN::asn_DEF_UL_CCCH_Message,&ulmsg,errbuf,&errlen);
printf("asn_check_constraints returned %d %s\n",stat,errbuf);
// Try sending the ccrmsg through the fecs. We cannot send it through the normal
// downlink RLC because SRB0 is assymetric: UM down and TM up.
// Send it to MAC directly.
// This pdu is not nearly the correct size, but mac should zero pad it to TB size.
ByteVector pdu(100);
const char *help = "RRCConnectionRequest";
if (!uperEncodeToBV(&ASN::asn_DEF_UL_CCCH_Message, &ulmsg,pdu,help)) {
printf("failed to encode %s\n",help);
return 0;
}
printf("==== RRCConnectionRequest sizebits=%d\n",pdu.sizeBits());
// Try decoding immediately:
ASN::UL_CCCH_Message *msg1 = (ASN::UL_CCCH_Message*)
uperDecodeFromByteV(&ASN::asn_DEF_UL_CCCH_Message, pdu);
printf("==== uperDecode returned: %p\n",msg1);
if (1) {
// Try sending ASN directly to rrcRecvCcchMessage.
BitVector foo(pdu.sizeBits()); // This would not work if it were not divisible by 8.
foo.unpack(pdu.begin());
rrcRecvCcchMessage(foo,0);
printf("FINISHED\n");
return 0;
}
// We have to format this as an UPLINK block, so that it will be recognized by MAC
// as such on the return trip.
//MaccTbDlCcch tb(gNodeB.mFachFec->l1GetDlTrBkSz(),&pdu); // Will go down on fach.
MacTbDl tb(gNodeB.mFachFec->l1GetDlTrBkSz());
tb.fillField(0,0,2); // On RACH, this is the TCTF field for a CCCH uplink block.
tb.segment(2,pdu.sizeBits()).unpack(pdu.begin()); // add the pdu data.
// Find the mac and send it off.
#if USE_OLD_FEC
MacEngine *mactmp = gNodeB.mRachFec->decoder()->mUpstream; // Then come up on rach.
MaccSimple *macc = dynamic_cast<MaccSimple*>(mactmp);
macc->sendDownstreamTb(tb);
#else
printf("This test uminplemented\n");
#endif
} else if (0==strcmp(subcmd,"4")) {
rrcDebugLevel = 0xffff; // Debugging this now...
UEInfo *uep = testCreateFakeUe();
// We have to start with the UE in CELL_FACH state or
// assertions will fail about moving from CELL_FACH to CELL_DCH state.
// May need to hook up the FACH rlcs too?
uep->ueConnectRlc(gRrcDcchConfig,stCELL_FACH);
uep->ueSetState(stCELL_FACH);
// Manufacture a Quality-of-Service. Currently we only use two fields.
SGSN::SmQoS qos(12);
qos.setMaxBitRate(16*8,0); // in kbits/sec
qos.setPeakThroughput(16000); // this comes in bytes/sec,
SGSN::RabStatus result;
//result = rrcAllocateRabForPdp(uep->mURNTI,5,qos);
result = SGSN::SgsnAdapter::allocateRabForPdp(uep->mURNTI,5,qos);
printf("result=%d failcode=%d\n",result.mStatus,result.mFailCode);
result.text(std::cout);
/***
// Allocate a physical channel.
DCHFEC *dch = gChannelTree.chChooseBySF(256);
if (dch == NULL) {
printf("oops: NULL dch\n");
return 0;
}
// TODO: We need a config...
sendRadioBearerSetup(uep, masterConfig, dch,true);
***/
} else if (0==strcmp(subcmd,"5")) {
rrcDebugLevel = 0xffff; // Debugging this now...
UEInfo *uep = testCreateFakeUe();
uep->ueConnectRlc(gRrcDcchConfig,stCELL_FACH);
uep->ueSetState(stCELL_FACH);
sendRrcConnectionRelease(uep);
} else if (0==strcmp(subcmd,"6")) {
rrcDebugLevel = 0xffff; // Debugging this now...
UEInfo *uep = testCreateFakeUe();
uep->ueConnectRlc(gRrcDcchConfig,stCELL_FACH);
uep->ueSetState(stCELL_FACH);
sendRadioBearerRelease(uep,1<<5,1);
sendCellUpdateConfirm(uep);
} else {
os << "invalid sub-command\n";
return 2; // bad command
}
return 0; // aka SUCCESS
}
};