OpenBTS/SMS/SMSMessages.cpp
svangundy 50f2a2de43 Fixed mismatch in files between openbts and smqueue.
Fixed all places where + was not being sent. Plus can't be sent as a digit it has to be encoded as an international type.
Fixed error where # is displayed in reply address for +.
SMS reply not working with plus in from address.
Fixed all places in smqueue that crash on purpose when bad data is found.
Fixed several other crashes related to handling missing tags.
Added support for receiving addresses with and without +. Whether a + is received or not depends on what the sender enters.
3gpp mode is working.
Fixed error where smqueue gets into an infinite loop when restarted with bad messages in the queue.
2014-04-08 12:43:39 +02:00

831 lines
20 KiB
C++

/*
* Copyright 2008, 2009, 2010, 2014 Free Software Foundation, Inc.
*
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.
* This software is distributed under multiple licenses; see the COPYING file in the main directory for licensing information for this specific distribuion.
*
* This use of this software may be subject to additional restrictions.
* See the LEGAL file in the main directory for details.
*/
#include <stdint.h>
#include <stdio.h>
#include <cstdio>
#include "SMSMessages.h"
#include <Logger.h>
using namespace std;
using namespace GSM;
using namespace SMS;
ostream& SMS::operator<<(ostream& os, CPMessage::MessageType val)
{
switch(val) {
case CPMessage::DATA: os<<"CP-DATA"; break;
case CPMessage::ACK: os<<"CP-ACK"; break;
case CPMessage::ERROR: os<<"CP-ERROR"; break;
default :
os<<hex<<"0x"<<(int)val<<dec; break;
}
return os;
}
CPMessage * SMS::CPFactory(CPMessage::MessageType val)
{
switch(val) {
case CPMessage::DATA: return new CPData();
case CPMessage::ACK: return new CPAck();
case CPMessage::ERROR: return new CPError();
default: {
LOG(NOTICE) << "no factory support for MTI="<<val;
return NULL;
}
}
}
// (pat) This parses an incoming SMS message from the MS, called from
CPMessage * SMS::parseSMS( const GSM::L3Frame& frame )
{
CPMessage::MessageType MTI = (CPMessage::MessageType)(frame.MTI());
LOG(DEBUG) << "MTI="<<MTI;
CPMessage * retVal = CPFactory(MTI);
if( retVal==NULL ) return NULL;
retVal->TI(frame.TI());
// Documentation courtesy pat:
// The L3Message::CPMessage is a base class for CPData, CPAck, CPError, one of which is created by the CPFactory above.
// The below calls L3Message::parse which calls the parseBody from the derived class.
// For CPAck and CPError, parseBody is null (or worse, assert out - a former bug.)
// For CPData messages: calls CPData::parseBody which then calls L3ProtocolElement::parseLV which calls:
// CPUserData::parseV, which just copies the data into CPUserData::mRPDU; which is an L3Frame::RLFrame
retVal->parse(frame);
LOG(DEBUG) << *retVal;
return retVal;
}
/*
* Returns
RPData*
*/
RPData *SMS::hex2rpdata(const char *hexstring)
{
RPData *rp_data = NULL;
BitVector2 RPDUbits(strlen(hexstring)*4);
if (!RPDUbits.unhex(hexstring)) {
return NULL;
}
LOG(DEBUG) << "SMS RPDU bits: " << RPDUbits;
try {
RLFrame RPDU(RPDUbits);
LOG(DEBUG) << "SMS RPDU: " << RPDU;
rp_data = new RPData();
rp_data->parse(RPDU);
LOG(DEBUG) << "SMS RP-DATA " << *rp_data;
}
catch (SMSReadError) {
LOG(WARNING) << "SMS parsing failed (above L3)";
// TODO:: send error back to the phone
delete rp_data;
rp_data = NULL;
}
catch (L3ReadError) {
LOG(WARNING) << "SMS parsing failed (in L3)";
// TODO:: send error back to the phone
delete rp_data;
rp_data = NULL;
}
return rp_data;
}
TLMessage *SMS::parseTPDU(const TLFrame& TPDU, bool directionUplink)
{
LOG(DEBUG) << "SMS: parseTPDU MTI=" << (TLMessage::MessageType)TPDU.MTI();
if (directionUplink) {
// Handle just the uplink cases.
switch ((TLMessage::MessageType)TPDU.MTI()) {
case TLMessage::DELIVER_REPORT:
case TLMessage::STATUS_REPORT:
// FIXME -- Not implemented yet.
LOG(WARNING) << "Unsupported TPDU type: " << (TLMessage::MessageType)TPDU.MTI();
return NULL;
case TLMessage::SUBMIT: {
TLSubmit *submit = new TLSubmit;
submit->parse(TPDU);
LOG(INFO) << "SMS SMS-SUBMIT " << *submit;
return submit;
}
default:
return NULL;
}
} else {
switch ((TLMessage::MessageType)TPDU.MTI()) {
// 10-2013: Pat added the DELIVER which is the downlink message so we can parse it for reporting purposes.
case TLMessage::DELIVER: {
TLDeliver *deliver = new TLDeliver(TPDU);
return deliver;
}
default:
LOG(WARNING) << "parsing unsupported TPDU type: " << (TLMessage::MessageType)TPDU.MTI();
return NULL;
}
}
}
void CPMessage::text(ostream& os) const
{
os << (CPMessage::MessageType)MTI();
os <<LOGHEX2("TI",mTI);
}
void CPMessage::write(L3Frame& dest) const
{
// We override L3Message::write for the transaction identifier.
dest.resize(bitsNeeded());
size_t wp = 0;
// Note that 1/2-octet fields are reversed relative to Table 7.1.
dest.writeField(wp,mTI,4);
dest.writeField(wp,PD(),4);
dest.writeField(wp,MTI(),8);
writeBody(dest, wp);
}
void CPData::parseBody( const L3Frame& src, size_t &rp )
{
mData.parseLV(src,rp);
}
void CPData::writeBody( L3Frame& dest, size_t &wp ) const
{
mData.writeLV(dest,wp);
}
void CPData::text(ostream& os) const
{
CPMessage::text(os);
os << " RPDU=(" << mData << ")";
}
void CPError::writeBody( L3Frame& dest, size_t &wp ) const
{
mCause.writeV(dest,wp);
}
// called from SMS::parseSMS.
void CPUserData::parseV(const L3Frame& src, size_t &rp, size_t expectedLength)
{
unsigned numBits = expectedLength*8;
// WARNING: segmentCopyTo does not modify the size of the target so we must do it.
mRPDU.resize(numBits);
int actualLength = (int) src.size() - rp;
if (actualLength < (int)numBits) {
// The length field (third byte) in the L3Frame was bogus, less than the remaining length of the frame.
LOG(ERR)<<"Invalid SMS frame:"<<LOGVAR(expectedLength*8) <<" (from L3 header)"<<LOGVAR(actualLength);
L3_READ_ERROR;
}
src.segmentCopyTo(mRPDU,rp,numBits);
rp += numBits;
}
void CPUserData::writeV(L3Frame& dest, size_t &wp) const
{
unsigned numBits = mRPDU.size();
mRPDU.copyToSegment(dest,wp,numBits);
wp += numBits;
}
ostream& SMS::operator<<(ostream& os, RPMessage::MessageType val)
{
switch(val) {
case RPMessage::Data: os<<"RP-DATA"; break;
case RPMessage::Ack: os<<"RP-ACK"; break;
case RPMessage::Error: os<<"RP-ERROR"; break;
case RPMessage::SMMA: os<<"RP-SMMA"; break;
default :
os<<hex<<"0x"<<(int)val<<dec; break;
}
return os;
}
ostream& SMS::operator<<(ostream& os, const RPMessage& msg)
{
msg.text(os);
return os;
}
void RPUserData::parseV(const L3Frame& src, size_t &rp, size_t expectedLength)
{
LOG(DEBUG) << "src=" << src << " (length=" << src.length() << ") rp=" << rp << " expectedLength=" << expectedLength;
unsigned numBits = expectedLength*8;
if (rp+numBits > src.size()) {
SMS_READ_ERROR;
}
mTPDU.resize(numBits);
LOG(DEBUG) << "mTPDU length=" << mTPDU.length() << " data=" << mTPDU;
src.segmentCopyTo(mTPDU,rp,numBits);
rp += numBits;
}
void RPUserData::writeV(L3Frame& dest, size_t &wp) const
{
unsigned numBits = mTPDU.size();
mTPDU.copyToSegment(dest,wp,numBits);
wp += numBits;
}
void RPMessage::parse(const RLFrame& frame)
{
size_t rp = 8;
// FIXME -- A consistency check of PD and MTI would be good.
mReference = frame.readField(rp,8);
parseBody(frame,rp);
}
void RPMessage::write(RLFrame& dest) const
{
// All relay-layer messages (GSM 04.11 7.3) have the same 2-byte header.
dest.resize(bitsNeeded());
size_t wp=0;
dest.writeField(wp,0,5);
// Note that we add one for the n->ms direction.
// See GSM 04.11 8.2.2 Table 8.3
dest.writeField(wp,MTI()+1,3);
dest.writeField(wp,mReference,8);
// After the header, fill in the body.
writeBody(dest,wp);
}
void RPMessage::text(ostream& os) const
{
os << MTI() << " ref=" << mReference;
}
void RPData::parseBody(const RLFrame& src, size_t &rp)
{
// GSM 04.11 7.3.1.2
mOriginator.parseLV(src,rp);
mDestination.parseLV(src,rp);
mUserData.parseLV(src,rp);
//LOG(DEBUG) << "parseBody orig=" << mOriginator << " dest=" << mDestination;
}
void RPData::writeBody(RLFrame& dest, size_t& wp) const
{
// GSM 04.11 7.3.1.1
// This is the downlink form.
mOriginator.writeLV(dest,wp);
//LOG(DEBUG) << "writeBody orig=" << mOriginator << " dest=" << mDestination;
mDestination.writeLV(dest,wp);
mUserData.writeLV(dest,wp);
}
void RPData::text(ostream& os) const
{
RPMessage::text(os);
os << " origSMSC=(" << mOriginator << ")";
os << " destSMSC=(" << mDestination << ")";
os << " TPDU=(" << TPDU() << ")";
}
void RPError::writeBody(RLFrame& dest, size_t &wp) const
{
mCause.writeLV(dest,wp);
}
void RPError::parseBody(const RLFrame& dest, size_t &wp)
{
mCause.parseLV(dest,wp);
}
void RPError::text(ostream& os) const
{
RPMessage::text(os);
os << " cause=(" << mCause << ")";
}
ostream& SMS::operator<<(ostream& os, TLMessage::MessageType val)
{
switch(val) {
case TLMessage::DELIVER: os<<"SMS-DELIVER/REPORT"; break;
case TLMessage::STATUS_REPORT: os<<"SMS-STATUS-REPORT/COMMAND"; break;
case TLMessage::SUBMIT: os<<"SMS-SUBMIT/REPORT"; break;
default :
os<<hex<<"0x"<<(int)val<<dec; break;
}
return os;
}
ostream& SMS::operator<<(ostream& os, const TLMessage& msg)
{
msg.text(os);
return os;
}
ostream& SMS::operator<<(ostream& os, const TLElement& elem)
{
elem.text(os);
return os;
}
/** Parse a TL address field, including length. */
void TLAddress::parse(const TLFrame& src, size_t& rp)
{
// GSM 03.40.
// This is different from the BCD formats in GSM 04.08,
// even though it looks very similar.
// The difference is in the encoding of the length field.
size_t numDigits = src.readField(rp,8);
size_t length = numDigits/2 + (numDigits % 2);
if (src.readField(rp, 1) != 1) SMS_READ_ERROR;
mType = (TypeOfNumber)src.readField(rp, 3);
mPlan = (NumberingPlan)src.readField(rp, 4);
//LOG(DEBUG) << "parse mType " << mType;
mDigits.parse(src,rp,length, mType == InternationalNumber);
}
void TLAddress::text(ostream& os) const
{
os << "type=" << mType;
os << " plan=" << mPlan;
os << " digits=" << mDigits;
}
void TLAddress::write(TLFrame& dest, size_t& wp) const
{
dest.writeField(wp,mDigits.size(),8);
dest.writeField(wp, 0x01, 1);
dest.writeField(wp, mType, 3);
dest.writeField(wp, mPlan, 4);
mDigits.write(dest,wp);
}
size_t TLValidityPeriod::length() const
{
// GSM 03.40 9.2.3.3
switch (mVPF) {
case 0: return 0; // not present
case 1: return 1; // relative format, 9.2.3.12.1
case 2: return 7; // enhanced format, 9.2.3.12.2
case 3: return 7; // absolute format, 9.2.3.12.3
default: assert(0); // someone forgot to initialize the VPF
}
}
void TLValidityPeriod::parse(const TLFrame& src, size_t& rp)
{
// FIXME -- Check remaining message length before reading!!
LOG(DEBUG) << "SMS: TLValidityPeriod::parse VPF=" << mVPF;
switch (mVPF) {
case 2: {
// Relative format.
// GSM 03.40 9.2.3.12.1
unsigned vp = src.readField(rp,8);
unsigned minutes = 0;
if (vp<144) minutes = (vp+1)*5;
else if (vp<168) minutes = 12*60 + (vp-143)*30;
else if (vp<197) minutes = 24*60*(vp-166);
else minutes = 7*24*60*(vp-192);
mExpiration = Timeval();
mExpiration.addMinutes(minutes);
return;
}
case 3: {
// Absolute format, borrowed from GSM 04.08 MM
// GSM 03.40 9.2.3.12.2
L3TimeZoneAndTime decoder;
decoder.parseV((TLFrame)(BitVector2)src,rp);
mExpiration = decoder.time();
return;
}
case 1:
// Enhanced format.
// GSM 03.40 9.2.3.12.3
LOG(NOTICE) << "SMS: ignoring grossly complex \"enhanced\" TP-VP and assuming 1 week.";
rp += 7;
// fall through...
case 0:
// No validity period field.
LOG(DEBUG) << "SMS: no validity period, assuming 1 week";
mExpiration = Timeval(7*24*60*60*1000);
return;
default: assert(0); // someone forgot to initialize the VPF
}
}
void TLValidityPeriod::write(TLFrame& dest, size_t& wp) const
{
if (mVPF==0) return;
// We only support VPF==1.
assert(mVPF==1);
int seconds = mExpiration.seconds() - time(NULL);
int minutes = seconds/60;
if (minutes<1) minutes=1;
unsigned vp;
if (minutes<=720) vp = (minutes-1)/5;
else if (minutes<1440) vp = 143 + (minutes-720)/30;
else if (minutes<43200) vp = 166 + minutes/(24*60);
else vp = 192 + minutes/(7*24*60);
if (vp>255) vp=255;
dest.writeField(wp,vp,8);
}
void TLValidityPeriod::text(ostream& os) const
{
char str[27];
time_t seconds = mExpiration.sec();
ctime_r(&seconds,str);
str[24]='\0';
os << "expiration=(" << str << ")";
}
void TLUserData::encode7bit(const char *text)
{
size_t wp = 0;
// 1. Prepare.
// Default alphabet (7-bit)
mDCS = 0;
// With 7-bit encoding TP-User-Data-Length count septets, i.e. just number
// of characters.
mLength = strlen(text);
int bytes = (mLength*7+7)/8;
int filler_bits = bytes*8-mLength*7;
mRawData.resize(bytes*8);
// 2. Write TP-UD
// This tail() works because UD is always the last field in the PDU.
BitVector2 chars = mRawData.tail(wp);
for (unsigned i=0; i<mLength; i++) {
char gsm = encodeGSMChar(text[i]);
mRawData.writeFieldReversed(wp,gsm,7);
}
mRawData.writeField(wp,0,filler_bits);
}
std::string TLUserData::decode() const
{
std::string text;
switch (mDCS) {
case 0:
case 244:
case 245:
case 246:
case 247:
{
// GSM 7-bit encoding, GSM 03.38 6.
// Check bounds.
if (mLength*7 > (mRawData.size())) {
LOG(NOTICE) << "badly formatted TL-UD";
SMS_READ_ERROR;
}
size_t crp = 0;
unsigned text_length = mLength;
// Skip User-Data-Header. We don't decode it here.
// User-Data-Header handling is described in GSM 03.40 9.2.3.24
// and is pictured in GSM 03.40 Figure 9.2.3.24 (a)
if (mUDHI) {
// Length-of-User-Data-Header
unsigned udhl = mRawData.peekFieldReversed(crp,8);
// Calculate UDH length in septets, including fill bits.
unsigned udh_septets = (udhl*8 + 8 + 6) / 7;
// Adjust actual text position and length.
crp += udh_septets * 7;
text_length -= udh_septets;
LOG(DEBUG) << "UDHL(octets)=" << udhl
<< " UDHL(septets)=" << udh_septets
<< " pointer(bits)=" << crp
<< " text_length(septets)=" << text_length;
}
// Do decoding
text.resize(text_length);
for (unsigned i=0; i<text_length; i++) {
char gsm = mRawData.readFieldReversed(crp,7);
text[i] = decodeGSMChar(gsm);
}
break;
}
default:
LOG(NOTICE) << "unsupported DCS 0x" << mDCS;
SMS_READ_ERROR;
break;
}
return text;
}
size_t TLUserData::length() const
{
// The reported value includes the length byte itself.
// The length() method only needs to work for formats supported
// by the write() method.
assert(mDCS<0x100); // Someone forgot to initialize the DCS.
size_t sum = 1; // Start by counting the TP-User-Data-Length byte.
#if 1
sum += (mRawData.size()+7)/8;
#else
// The DCS is defined in GSM 03.38 4.
if (mDCS==0) {
// Default 7-bit alphabet
// Return the number of octets needed for encoding.
unsigned bits = strlen(mData) * 7;
unsigned octets = bits/8;
if (bits%8) octets += 1;
sum += octets;
} else {
LOG(WARNING) << "unsupported SMS DCS 0x" << hex << mDCS;
// It's OK to abort here. This method is only used for encoding.
// So we should never end up here.
assert(0); // We don't support this DCS.
}
#endif
return sum;
}
void TLUserData::parse(const TLFrame& src, size_t& rp)
{
// The DCS is defined in GSM 03.38 4.
assert(mDCS<0x100); // Someone forgot to initialize the DCS.
// TP-User-Data-Length
mLength = src.readField(rp,8);
#if 1
// This tail() works because UD is always the last field in the PDU.
mRawData.clone(src.alias().tail(rp)); // TODO: Could use cloneSegment
// Should we do this here?
mRawData.LSB8MSB();
#else
assert(!mUDHI); // We don't support user headers.
switch (mDCS) {
case 0:
case 244:
case 245:
case 246:
case 247:
{
// GSM 7-bit encoding, GSM 03.38 6.
// Check bounds.
if (numChar*7 > (src.size()-rp)) {
LOG(NOTICE) << "badly formatted TL-UD";
SMS_READ_ERROR;
}
BitVector2 chars(src.tail(rp));
chars.LSB8MSB();
size_t crp=0;
for (unsigned i=0; i<numChar; i++) {
char gsm = chars.readFieldReversed(crp,7);
mData[i] = decodeGSMChar(gsm);
}
mData[numChar]='\0';
if (crp%8) crp += 8 - crp%8;
rp += crp;
return;
}
default:
{
rp += numChar;
sprintf(mData,"unsupported DCS 0x%x", mDCS);
LOG(NOTICE) << mData;
SMS_READ_ERROR;
}
}
#endif
}
void TLUserData::write(TLFrame& dest, size_t& wp) const
{
#if 1
// First write TP-User-Data-Length
dest.writeField(wp,mLength,8);
// Then write TP-User-Data
// This tail() works because UD is always the last field in the PDU.
BitVector2 ud_dest = dest.tail(wp);
mRawData.copyTo(ud_dest);
ud_dest.LSB8MSB();
#else
// Stuff we don't support...
assert(!mUDHI);
assert(mDCS==0);
unsigned numChar = strlen(mData);
dest.writeField(wp,numChar,8);
// This tail() works because UD is always the last field in the PDU.
BitVector2 chars = dest.tail(wp);
chars.zero();
for (unsigned i=0; i<numChar; i++) {
char gsm = encodeGSMChar(mData[i]);
dest.writeFieldReversed(wp,gsm,7);
}
chars.LSB8MSB();
#endif
}
void TLUserData::text(ostream& os) const
{
os << "DCS=" << mDCS;
os << " UDHI=" << mUDHI;
os << " UDLength=" << mLength;
os << " UD=("; mRawData.hex(os); os << ")";
}
// (pat 10-2013) This is just wrong. The contents of the first byte depend
// on the message type, so there is no separate "body". This routine should not
// skip the first byte, it should let the invidual parsers crack out the TLMessage header bits.
void TLMessage::parse(const TLFrame& src)
{
// FIXME -- Check MTI for consistency.
size_t rp=8;
return parseBody(src,rp);
}
void TLMessage::write(TLFrame& dest) const
{
dest.resize(bitsNeeded());
size_t wp=8;
writeMTI(dest);
writeBody(dest,wp);
}
size_t TLSubmit::l2BodyLength() const
{
return 1 + mDA.length() + 1 + 1 + mVP.length() + mUD.length();
}
// GSM 3.40 9.2.2.1
TLDeliver::TLDeliver(const TLFrame& fm)
{
size_t rp = 8;
parseBody(fm,rp);
}
void TLDeliver::parseBody(const TLFrame &src, size_t &rp)
{
// Note that offset is reversed, i'=7-i.
// Ignore MTI, we already know it is DELIVER.
// Note that these header fields come from src ignoring rp.
parseMMS(src);
parseRP(src);
parseUDHI(src);
parseSRI(src);
// Now the 'body'
assert(rp == 8);
mOA.parse(src,rp); // originating address.
mPID = src.readField(rp,8); // protocol id
mUD.DCS(src.readField(rp,8)); // data coding scheme, stored in the TLUserData.
mSCTS.parse(src,rp); // time stamp
mUD.parse(src,rp); // user data.
}
void TLSubmit::parseBody(const TLFrame& src, size_t& rp)
{
bool udhi;
parseRD(src);
parseVPF(src);
parseRP(src);
udhi = parseUDHI(src);
parseSRR(src);
mMR = src.readField(rp,8);
mDA.parse(src,rp);
//LOG(DEBUG) << "Destination " << mDA.digits();
mPI = src.readField(rp,8);
mDCS = src.readField(rp,8);
mVP.VPF(mVPF);
mVP.parse(src,rp);
mUD.DCS(mDCS);
mUD.UDHI(udhi);
mUD.parse(src,rp);
}
void TLSubmit::text(ostream& os) const
{
TLMessage::text(os);
os << " RD=" << mRD;
os << " VPF=" << mVPF;
os << " RP=" << mRP;
os << " UDHI=" << mUD.UDHI();
os << " SRR=" << mSRR;
os << " MR=" << mMR;
os << " DA=(" << mDA << ")";
os << " PI=" << mPI;
os << " DCS=" << mDCS;
os << " VP=(" << mVP << ")";
os << " UD=\"" << mUD << "\"";
}
size_t TLDeliver::l2BodyLength() const
{
LOG(DEBUG) << "TLDEliver::l2BodyLength OA " << mOA.length() << " SCTS " << mSCTS.length() << " UD " << mUD.length();
return mOA.length() + 1 + 1 + mSCTS.length() + mUD.length();
}
// (pat) See 3GPP 3.40 9.2.2
void TLDeliver::writeBody(TLFrame& dest, size_t& wp) const
{
writeMMS(dest); // more messages to send bit.
writeRP(dest); // reply path bit.
writeUDHI(dest, mUD.UDHI()); // User-data-header-indicator bit
writeSRI(dest); // status-report-indication bit
mOA.write(dest,wp); // originating address
dest.writeField(wp,mPID,8); // protocol id
dest.writeField(wp,mUD.DCS(),8); // Data-coding-scheme
mSCTS.write(dest,wp); // service-centre-time-stamp
writeUnused(dest); // user-data-length. (pat) Why empty?
mUD.write(dest,wp); // user data.
}
void TLDeliver::text(ostream& os) const
{
TLMessage::text(os);
os << " OriginatingAddress=(" << mOA << ")";
os << " SCTimeStamp=(" << mSCTS << ")";
os << " DataCodingScheme="<<mUD.DCS();
os << " UserData=(" << mUD << ")";
}
void TLTimestamp::text(std::ostream&os) const
{
mTime.text(os);
}
// vim: ts=4 sw=4