/* * 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 // 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(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 -loss -seed -reset[12] \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<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(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 } };