[smsmodem] Initial contribution (#12250)
* [smsmodem] Initial contribution This binding connects to a USB serial GSM modem (or a network exposed one, a.k.a ser2net) and allows openHAB to send and receive SMS through it. Signed-off-by: Gwendal Roulleau <gwendal.roulleau@gmail.com> * [smsmodem] README fix Signed-off-by: Gwendal Roulleau <gwendal.roulleau@gmail.com> * [smsmodem] build/spotless fix Signed-off-by: Gwendal Roulleau <gwendal.roulleau@gmail.com> * [smsmodem] compliance with 3rd party license And long running thread naming convention And treated some code warning Signed-off-by: Gwendal Roulleau <gwendal.roulleau@gmail.com> * [smsmodem] i18n Signed-off-by: Gwendal Roulleau <gwendal.roulleau@gmail.com> * [smsmodem] Small fixes update channel rename action to avoid colision with other binding and a too generic name Signed-off-by: Gwendal Roulleau <gwendal.roulleau@gmail.com> * [smsmodem] Use of standard Thing properties Signed-off-by: Gwendal Roulleau <gwendal.roulleau@gmail.com> * [smsmodem] Fix sender identifier error with special character Signed-off-by: Gwendal Roulleau <gwendal.roulleau@gmail.com> * [smsmodem] Add encoding parameter For non latin character in SMS Signed-off-by: Gwendal Roulleau <gwendal.roulleau@gmail.com> * [smsmodem] Apply review Signed-off-by: Gwendal Roulleau <gwendal.roulleau@gmail.com> * [smsmodem] Split local and remote modem in two thing-types Signed-off-by: Gwendal Roulleau <gwendal.roulleau@gmail.com> * [smsmodem] Apply review Signed-off-by: Gwendal Roulleau <gwendal.roulleau@gmail.com> * [smsmodem] Apply review Signed-off-by: Gwendal Roulleau <gwendal.roulleau@gmail.com> * [smsmodem] Apply code review (removing unnecessary method) Signed-off-by: Gwendal Roulleau <gwendal.roulleau@gmail.com> Signed-off-by: Gwendal Roulleau <gwendal.roulleau@gmail.com> Co-authored-by: Gwendal Roulleau <gwendal.roulleau@gmail.com>
This commit is contained in:
49
bundles/org.openhab.binding.smsmodem/src/3rdparty/java/org/smslib/Capabilities.java
vendored
Normal file
49
bundles/org.openhab.binding.smsmodem/src/3rdparty/java/org/smslib/Capabilities.java
vendored
Normal file
@@ -0,0 +1,49 @@
|
||||
package org.smslib;
|
||||
|
||||
import java.util.BitSet;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* Extracted from SMSLib
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class Capabilities {
|
||||
BitSet caps = new BitSet();
|
||||
|
||||
public enum Caps {
|
||||
CanSendMessage,
|
||||
CanSendBinaryMessage,
|
||||
CanSendUnicodeMessage,
|
||||
CanSendWapMessage,
|
||||
CanSendFlashMessage,
|
||||
CanSendPortInfo,
|
||||
CanSetSenderId,
|
||||
CanSplitMessages,
|
||||
CanRequestDeliveryStatus,
|
||||
CanQueryDeliveryStatus,
|
||||
CanQueryCreditBalance,
|
||||
CanQueryCoverage,
|
||||
CanSetValidityPeriod
|
||||
}
|
||||
|
||||
public void set(Caps c) {
|
||||
this.caps.set(c.ordinal());
|
||||
}
|
||||
|
||||
public BitSet getCapabilities() {
|
||||
return (BitSet) this.caps.clone();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
BitSet bs = (BitSet) getCapabilities().clone();
|
||||
StringBuffer b = new StringBuffer();
|
||||
for (Caps c : Caps.values()) {
|
||||
b.append(String.format("%-30s : ", c.toString()));
|
||||
b.append(bs.get(c.ordinal()) ? "YES" : "NO");
|
||||
b.append("\n");
|
||||
}
|
||||
return b.toString();
|
||||
}
|
||||
}
|
||||
20
bundles/org.openhab.binding.smsmodem/src/3rdparty/java/org/smslib/CommunicationException.java
vendored
Normal file
20
bundles/org.openhab.binding.smsmodem/src/3rdparty/java/org/smslib/CommunicationException.java
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
package org.smslib;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* Wrapper for communication exception
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class CommunicationException extends Exception {
|
||||
|
||||
private static final long serialVersionUID = -5175636461754717860L;
|
||||
|
||||
public CommunicationException(String message, Exception cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
public CommunicationException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
161
bundles/org.openhab.binding.smsmodem/src/3rdparty/java/org/smslib/DeviceInformation.java
vendored
Normal file
161
bundles/org.openhab.binding.smsmodem/src/3rdparty/java/org/smslib/DeviceInformation.java
vendored
Normal file
@@ -0,0 +1,161 @@
|
||||
package org.smslib;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.smslib.callback.IDeviceInformationListener;
|
||||
|
||||
/**
|
||||
* Extracted from SMSLib
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class DeviceInformation {
|
||||
|
||||
@Nullable
|
||||
private IDeviceInformationListener deviceInformationListener;
|
||||
|
||||
public enum Modes {
|
||||
PDU,
|
||||
TEXT
|
||||
}
|
||||
|
||||
String manufacturer = "N/A";
|
||||
String model = "N/A";
|
||||
String swVersion = "N/A";
|
||||
String serialNo = "N/A";
|
||||
String imsi = "N/A";
|
||||
int rssi = 0;
|
||||
|
||||
@Nullable
|
||||
Modes mode;
|
||||
|
||||
int totalSent = 0;
|
||||
int totalFailed = 0;
|
||||
int totalReceived = 0;
|
||||
int totalFailures = 0;
|
||||
|
||||
public void setDeviceInformationListener(@Nullable IDeviceInformationListener deviceInformationListener) {
|
||||
this.deviceInformationListener = deviceInformationListener;
|
||||
}
|
||||
|
||||
public synchronized void increaseTotalSent() {
|
||||
this.totalSent++;
|
||||
IDeviceInformationListener dil = deviceInformationListener;
|
||||
if (dil != null) {
|
||||
dil.setTotalSent(Integer.toString(totalSent));
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void increaseTotalFailed() {
|
||||
this.totalFailed++;
|
||||
IDeviceInformationListener dil = deviceInformationListener;
|
||||
if (dil != null) {
|
||||
dil.setTotalFailed(Integer.toString(totalFailed));
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void increaseTotalReceived() {
|
||||
this.totalReceived++;
|
||||
IDeviceInformationListener dil = deviceInformationListener;
|
||||
if (dil != null) {
|
||||
dil.setTotalReceived(Integer.toString(totalReceived));
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void increaseTotalFailures() {
|
||||
this.totalFailures++;
|
||||
IDeviceInformationListener dil = deviceInformationListener;
|
||||
if (dil != null) {
|
||||
dil.setTotalFailures(Integer.toString(totalFailures));
|
||||
}
|
||||
}
|
||||
|
||||
public String getManufacturer() {
|
||||
return this.manufacturer;
|
||||
}
|
||||
|
||||
public void setManufacturer(String manufacturer) {
|
||||
this.manufacturer = manufacturer;
|
||||
IDeviceInformationListener finalDeviceInformationListener = deviceInformationListener;
|
||||
if (finalDeviceInformationListener != null) {
|
||||
finalDeviceInformationListener.setManufacturer(manufacturer);
|
||||
}
|
||||
}
|
||||
|
||||
public String getModel() {
|
||||
return this.model;
|
||||
}
|
||||
|
||||
public void setModel(String model) {
|
||||
this.model = model;
|
||||
IDeviceInformationListener finalDeviceInformationListener = deviceInformationListener;
|
||||
if (finalDeviceInformationListener != null) {
|
||||
finalDeviceInformationListener.setModel(model);
|
||||
}
|
||||
}
|
||||
|
||||
public String getSwVersion() {
|
||||
return this.swVersion;
|
||||
}
|
||||
|
||||
public void setSwVersion(String swVersion) {
|
||||
this.swVersion = swVersion;
|
||||
IDeviceInformationListener finalDeviceInformationListener = deviceInformationListener;
|
||||
if (finalDeviceInformationListener != null) {
|
||||
finalDeviceInformationListener.setSwVersion(swVersion);
|
||||
}
|
||||
}
|
||||
|
||||
public String getSerialNo() {
|
||||
return this.serialNo;
|
||||
}
|
||||
|
||||
public void setSerialNo(String serialNo) {
|
||||
this.serialNo = serialNo;
|
||||
IDeviceInformationListener finalDeviceInformationListener = deviceInformationListener;
|
||||
if (finalDeviceInformationListener != null) {
|
||||
finalDeviceInformationListener.setSerialNo(serialNo);
|
||||
}
|
||||
}
|
||||
|
||||
public String getImsi() {
|
||||
return this.imsi;
|
||||
}
|
||||
|
||||
public void setImsi(String imsi) {
|
||||
this.imsi = imsi;
|
||||
IDeviceInformationListener finalDeviceInformationListener = deviceInformationListener;
|
||||
if (finalDeviceInformationListener != null) {
|
||||
finalDeviceInformationListener.setImsi(imsi);
|
||||
}
|
||||
}
|
||||
|
||||
public int getRssi() {
|
||||
return this.rssi;
|
||||
}
|
||||
|
||||
public void setRssi(int rssi) {
|
||||
this.rssi = rssi;
|
||||
IDeviceInformationListener finalDeviceInformationListener = deviceInformationListener;
|
||||
if (finalDeviceInformationListener != null) {
|
||||
finalDeviceInformationListener.setRssi(Integer.toString(rssi));
|
||||
}
|
||||
}
|
||||
|
||||
public @Nullable Modes getMode() {
|
||||
return this.mode;
|
||||
}
|
||||
|
||||
public void setMode(Modes mode) {
|
||||
this.mode = mode;
|
||||
IDeviceInformationListener finalDeviceInformationListener = deviceInformationListener;
|
||||
if (finalDeviceInformationListener != null) {
|
||||
finalDeviceInformationListener.setMode(mode.toString());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("MANUF:%s, MODEL:%s, SERNO:%s, IMSI:%s, SW:%s, RSSI:%ddBm, MODE:%s", getManufacturer(),
|
||||
getModel(), getSerialNo(), getImsi(), getSwVersion(), getRssi(), getMode());
|
||||
}
|
||||
}
|
||||
387
bundles/org.openhab.binding.smsmodem/src/3rdparty/java/org/smslib/MessageReader.java
vendored
Normal file
387
bundles/org.openhab.binding.smsmodem/src/3rdparty/java/org/smslib/MessageReader.java
vendored
Normal file
@@ -0,0 +1,387 @@
|
||||
package org.smslib;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.StringReader;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Calendar;
|
||||
import java.util.Collection;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.StringTokenizer;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.smslib.DeviceInformation.Modes;
|
||||
import org.smslib.Modem.Status;
|
||||
import org.smslib.message.DeliveryReportMessage;
|
||||
import org.smslib.message.InboundBinaryMessage;
|
||||
import org.smslib.message.InboundMessage;
|
||||
import org.smslib.message.Payload;
|
||||
import org.smslib.pduUtils.gsm3040.Pdu;
|
||||
import org.smslib.pduUtils.gsm3040.PduParser;
|
||||
import org.smslib.pduUtils.gsm3040.PduUtils;
|
||||
import org.smslib.pduUtils.gsm3040.SmsDeliveryPdu;
|
||||
import org.smslib.pduUtils.gsm3040.SmsStatusReportPdu;
|
||||
|
||||
/**
|
||||
*
|
||||
* Poll the modem to check for new received messages
|
||||
* (sms or delivery report)
|
||||
*
|
||||
* Extracted from SMSLib
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class MessageReader extends Thread {
|
||||
static Logger logger = LoggerFactory.getLogger(MessageReader.class);
|
||||
|
||||
Modem modem;
|
||||
|
||||
private static int HOURS_TO_RETAIN_ORPHANED_MESSAGE_PARTS = 72;
|
||||
|
||||
public MessageReader(Modem modem) {
|
||||
this.modem = modem;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
logger.debug("Started!");
|
||||
if (this.modem.getStatus() == Status.Started) {
|
||||
try {
|
||||
this.modem.getModemDriver().lock();
|
||||
ArrayList<InboundMessage> messageList = new ArrayList<InboundMessage>();
|
||||
try {
|
||||
for (int i = 0; i < (this.modem.getModemDriver().getMemoryLocations().length() / 2); i++) {
|
||||
String memLocation = this.modem.getModemDriver().getMemoryLocations().substring((i * 2),
|
||||
(i * 2) + 2);
|
||||
String data = this.modem.getModemDriver().atGetMessages(memLocation).getResponseData();
|
||||
if (data.length() > 0) {
|
||||
messageList.addAll((this.modem.getDeviceInformation().getMode() == Modes.PDU
|
||||
? parsePDU(data, memLocation)
|
||||
: parseTEXT(data, memLocation)));
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
this.modem.getModemDriver().unlock();
|
||||
}
|
||||
for (InboundMessage message : messageList) {
|
||||
processMessage(message);
|
||||
}
|
||||
|
||||
} catch (CommunicationException | IOException e) {
|
||||
logger.error("Unhandled exception while trying to read new messages", e);
|
||||
modem.error();
|
||||
}
|
||||
}
|
||||
logger.debug("Stopped!");
|
||||
}
|
||||
|
||||
private ArrayList<InboundMessage> parsePDU(String data, String memLocation) throws IOException {
|
||||
ArrayList<InboundMessage> messageList = new ArrayList<>();
|
||||
List<List<InboundMessage>> mpMsgList = new ArrayList<>();
|
||||
BufferedReader reader = new BufferedReader(new StringReader(data));
|
||||
while (true) {
|
||||
String line = reader.readLine();
|
||||
if (line == null) {
|
||||
break;
|
||||
}
|
||||
PduParser parser = new PduParser();
|
||||
int i = line.indexOf(':');
|
||||
int j = line.indexOf(',');
|
||||
if (j == -1) {
|
||||
logger.error("Bad PDU announce : {}", line);
|
||||
continue;
|
||||
}
|
||||
int memIndex = Integer.parseInt(line.substring(i + 1, j).trim());
|
||||
i = line.lastIndexOf(',');
|
||||
j = line.length();
|
||||
int pduSize = Integer.parseInt(line.substring(i + 1, j).trim());
|
||||
String pduString = reader.readLine().trim();
|
||||
if ((pduSize > 0) && ((pduSize * 2) == pduString.length())) {
|
||||
pduString = "00" + pduString;
|
||||
}
|
||||
Pdu pdu = parser.parsePdu(pduString);
|
||||
if (pdu instanceof SmsDeliveryPdu) {
|
||||
logger.debug("PDU = {}", pdu.toString());
|
||||
InboundMessage msg = null;
|
||||
if (pdu.isBinary()) {
|
||||
msg = new InboundBinaryMessage((SmsDeliveryPdu) pdu, memLocation, memIndex);
|
||||
} else {
|
||||
msg = new InboundMessage((SmsDeliveryPdu) pdu, memLocation, memIndex);
|
||||
}
|
||||
msg.setGatewayId(this.modem.getGatewayId());
|
||||
msg.setGatewayId(this.modem.getGatewayId());
|
||||
logger.debug("IN-DTLS: MI:{} REF:{} MAX:{} SEQ:{}", msg.getMemIndex(), msg.getMpRefNo(),
|
||||
msg.getMpMaxNo(), msg.getMpSeqNo());
|
||||
if (msg.getMpRefNo() == 0) {
|
||||
messageList.add(msg);
|
||||
} else {
|
||||
// multi-part message
|
||||
int k, l;
|
||||
List<InboundMessage> tmpList;
|
||||
InboundMessage listMsg;
|
||||
boolean found, duplicate;
|
||||
found = false;
|
||||
for (k = 0; k < mpMsgList.size(); k++) {
|
||||
// List of List<InboundMessage>
|
||||
tmpList = mpMsgList.get(k);
|
||||
listMsg = tmpList.get(0);
|
||||
// check if current message list is for this message
|
||||
if (listMsg.getMpRefNo() == msg.getMpRefNo()) {
|
||||
duplicate = false;
|
||||
// check if the message is already in the message list
|
||||
for (l = 0; l < tmpList.size(); l++) {
|
||||
listMsg = tmpList.get(l);
|
||||
if (listMsg.getMpSeqNo() == msg.getMpSeqNo()) {
|
||||
duplicate = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!duplicate) {
|
||||
tmpList.add(msg);
|
||||
}
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
// no existing list present for this message
|
||||
// add one
|
||||
tmpList = new ArrayList<>();
|
||||
tmpList.add(msg);
|
||||
mpMsgList.add(tmpList);
|
||||
}
|
||||
}
|
||||
} else if (pdu instanceof SmsStatusReportPdu) {
|
||||
DeliveryReportMessage msg;
|
||||
msg = new DeliveryReportMessage((SmsStatusReportPdu) pdu, memLocation, memIndex);
|
||||
msg.setGatewayId(this.modem.getGatewayId());
|
||||
messageList.add(msg);
|
||||
}
|
||||
}
|
||||
checkMpMsgList(messageList, mpMsgList);
|
||||
List<InboundMessage> tmpList;
|
||||
for (int k = 0; k < mpMsgList.size(); k++) {
|
||||
tmpList = mpMsgList.get(k);
|
||||
tmpList.clear();
|
||||
}
|
||||
mpMsgList.clear();
|
||||
return messageList;
|
||||
}
|
||||
|
||||
private ArrayList<InboundMessage> parseTEXT(String data, String memLocation) throws IOException {
|
||||
ArrayList<InboundMessage> messageList = new ArrayList<>();
|
||||
BufferedReader reader;
|
||||
String line;
|
||||
Calendar cal1 = Calendar.getInstance();
|
||||
Calendar cal2 = Calendar.getInstance();
|
||||
String myData = data;
|
||||
myData = myData.replaceAll("\\s+OK\\s+", "\nOK");
|
||||
myData = myData.replaceAll("$", "\n");
|
||||
logger.debug(myData);
|
||||
reader = new BufferedReader(new StringReader(myData));
|
||||
for (;;) {
|
||||
line = reader.readLine();
|
||||
if (line == null) {
|
||||
break;
|
||||
}
|
||||
line = line.trim();
|
||||
if (line.length() > 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
while (true) {
|
||||
if (line == null) {
|
||||
break;
|
||||
}
|
||||
if (line.length() <= 0 || "OK".equalsIgnoreCase(line)) {
|
||||
break;
|
||||
}
|
||||
int i = line.indexOf(':');
|
||||
int j = line.indexOf(',');
|
||||
int memIndex = Integer.parseInt(line.substring(i + 1, j).trim());
|
||||
StringTokenizer tokens = new StringTokenizer(line, ",");
|
||||
tokens.nextToken();
|
||||
tokens.nextToken();
|
||||
String tmpLine = "";
|
||||
if (Character.isDigit(tokens.nextToken().trim().charAt(0))) {
|
||||
line = line.replaceAll(",,", ", ,");
|
||||
tokens = new StringTokenizer(line, ",");
|
||||
tokens.nextToken();
|
||||
tokens.nextToken();
|
||||
tokens.nextToken();
|
||||
String messageId = tokens.nextToken();
|
||||
String recipient = tokens.nextToken().replaceAll("\"", "");
|
||||
String dateStr = tokens.nextToken().replaceAll("\"", "");
|
||||
if (dateStr.indexOf('/') == -1) {
|
||||
dateStr = tokens.nextToken().replaceAll("\"", "");
|
||||
}
|
||||
cal1.set(Calendar.YEAR, 2000 + Integer.parseInt(dateStr.substring(0, 2)));
|
||||
cal1.set(Calendar.MONTH, Integer.parseInt(dateStr.substring(3, 5)) - 1);
|
||||
cal1.set(Calendar.DAY_OF_MONTH, Integer.parseInt(dateStr.substring(6, 8)));
|
||||
dateStr = tokens.nextToken().replaceAll("\"", "");
|
||||
cal1.set(Calendar.HOUR_OF_DAY, Integer.parseInt(dateStr.substring(0, 2)));
|
||||
cal1.set(Calendar.MINUTE, Integer.parseInt(dateStr.substring(3, 5)));
|
||||
cal1.set(Calendar.SECOND, Integer.parseInt(dateStr.substring(6, 8)));
|
||||
dateStr = tokens.nextToken().replaceAll("\"", "");
|
||||
cal2.set(Calendar.YEAR, 2000 + Integer.parseInt(dateStr.substring(0, 2)));
|
||||
cal2.set(Calendar.MONTH, Integer.parseInt(dateStr.substring(3, 5)) - 1);
|
||||
cal2.set(Calendar.DAY_OF_MONTH, Integer.parseInt(dateStr.substring(6, 8)));
|
||||
dateStr = tokens.nextToken().replaceAll("\"", "");
|
||||
cal2.set(Calendar.HOUR_OF_DAY, Integer.parseInt(dateStr.substring(0, 2)));
|
||||
cal2.set(Calendar.MINUTE, Integer.parseInt(dateStr.substring(3, 5)));
|
||||
cal2.set(Calendar.SECOND, Integer.parseInt(dateStr.substring(6, 8)));
|
||||
DeliveryReportMessage msg;
|
||||
msg = new DeliveryReportMessage(messageId, recipient, memLocation, memIndex, cal1.getTime(),
|
||||
cal2.getTime());
|
||||
msg.setGatewayId(this.modem.getGatewayId());
|
||||
messageList.add(msg);
|
||||
} else {
|
||||
line = line.replaceAll(",,", ", ,");
|
||||
tokens = new StringTokenizer(line, ",");
|
||||
tokens.nextToken();
|
||||
tokens.nextToken();
|
||||
String originator = tokens.nextToken().replaceAll("\"", "");
|
||||
tokens.nextToken();
|
||||
String dateStr = tokens.nextToken().replaceAll("\"", "");
|
||||
cal1.set(Calendar.YEAR, 2000 + Integer.parseInt(dateStr.substring(0, 2)));
|
||||
cal1.set(Calendar.MONTH, Integer.parseInt(dateStr.substring(3, 5)) - 1);
|
||||
cal1.set(Calendar.DAY_OF_MONTH, Integer.parseInt(dateStr.substring(6, 8)));
|
||||
dateStr = tokens.nextToken().replaceAll("\"", "");
|
||||
cal1.set(Calendar.HOUR_OF_DAY, Integer.parseInt(dateStr.substring(0, 2)));
|
||||
cal1.set(Calendar.MINUTE, Integer.parseInt(dateStr.substring(3, 5)));
|
||||
cal1.set(Calendar.SECOND, Integer.parseInt(dateStr.substring(6, 8)));
|
||||
String msgText = "";
|
||||
while (true) {
|
||||
tmpLine = reader.readLine();
|
||||
if (tmpLine == null) {
|
||||
break;
|
||||
}
|
||||
if (tmpLine.startsWith("+CMGL")) {
|
||||
break;
|
||||
}
|
||||
if (tmpLine.startsWith("+CMGR")) {
|
||||
break;
|
||||
}
|
||||
msgText += (msgText.length() == 0 ? "" : "\n") + tmpLine;
|
||||
}
|
||||
InboundMessage msg = new InboundMessage(originator, msgText.trim(), cal1.getTime(), memLocation,
|
||||
memIndex);
|
||||
msg.setGatewayId(this.modem.getGatewayId());
|
||||
messageList.add(msg);
|
||||
}
|
||||
while (true) {
|
||||
// line = reader.readLine();
|
||||
line = ((tmpLine == null || tmpLine.length() == 0) ? reader.readLine() : tmpLine);
|
||||
if (line == null) {
|
||||
break;
|
||||
}
|
||||
line = line.trim();
|
||||
if (line.length() > 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
reader.close();
|
||||
return messageList;
|
||||
}
|
||||
|
||||
private void checkMpMsgList(Collection<InboundMessage> msgList, List<List<InboundMessage>> mpMsgList) {
|
||||
int k, l, m;
|
||||
List<InboundMessage> tmpList;
|
||||
InboundMessage listMsg, mpMsg;
|
||||
boolean found;
|
||||
mpMsg = null;
|
||||
logger.debug("CheckMpMsgList(): MAINLIST: {}", mpMsgList.size());
|
||||
for (k = 0; k < mpMsgList.size(); k++) {
|
||||
tmpList = mpMsgList.get(k);
|
||||
logger.debug("CheckMpMsgList(): SUBLIST[{}]: ", tmpList.size());
|
||||
listMsg = tmpList.get(0);
|
||||
found = false;
|
||||
if (listMsg.getMpMaxNo() == tmpList.size()) {
|
||||
found = true;
|
||||
for (l = 0; l < tmpList.size(); l++) {
|
||||
for (m = 0; m < tmpList.size(); m++) {
|
||||
listMsg = tmpList.get(m);
|
||||
if (listMsg.getMpSeqNo() == (l + 1)) {
|
||||
if (listMsg.getMpSeqNo() == 1) {
|
||||
mpMsg = listMsg;
|
||||
mpMsg.setMpMemIndex(mpMsg.getMemIndex());
|
||||
if (listMsg.getMpMaxNo() == 1) {
|
||||
msgList.add(mpMsg);
|
||||
}
|
||||
} else {
|
||||
if (mpMsg != null) {
|
||||
String textToAdd = listMsg.getPayload().getText();
|
||||
if (mpMsg.getEndsWithMultiChar()) {
|
||||
if (textToAdd == null) {
|
||||
throw new UnrecoverableSmslibException("Cannot add text to message");
|
||||
}
|
||||
// adjust first char of textToAdd
|
||||
logger.debug("Adjusting dangling multi-char: {} --> {}", textToAdd.charAt(0),
|
||||
PduUtils.getMultiCharFor(textToAdd.charAt(0)));
|
||||
textToAdd = PduUtils.getMultiCharFor(textToAdd.charAt(0))
|
||||
+ textToAdd.substring(1);
|
||||
}
|
||||
mpMsg.setEndsWithMultiChar(listMsg.getEndsWithMultiChar());
|
||||
mpMsg.setPayload(new Payload(mpMsg.getPayload().getText() + textToAdd));
|
||||
// }
|
||||
mpMsg.setMpSeqNo(listMsg.getMpSeqNo());
|
||||
mpMsg.setMpMemIndex(listMsg.getMemIndex());
|
||||
if (listMsg.getMpSeqNo() == listMsg.getMpMaxNo()) {
|
||||
mpMsg.setMemIndex(-1);
|
||||
msgList.add(mpMsg);
|
||||
mpMsg = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
tmpList.clear();
|
||||
tmpList = null;
|
||||
}
|
||||
if (found) {
|
||||
mpMsgList.remove(k);
|
||||
k--;
|
||||
}
|
||||
}
|
||||
// Check the remaining parts for "orphaned" status
|
||||
for (List<InboundMessage> remainingList : mpMsgList) {
|
||||
for (InboundMessage msg : remainingList) {
|
||||
Date sentDate = msg.getSentDate();
|
||||
if (sentDate == null || getAgeInHours(sentDate) > HOURS_TO_RETAIN_ORPHANED_MESSAGE_PARTS) {
|
||||
try {
|
||||
this.modem.delete(msg);
|
||||
} catch (CommunicationException e) {
|
||||
logger.error("Could not delete orphaned message: {}", msg.toString(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static int getAgeInHours(Date fromDate) {
|
||||
Calendar cal = Calendar.getInstance();
|
||||
cal.setTime(new java.util.Date());
|
||||
long now = cal.getTimeInMillis();
|
||||
cal.setTime(fromDate);
|
||||
long past = cal.getTimeInMillis();
|
||||
return (int) ((now - past) / (60 * 60 * 1000));
|
||||
}
|
||||
|
||||
private void processMessage(InboundMessage message) {
|
||||
String messageSignature = message.getSignature();
|
||||
if (!this.modem.getReadMessagesSet().contains(messageSignature)) {
|
||||
this.modem.getDeviceInformation().increaseTotalReceived();
|
||||
if (message instanceof DeliveryReportMessage) {
|
||||
modem.processDeliveryReport((DeliveryReportMessage) message);
|
||||
} else {
|
||||
modem.processMessage(message);
|
||||
}
|
||||
this.modem.getReadMessagesSet().add(messageSignature);
|
||||
}
|
||||
}
|
||||
}
|
||||
78
bundles/org.openhab.binding.smsmodem/src/3rdparty/java/org/smslib/MessageSender.java
vendored
Normal file
78
bundles/org.openhab.binding.smsmodem/src/3rdparty/java/org/smslib/MessageSender.java
vendored
Normal file
@@ -0,0 +1,78 @@
|
||||
package org.smslib;
|
||||
|
||||
import java.util.Queue;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.smslib.message.OutboundMessage;
|
||||
import org.smslib.message.OutboundMessage.FailureCause;
|
||||
import org.smslib.message.OutboundMessage.SentStatus;
|
||||
|
||||
/**
|
||||
* Poll the modem queue and send messages
|
||||
*
|
||||
* Extracted from SMSLib
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class MessageSender extends Thread {
|
||||
static Logger logger = LoggerFactory.getLogger(MessageSender.class);
|
||||
|
||||
Queue<OutboundMessage> messageQueue;
|
||||
|
||||
Modem modem;
|
||||
|
||||
private int gatewayDispatcherYield;
|
||||
|
||||
private AtomicBoolean isRunning = new AtomicBoolean(false);
|
||||
private boolean interrupt = false;
|
||||
|
||||
public MessageSender(String name, Queue<OutboundMessage> messageQueue, Modem modem, int gatewayDispatcherYield) {
|
||||
setName(name);
|
||||
setDaemon(false);
|
||||
this.messageQueue = messageQueue;
|
||||
this.modem = modem;
|
||||
this.gatewayDispatcherYield = gatewayDispatcherYield;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
if (!isRunning.getAndSet(true)) {
|
||||
interrupt = false; // reset interruption status
|
||||
try {
|
||||
logger.debug("Started!");
|
||||
while (!interrupt && messageQueue.size() > 0) {
|
||||
try {
|
||||
OutboundMessage message = messageQueue.poll();
|
||||
if (message != null) {
|
||||
try {
|
||||
this.modem.send(message);
|
||||
} catch (CommunicationException e) {
|
||||
logger.error("Send failed!", e);
|
||||
message.setSentStatus(SentStatus.Failed);
|
||||
message.setFailureCause(FailureCause.None);
|
||||
} finally {
|
||||
this.modem.processMessageSent(message);
|
||||
sleep(this.gatewayDispatcherYield);
|
||||
}
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
logger.debug("Message dispatcher thread interrupted", e);
|
||||
}
|
||||
}
|
||||
logger.debug("Ended!");
|
||||
} finally {
|
||||
this.isRunning.set(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void setInterrupt() {
|
||||
this.interrupt = true;
|
||||
}
|
||||
|
||||
public boolean isRunning() {
|
||||
return isRunning.get();
|
||||
}
|
||||
}
|
||||
444
bundles/org.openhab.binding.smsmodem/src/3rdparty/java/org/smslib/Modem.java
vendored
Normal file
444
bundles/org.openhab.binding.smsmodem/src/3rdparty/java/org/smslib/Modem.java
vendored
Normal file
@@ -0,0 +1,444 @@
|
||||
package org.smslib;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.Date;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Queue;
|
||||
import java.util.Random;
|
||||
import java.util.StringTokenizer;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.locks.Lock;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.core.io.transport.serial.SerialPortManager;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.smslib.Capabilities.Caps;
|
||||
import org.smslib.DeviceInformation.Modes;
|
||||
import org.smslib.callback.IDeviceInformationListener;
|
||||
import org.smslib.callback.IInboundOutboundMessageListener;
|
||||
import org.smslib.callback.IModemStatusListener;
|
||||
import org.smslib.driver.AbstractModemDriver;
|
||||
import org.smslib.driver.IPModemDriver;
|
||||
import org.smslib.driver.JSerialModemDriver;
|
||||
import org.smslib.message.DeliveryReportMessage;
|
||||
import org.smslib.message.InboundMessage;
|
||||
import org.smslib.message.MsIsdn;
|
||||
import org.smslib.message.OutboundMessage;
|
||||
import org.smslib.message.OutboundMessage.FailureCause;
|
||||
import org.smslib.message.OutboundMessage.SentStatus;
|
||||
import org.smslib.message.Payload;
|
||||
import org.smslib.message.Payload.Type;
|
||||
|
||||
/**
|
||||
* The Modem class is an abstraction, central to all operations
|
||||
*
|
||||
* Extracted from SMSLib
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class Modem {
|
||||
static Logger logger = LoggerFactory.getLogger(Modem.class);
|
||||
|
||||
public enum Status {
|
||||
Starting,
|
||||
Started,
|
||||
Stopping,
|
||||
Stopped,
|
||||
Error
|
||||
}
|
||||
|
||||
AbstractModemDriver modemDriver;
|
||||
|
||||
String simPin;
|
||||
MsIsdn smscNumber;
|
||||
protected String operatorId = "";
|
||||
String gatewayId = "";
|
||||
String description = "";
|
||||
|
||||
private ScheduledExecutorService scheduledService;
|
||||
@Nullable
|
||||
ScheduledFuture<?> messageReader;
|
||||
MessageSender messageSender;
|
||||
Queue<OutboundMessage> messageQueue = new ConcurrentLinkedQueue<>();
|
||||
HashSet<String> readMessagesSet;
|
||||
|
||||
Status status = Status.Stopped;
|
||||
Lock startAndStoplock = new ReentrantLock();
|
||||
|
||||
int multipartReferenceNo = 0;
|
||||
|
||||
Capabilities capabilities = new Capabilities();
|
||||
DeviceInformation deviceInformation = new DeviceInformation();
|
||||
private @Nullable IModemStatusListener modemStatusCallback = null;
|
||||
private @Nullable IInboundOutboundMessageListener messageCallback = null;
|
||||
|
||||
private Random randomizer = new Random();
|
||||
|
||||
private AtomicBoolean isStopping = new AtomicBoolean(false);
|
||||
private AtomicBoolean isStarting = new AtomicBoolean(false);
|
||||
|
||||
/**
|
||||
* Time between sending messages (ms)
|
||||
*/
|
||||
private int gatewayDispatcherYield = 100;
|
||||
|
||||
/**
|
||||
* Time between polling for new messages (ms)
|
||||
*/
|
||||
public int modemPollingInterval = 15;
|
||||
|
||||
public Modem(SerialPortManager serialPortManager, String address, int port, String simPin,
|
||||
ScheduledExecutorService scheduledService, Integer pollingInterval, Integer delayBetweenSend) {
|
||||
this.gatewayId = address + "-" + port;
|
||||
this.scheduledService = scheduledService;
|
||||
this.modemPollingInterval = pollingInterval;
|
||||
this.gatewayDispatcherYield = delayBetweenSend;
|
||||
setDescription("GSM Modem " + address + "/" + port);
|
||||
|
||||
Capabilities caps = new Capabilities();
|
||||
caps.set(Caps.CanSendMessage);
|
||||
caps.set(Caps.CanSendBinaryMessage);
|
||||
caps.set(Caps.CanSendUnicodeMessage);
|
||||
caps.set(Caps.CanSendWapMessage);
|
||||
caps.set(Caps.CanSendFlashMessage);
|
||||
caps.set(Caps.CanSendPortInfo);
|
||||
caps.set(Caps.CanSplitMessages);
|
||||
caps.set(Caps.CanRequestDeliveryStatus);
|
||||
setCapabilities(caps);
|
||||
if (isPortAnIpAddress(address)) {
|
||||
this.modemDriver = new IPModemDriver(this, address, port);
|
||||
} else {
|
||||
this.modemDriver = new JSerialModemDriver(serialPortManager, this, address, port);
|
||||
}
|
||||
this.simPin = simPin;
|
||||
this.smscNumber = new MsIsdn();
|
||||
this.readMessagesSet = new HashSet<>();
|
||||
this.messageSender = new MessageSender(String.format("Gateway Dispatcher 1 [%s]", this.gatewayId), messageQueue,
|
||||
this, gatewayDispatcherYield);
|
||||
}
|
||||
|
||||
final public boolean start() {
|
||||
if (!isStarting.getAndSet(true)) {
|
||||
this.startAndStoplock.lock();
|
||||
try {
|
||||
if ((getStatus() == Status.Stopped) || (getStatus() == Status.Error)) {
|
||||
try {
|
||||
setStatus(Status.Starting);
|
||||
logger.debug("Starting gateway: {}", toShortString());
|
||||
this.modemDriver.lock();
|
||||
try {
|
||||
this.modemDriver.openPort();
|
||||
this.modemDriver.initializeModem();
|
||||
ScheduledFuture<?> messageReaderFinal = this.messageReader;
|
||||
if (messageReaderFinal != null) {
|
||||
messageReaderFinal.cancel(true);
|
||||
}
|
||||
this.messageReader = scheduledService.scheduleWithFixedDelay(new MessageReader(this), 15,
|
||||
modemPollingInterval, TimeUnit.SECONDS);
|
||||
this.modemDriver.refreshRssi();
|
||||
this.messageSender = new MessageSender(
|
||||
String.format("Gateway Dispatcher 1 [%s]", this.gatewayId), messageQueue, this,
|
||||
gatewayDispatcherYield);
|
||||
startSendingQueue();
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Gateway: {}: {}, SL:{}, SIG: {} / {}", toShortString(),
|
||||
getDeviceInformation().toString(), this.modemDriver.getMemoryLocations(),
|
||||
this.modemDriver.getSignature(true), this.modemDriver.getSignature(false));
|
||||
}
|
||||
} finally {
|
||||
this.modemDriver.unlock();
|
||||
}
|
||||
setStatus(Status.Started);
|
||||
} catch (CommunicationException e) {
|
||||
logger.error("Communication exception when trying to start", e);
|
||||
try {
|
||||
stop();
|
||||
} finally {
|
||||
setStatus(Status.Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
this.startAndStoplock.unlock();
|
||||
this.isStarting.set(false);
|
||||
}
|
||||
}
|
||||
return (getStatus() == Status.Started);
|
||||
}
|
||||
|
||||
final public boolean stop() {
|
||||
if (!isStopping.getAndSet(true)) {
|
||||
this.startAndStoplock.lock();
|
||||
try {
|
||||
if ((getStatus() == Status.Started) || (getStatus() == Status.Error)) {
|
||||
setStatus(Status.Stopping);
|
||||
logger.debug("Stopping gateway: {}", toShortString());
|
||||
if (messageSender.isRunning()) {
|
||||
this.messageSender.setInterrupt();
|
||||
}
|
||||
logger.warn("Gateway stopping, message not delivered : {}", this.messageQueue.size());
|
||||
ScheduledFuture<?> messageReaderFinal = this.messageReader;
|
||||
if (messageReaderFinal != null) {
|
||||
messageReaderFinal.cancel(true);
|
||||
}
|
||||
this.modemDriver.lock();
|
||||
try {
|
||||
this.modemDriver.closePort();
|
||||
} finally {
|
||||
this.modemDriver.unlock();
|
||||
}
|
||||
setStatus(Status.Stopped);
|
||||
}
|
||||
} finally {
|
||||
this.startAndStoplock.unlock();
|
||||
isStopping.set(false);
|
||||
}
|
||||
}
|
||||
return (getStatus() == Status.Stopped);
|
||||
}
|
||||
|
||||
final public void error() {
|
||||
this.stop();
|
||||
this.status = Status.Error;
|
||||
}
|
||||
|
||||
final public boolean send(OutboundMessage message) throws CommunicationException {
|
||||
try {
|
||||
if (getStatus() != Status.Started) {
|
||||
logger.debug("Outbound message routed via non-started gateway: {} ({})", message.toShortString(),
|
||||
getStatus());
|
||||
return false;
|
||||
}
|
||||
this.modemDriver.lock();
|
||||
try {
|
||||
if (getDeviceInformation().getMode() == Modes.PDU) {
|
||||
List<String> pdus = message.getPdus(getSmscNumber(), getNextMultipartReferenceNo());
|
||||
for (String pdu : pdus) {
|
||||
int j = pdu.length() / 2 - 1;
|
||||
int refNo = this.modemDriver.atSendPDUMessage(j, pdu);
|
||||
if (refNo >= 0) {
|
||||
message.setGatewayId(getGatewayId());
|
||||
message.setSentDate(new Date());
|
||||
message.getOperatorMessageIds().add(String.valueOf(refNo));
|
||||
message.setSentStatus(SentStatus.Sent);
|
||||
message.setFailureCause(FailureCause.None);
|
||||
} else {
|
||||
message.setSentStatus(SentStatus.Failed);
|
||||
message.setFailureCause(FailureCause.GatewayFailure);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
MsIsdn recipientAddress = message.getRecipientAddress();
|
||||
Payload payload = message.getPayload();
|
||||
if (recipientAddress == null) {
|
||||
throw new IllegalArgumentException("Recipient is null");
|
||||
}
|
||||
String text = payload.getText();
|
||||
if (payload.getType() == Type.Binary || text == null) {
|
||||
throw new IllegalArgumentException("Cannot send sms in binary format");
|
||||
}
|
||||
int refNo = this.modemDriver.atSendTEXTMessage(recipientAddress.getAddress(), text);
|
||||
if (refNo >= 0) {
|
||||
message.setGatewayId(getGatewayId());
|
||||
message.setSentDate(new Date());
|
||||
message.getOperatorMessageIds().add(String.valueOf(refNo));
|
||||
message.setSentStatus(SentStatus.Sent);
|
||||
message.setFailureCause(FailureCause.None);
|
||||
} else {
|
||||
message.setSentStatus(SentStatus.Failed);
|
||||
message.setFailureCause(FailureCause.GatewayFailure);
|
||||
}
|
||||
}
|
||||
if (message.getSentStatus() == SentStatus.Sent) {
|
||||
getDeviceInformation().increaseTotalSent();
|
||||
} else {
|
||||
getDeviceInformation().increaseTotalFailed();
|
||||
}
|
||||
} finally {
|
||||
this.modemDriver.unlock();
|
||||
}
|
||||
return message.getSentStatus() == SentStatus.Sent;
|
||||
} catch (CommunicationException e) {
|
||||
getDeviceInformation().increaseTotalFailures();
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
final public boolean delete(InboundMessage message) throws CommunicationException {
|
||||
if (getStatus() != Status.Started) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Delete message via non-started gateway: {} ({})", message.toShortString(), getStatus());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
this.modemDriver.lock();
|
||||
try {
|
||||
this.readMessagesSet.remove(message.getSignature());
|
||||
if (message.getMemIndex() >= 0) {
|
||||
return this.modemDriver.atDeleteMessage(message.getMemLocation(), message.getMemIndex()).isResponseOk();
|
||||
}
|
||||
if ((message.getMemIndex() == -1) && (message.getMpMemIndex().length() > 0)) {
|
||||
StringTokenizer tokens = new StringTokenizer(message.getMpMemIndex(), ",");
|
||||
while (tokens.hasMoreTokens()) {
|
||||
this.modemDriver.atDeleteMessage(message.getMemLocation(), Integer.valueOf(tokens.nextToken()));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
} finally {
|
||||
this.modemDriver.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
public boolean queue(OutboundMessage message) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Queue: {}", message.toShortString());
|
||||
}
|
||||
boolean added = messageQueue.add(message);
|
||||
IInboundOutboundMessageListener messageCallbackFinal = messageCallback;
|
||||
if (messageCallbackFinal != null) {
|
||||
messageCallbackFinal.messageSent(message);
|
||||
}
|
||||
startSendingQueue();
|
||||
return added;
|
||||
}
|
||||
|
||||
private void startSendingQueue() {
|
||||
if (messageQueue.size() > 0 && (!this.messageSender.isRunning())) {
|
||||
this.scheduledService.execute(messageSender);
|
||||
}
|
||||
}
|
||||
|
||||
public DeviceInformation getDeviceInformation() {
|
||||
return this.deviceInformation;
|
||||
}
|
||||
|
||||
public AbstractModemDriver getModemDriver() {
|
||||
return this.modemDriver;
|
||||
}
|
||||
|
||||
public String getSimPin() {
|
||||
return this.simPin;
|
||||
}
|
||||
|
||||
public MsIsdn getSmscNumber() {
|
||||
return this.smscNumber;
|
||||
}
|
||||
|
||||
public void setSmscNumber(MsIsdn smscNumber) {
|
||||
this.smscNumber = smscNumber;
|
||||
}
|
||||
|
||||
public HashSet<String> getReadMessagesSet() {
|
||||
return this.readMessagesSet;
|
||||
}
|
||||
|
||||
private void setStatus(Status status) {
|
||||
Status oldStatus = this.status;
|
||||
this.status = status;
|
||||
Status newStatus = this.status;
|
||||
IModemStatusListener modemStatusCallbackFinal = modemStatusCallback;
|
||||
if (modemStatusCallbackFinal != null) {
|
||||
modemStatusCallbackFinal.processStatusCallback(oldStatus, newStatus);
|
||||
}
|
||||
}
|
||||
|
||||
protected int getNextMultipartReferenceNo() {
|
||||
if (this.multipartReferenceNo == 0) {
|
||||
this.multipartReferenceNo = this.randomizer.nextInt();
|
||||
if (this.multipartReferenceNo < 0) {
|
||||
this.multipartReferenceNo *= -1;
|
||||
}
|
||||
this.multipartReferenceNo %= 65536;
|
||||
}
|
||||
this.multipartReferenceNo = (this.multipartReferenceNo + 1) % 65536;
|
||||
return this.multipartReferenceNo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuffer b = new StringBuffer(1024);
|
||||
b.append("== GATEWAY ========================================================================%n");
|
||||
b.append(String.format("Gateway ID: %s%n", getGatewayId()));
|
||||
b.append(String.format("-- Capabilities --%n"));
|
||||
b.append(capabilities.toString());
|
||||
b.append(String.format("-- Settings --%n"));
|
||||
b.append("== GATEWAY END ========================================================================%n");
|
||||
return b.toString();
|
||||
}
|
||||
|
||||
public String toShortString() {
|
||||
return getGatewayId() + String.format(" [%s]", this.modemDriver.getPortInfo());
|
||||
}
|
||||
|
||||
private boolean isPortAnIpAddress(String address) {
|
||||
try {
|
||||
InetAddress.getByName(address);
|
||||
return true;
|
||||
} catch (UnknownHostException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public void registerStatusListener(@Nullable IModemStatusListener smsModemStatusCallback) {
|
||||
this.modemStatusCallback = smsModemStatusCallback;
|
||||
}
|
||||
|
||||
public void registerMessageListener(@Nullable IInboundOutboundMessageListener messageCallback) {
|
||||
this.messageCallback = messageCallback;
|
||||
}
|
||||
|
||||
public void registerInformationListener(@Nullable IDeviceInformationListener deviceInformationListener) {
|
||||
this.deviceInformation.setDeviceInformationListener(deviceInformationListener);
|
||||
}
|
||||
|
||||
public void processMessage(InboundMessage message) {
|
||||
IInboundOutboundMessageListener messageCallbackFinal = this.messageCallback;
|
||||
if (messageCallbackFinal != null) {
|
||||
messageCallbackFinal.messageReceived(message);
|
||||
}
|
||||
}
|
||||
|
||||
public void processMessageSent(OutboundMessage message) {
|
||||
IInboundOutboundMessageListener messageCallbackFinal = this.messageCallback;
|
||||
if (messageCallbackFinal != null) {
|
||||
messageCallbackFinal.messageSent(message);
|
||||
}
|
||||
}
|
||||
|
||||
public void processDeliveryReport(DeliveryReportMessage message) {
|
||||
IInboundOutboundMessageListener messageCallbackFinal = this.messageCallback;
|
||||
if (messageCallbackFinal != null) {
|
||||
messageCallbackFinal.messageDelivered(message);
|
||||
}
|
||||
}
|
||||
|
||||
public Status getStatus() {
|
||||
return this.status;
|
||||
}
|
||||
|
||||
public final String getGatewayId() {
|
||||
return this.gatewayId;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return this.description;
|
||||
}
|
||||
|
||||
public void setDescription(String description) {
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
public void setCapabilities(Capabilities capabilities) {
|
||||
this.capabilities = capabilities;
|
||||
}
|
||||
}
|
||||
26
bundles/org.openhab.binding.smsmodem/src/3rdparty/java/org/smslib/ModemResponse.java
vendored
Normal file
26
bundles/org.openhab.binding.smsmodem/src/3rdparty/java/org/smslib/ModemResponse.java
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
package org.smslib;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* Extracted from SMSLib
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ModemResponse {
|
||||
String responseData;
|
||||
|
||||
boolean responseOk;
|
||||
|
||||
public ModemResponse(String responseData, boolean responseOk) {
|
||||
this.responseData = responseData;
|
||||
this.responseOk = responseOk;
|
||||
}
|
||||
|
||||
public String getResponseData() {
|
||||
return this.responseData;
|
||||
}
|
||||
|
||||
public boolean isResponseOk() {
|
||||
return this.responseOk;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.smslib;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
*
|
||||
* Exception class for internal SMSLib unrecoverable error
|
||||
*
|
||||
* @author Gwendal ROULLEAU - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class UnrecoverableSmslibException extends RuntimeException {
|
||||
|
||||
private static final long serialVersionUID = 7649578885702261759L;
|
||||
|
||||
public UnrecoverableSmslibException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public UnrecoverableSmslibException(String message, Exception cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package org.smslib.callback;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* The {@link IDeviceInformationListener} will receive informations
|
||||
* and statistics
|
||||
* Extracted from SMSLib
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public interface IDeviceInformationListener {
|
||||
|
||||
void setManufacturer(String manufacturer);
|
||||
|
||||
void setModel(String string);
|
||||
|
||||
void setSwVersion(String swVersion);
|
||||
|
||||
void setSerialNo(String serialNo);
|
||||
|
||||
void setImsi(String imsi);
|
||||
|
||||
void setRssi(String rssi);
|
||||
|
||||
void setMode(String mode);
|
||||
|
||||
public void setTotalSent(String totalSent);
|
||||
|
||||
public void setTotalFailed(String totalFailed);
|
||||
|
||||
public void setTotalReceived(String totalReceived);
|
||||
|
||||
public void setTotalFailures(String totalFailure);
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package org.smslib.callback;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.smslib.message.DeliveryReportMessage;
|
||||
import org.smslib.message.InboundMessage;
|
||||
import org.smslib.message.OutboundMessage;
|
||||
|
||||
/**
|
||||
*
|
||||
* Interface to implement to get messages and reports
|
||||
*
|
||||
* Extracted from SMSLib
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public interface IInboundOutboundMessageListener {
|
||||
|
||||
/**
|
||||
* Implement this method to get incoming messages
|
||||
*
|
||||
* @param message The inbound message received
|
||||
*/
|
||||
public void messageReceived(InboundMessage message);
|
||||
|
||||
/**
|
||||
* Implement this method to get warned when
|
||||
* a message is sent on the network
|
||||
*
|
||||
* @param message the message sent
|
||||
*/
|
||||
public void messageSent(OutboundMessage message);
|
||||
|
||||
/**
|
||||
* Implement this method to get warned when
|
||||
* a message previously sent is received by the recipient
|
||||
*
|
||||
* @param message the delivery report message
|
||||
*/
|
||||
public void messageDelivered(DeliveryReportMessage message);
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package org.smslib.callback;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.smslib.Modem.Status;
|
||||
|
||||
/**
|
||||
* Implement this interface to get status change
|
||||
*
|
||||
* Extracted from SMSLib
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public interface IModemStatusListener {
|
||||
|
||||
boolean processStatusCallback(Status oldStatus, Status newStatus);
|
||||
}
|
||||
540
bundles/org.openhab.binding.smsmodem/src/3rdparty/java/org/smslib/driver/AbstractModemDriver.java
vendored
Normal file
540
bundles/org.openhab.binding.smsmodem/src/3rdparty/java/org/smslib/driver/AbstractModemDriver.java
vendored
Normal file
@@ -0,0 +1,540 @@
|
||||
package org.smslib.driver;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.StringReader;
|
||||
import java.util.Properties;
|
||||
import java.util.StringTokenizer;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import java.util.concurrent.locks.Lock;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.smslib.Capabilities;
|
||||
import org.smslib.CommunicationException;
|
||||
import org.smslib.Capabilities.Caps;
|
||||
import org.smslib.DeviceInformation.Modes;
|
||||
import org.smslib.Modem;
|
||||
import org.smslib.ModemResponse;
|
||||
import org.smslib.UnrecoverableSmslibException;
|
||||
|
||||
/**
|
||||
* Extracted from SMSLib
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public abstract class AbstractModemDriver {
|
||||
static Logger logger = LoggerFactory.getLogger(AbstractModemDriver.class);
|
||||
|
||||
private Lock lock = new ReentrantLock();
|
||||
|
||||
Properties modemProperties;
|
||||
|
||||
@NonNullByDefault({})
|
||||
InputStream in;
|
||||
|
||||
@NonNullByDefault({})
|
||||
OutputStream out;
|
||||
|
||||
StringBuffer buffer = new StringBuffer(4096);
|
||||
|
||||
PollReader pollReader = new PollReader(this, "undefined");
|
||||
|
||||
Modem modem;
|
||||
|
||||
boolean responseOk;
|
||||
|
||||
String memoryLocations = "";
|
||||
|
||||
int atATHCounter = 0;
|
||||
|
||||
public abstract void openPort() throws CommunicationException;
|
||||
|
||||
public abstract void closePort();
|
||||
|
||||
public abstract String getPortInfo();
|
||||
|
||||
public AbstractModemDriver(Modem modem) {
|
||||
modemProperties = new Properties();
|
||||
try {
|
||||
ClassLoader classLoader = this.getClass().getClassLoader();
|
||||
if (classLoader != null) {
|
||||
try (InputStream inputStream = classLoader.getResourceAsStream("modem.properties")) {
|
||||
modemProperties.load(inputStream);
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new UnrecoverableSmslibException("Cannot instantiate modem driver", e);
|
||||
}
|
||||
this.modem = modem;
|
||||
}
|
||||
|
||||
public ModemResponse write(String data) throws CommunicationException {
|
||||
return write(data, false);
|
||||
}
|
||||
|
||||
public ModemResponse write(String data, boolean skipResponse) throws CommunicationException {
|
||||
this.lock.lock();
|
||||
try {
|
||||
logger.debug("{} <== {}", getPortInfo(), data);
|
||||
write(data.getBytes());
|
||||
countSheeps(Integer.valueOf(getModemSettings("command_wait_unit")));
|
||||
return (new ModemResponse((skipResponse ? "" : getResponse()), (skipResponse ? true : this.responseOk)));
|
||||
} finally {
|
||||
this.lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
protected boolean hasData() throws IOException {
|
||||
return ((this.in != null) && (this.in.available() > 0));
|
||||
}
|
||||
|
||||
protected int read() throws IOException {
|
||||
return this.in.read();
|
||||
}
|
||||
|
||||
protected void write(byte[] s) throws CommunicationException {
|
||||
int charDelay = Integer.valueOf(getModemSettings("char_wait_unit"));
|
||||
try {
|
||||
if (charDelay == 0) {
|
||||
this.out.write(s);
|
||||
} else {
|
||||
for (int i = 0; i < s.length; i++) {
|
||||
byte b = s[i];
|
||||
this.out.write(b);
|
||||
countSheeps(charDelay);
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new CommunicationException("Cannot write to device", e);
|
||||
}
|
||||
}
|
||||
|
||||
protected void write(byte s) throws CommunicationException {
|
||||
try {
|
||||
this.out.write(s);
|
||||
} catch (IOException e) {
|
||||
throw new CommunicationException("Cannot write data", e);
|
||||
}
|
||||
}
|
||||
|
||||
private String getResponse() throws CommunicationException {
|
||||
StringBuffer raw = new StringBuffer(256);
|
||||
StringBuffer b = new StringBuffer(256);
|
||||
try {
|
||||
while (true) {
|
||||
String line = getLineFromBuffer();
|
||||
logger.debug("{} >>> {}", getPortInfo(), line);
|
||||
this.buffer.delete(0, line.length() + 2);
|
||||
if (line.isBlank()) {
|
||||
continue;
|
||||
}
|
||||
if (line.charAt(0) == '^') {
|
||||
continue;
|
||||
}
|
||||
if (line.charAt(0) == '*') {
|
||||
continue;
|
||||
}
|
||||
if (line.startsWith("RING")) {
|
||||
continue;
|
||||
}
|
||||
if (line.startsWith("+STIN:")) {
|
||||
continue;
|
||||
}
|
||||
if (Integer.valueOf(getModemSettings("cpin_without_ok")) == 1) {
|
||||
if (line.startsWith("+CPIN:")) {
|
||||
raw.append(line);
|
||||
raw.append("$");
|
||||
b.append(line);
|
||||
this.responseOk = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (line.startsWith("+CLIP:")) {
|
||||
write("+++", true);
|
||||
countSheeps(Integer.valueOf(getModemSettings("wait_unit")));
|
||||
write("ATH\r", true);
|
||||
logger.debug("+++ INCREASE ATH");
|
||||
this.atATHCounter++;
|
||||
// no need for a call handler. discard
|
||||
countSheeps(Integer.valueOf(getModemSettings("wait_unit")));
|
||||
continue;
|
||||
}
|
||||
if (line.indexOf("OK") == 0) {
|
||||
if (this.atATHCounter > 0) {
|
||||
logger.debug("--- DECREASE ATH");
|
||||
this.atATHCounter--;
|
||||
continue;
|
||||
}
|
||||
this.responseOk = true;
|
||||
break;
|
||||
}
|
||||
if ((line.indexOf("ERROR") == 0) || (line.indexOf("+CMS ERROR") == 0)
|
||||
|| (line.indexOf("+CME ERROR") == 0)) {
|
||||
logger.warn("{} ERR==> {}", getPortInfo(), line);
|
||||
this.responseOk = false;
|
||||
break;
|
||||
}
|
||||
if (b.length() > 0) {
|
||||
b.append('\n');
|
||||
}
|
||||
raw.append(line);
|
||||
raw.append("$");
|
||||
b.append(line);
|
||||
}
|
||||
} catch (IOException | TimeoutException e) {
|
||||
throw new CommunicationException("Cannot get response", e);
|
||||
}
|
||||
logger.debug("{} ==> {}", getPortInfo(), raw.toString());
|
||||
return b.toString();
|
||||
}
|
||||
|
||||
private String getLineFromBuffer() throws TimeoutException, IOException {
|
||||
long startTimeout = System.currentTimeMillis();
|
||||
long endTimeout = startTimeout;
|
||||
while (this.buffer.indexOf("\r") == -1) {
|
||||
endTimeout += Integer.valueOf(getModemSettings("wait_unit"));
|
||||
if ((endTimeout - startTimeout) > Integer.valueOf(getModemSettings("timeout"))) {
|
||||
throw new TimeoutException("Timeout elapsed for " + getPortInfo());
|
||||
}
|
||||
countSheeps(Integer.valueOf(getModemSettings("wait_unit")));
|
||||
}
|
||||
BufferedReader r = new BufferedReader(new StringReader(this.buffer.toString()));
|
||||
String line = r.readLine();
|
||||
r.close();
|
||||
return line;
|
||||
}
|
||||
|
||||
public void clearResponses() {
|
||||
countSheeps(Integer.valueOf(getModemSettings("wait_unit")) * 1);
|
||||
while (this.buffer.length() > 0) {
|
||||
this.buffer.delete(0, this.buffer.length());
|
||||
countSheeps(Integer.valueOf(getModemSettings("wait_unit")) * 1);
|
||||
}
|
||||
}
|
||||
|
||||
public String getMemoryLocations() {
|
||||
return this.memoryLocations;
|
||||
}
|
||||
|
||||
public void initializeModem() throws CommunicationException {
|
||||
int counter = 0;
|
||||
this.lock.lock();
|
||||
try {
|
||||
atAT();
|
||||
atAT();
|
||||
atAT();
|
||||
atAT();
|
||||
atEchoOff();
|
||||
clearResponses();
|
||||
this.modem.getDeviceInformation().setManufacturer(atGetManufacturer().getResponseData());
|
||||
this.modem.getDeviceInformation().setModel(atGetModel().getResponseData());
|
||||
countSheeps(Integer.valueOf(getModemSettings("wait_unit")));
|
||||
atFromModemSettings("init1");
|
||||
countSheeps(Integer.valueOf(getModemSettings("wait_unit"))
|
||||
* Integer.valueOf(getModemSettings("delay_after_init1")));
|
||||
atFromModemSettings("init2");
|
||||
countSheeps(Integer.valueOf(getModemSettings("wait_unit"))
|
||||
* Integer.valueOf(getModemSettings("delay_after_init2")));
|
||||
clearResponses();
|
||||
atEchoOff();
|
||||
clearResponses();
|
||||
atFromModemSettings("pre_pin");
|
||||
countSheeps(Integer.valueOf(getModemSettings("wait_unit"))
|
||||
* Integer.valueOf(getModemSettings("delay_after_pre_pin")));
|
||||
while (true) {
|
||||
counter++;
|
||||
if (counter == 5) {
|
||||
throw new CommunicationException("Modem does not correspond correctly, giving up...");
|
||||
}
|
||||
ModemResponse simStatus = atGetSimStatus();
|
||||
if (simStatus.getResponseData().indexOf("SIM PIN") >= 0) {
|
||||
if (this.modem.getSimPin().isBlank()) {
|
||||
throw new CommunicationException("SIM PIN requested but not defined!");
|
||||
}
|
||||
atEnterPin(this.modem.getSimPin());
|
||||
} else if (simStatus.getResponseData().indexOf("READY") >= 0) {
|
||||
break;
|
||||
} else if (simStatus.getResponseData().indexOf("OK") >= 0) {
|
||||
break;
|
||||
} else if (simStatus.getResponseData().indexOf("ERROR") >= 0) {
|
||||
logger.error("SIM PIN error!");
|
||||
}
|
||||
logger.debug("SIM PIN Not ok, waiting for a while...");
|
||||
countSheeps(Integer.valueOf(getModemSettings("wait_unit"))
|
||||
* Integer.valueOf(getModemSettings("delay_on_sim_error")));
|
||||
}
|
||||
atFromModemSettings("post_pin");
|
||||
countSheeps(Integer.valueOf(getModemSettings("wait_unit"))
|
||||
* Integer.valueOf(getModemSettings("delay_after_post_pin")));
|
||||
atEnableClip();
|
||||
if (!atNetworkRegistration().isResponseOk()) {
|
||||
throw new CommunicationException("Network registration failed!");
|
||||
}
|
||||
atVerboseOff();
|
||||
if (atSetPDUMode().isResponseOk()) {
|
||||
this.modem.getDeviceInformation().setMode(Modes.PDU);
|
||||
} else {
|
||||
logger.debug("Modem does not support PDU, trying to switch to TEXT...");
|
||||
if (atSetTEXTMode().isResponseOk()) {
|
||||
Capabilities caps = new Capabilities();
|
||||
caps.set(Caps.CanSendMessage);
|
||||
this.modem.setCapabilities(caps);
|
||||
this.modem.getDeviceInformation().setMode(Modes.TEXT);
|
||||
} else {
|
||||
throw new CommunicationException("Neither PDU nor TEXT mode are supported by this modem!");
|
||||
}
|
||||
}
|
||||
atCnmiOff();
|
||||
retrieveMemoryLocations();
|
||||
refreshDeviceInformation();
|
||||
|
||||
} finally {
|
||||
this.lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
public void refreshDeviceInformation() throws CommunicationException {
|
||||
this.modem.getDeviceInformation().setManufacturer(atGetManufacturer().getResponseData());
|
||||
this.modem.getDeviceInformation().setModel(atGetModel().getResponseData());
|
||||
this.modem.getDeviceInformation().setSerialNo(atGetSerialNo().getResponseData());
|
||||
this.modem.getDeviceInformation().setImsi(atGetImsi().getResponseData());
|
||||
this.modem.getDeviceInformation().setSwVersion(atGetSWVersion().getResponseData());
|
||||
this.refreshRssi();
|
||||
}
|
||||
|
||||
public void refreshRssi() throws CommunicationException {
|
||||
String s = atGetSignalStrengh().getResponseData();
|
||||
if (this.responseOk) {
|
||||
String s1 = s.split("\\R")[0]; // ensure to get first line only
|
||||
s1 = s1.substring(s.indexOf(':') + 1).trim();
|
||||
StringTokenizer tokens = new StringTokenizer(s1, ",");
|
||||
int rssi = Integer.valueOf(tokens.nextToken().trim());
|
||||
this.modem.getDeviceInformation().setRssi(rssi == 99 ? 99 : (-113 + 2 * rssi));
|
||||
}
|
||||
}
|
||||
|
||||
void retrieveMemoryLocations() throws CommunicationException {
|
||||
if (this.memoryLocations.isBlank()) {
|
||||
this.memoryLocations = getModemSettings("memory_locations");
|
||||
if (this.memoryLocations.isBlank()) {
|
||||
this.memoryLocations = "";
|
||||
}
|
||||
if (this.memoryLocations.isBlank()) {
|
||||
try {
|
||||
String response = atGetMemoryLocations().getResponseData();
|
||||
if (response.indexOf("+CPMS:") >= 0) {
|
||||
int i, j;
|
||||
i = response.indexOf('(');
|
||||
while (response.charAt(i) == '(') {
|
||||
i++;
|
||||
}
|
||||
j = i;
|
||||
while (response.charAt(j) != ')') {
|
||||
j++;
|
||||
}
|
||||
response = response.substring(i, j);
|
||||
StringTokenizer tokens = new StringTokenizer(response, ",");
|
||||
while (tokens.hasMoreTokens()) {
|
||||
String loc = tokens.nextToken().replaceAll("\"", "");
|
||||
if (!"MT".equalsIgnoreCase(loc) && this.memoryLocations.indexOf(loc) < 0) {
|
||||
this.memoryLocations += loc;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.memoryLocations = "SM";
|
||||
logger.debug("CPMS detection failed, proceeding with default memory 'SM'.");
|
||||
}
|
||||
} catch (CommunicationException e) {
|
||||
this.memoryLocations = "SM";
|
||||
logger.debug("CPMS detection failed, proceeding with default memory 'SM'.", e);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
logger.debug("Using given memory locations: {}", this.memoryLocations);
|
||||
}
|
||||
}
|
||||
|
||||
public String getSignature(boolean complete) {
|
||||
String manufacturer = this.modem.getDeviceInformation().getManufacturer().toLowerCase().replaceAll(" ", "")
|
||||
.replaceAll(" ", "").replaceAll(" ", "");
|
||||
String model = this.modem.getDeviceInformation().getModel().toLowerCase().replaceAll(" ", "")
|
||||
.replaceAll(" ", "").replaceAll(" ", "");
|
||||
return (complete ? manufacturer + "_" + model : manufacturer);
|
||||
}
|
||||
|
||||
protected ModemResponse atAT() throws CommunicationException {
|
||||
return write("AT\r", true);
|
||||
}
|
||||
|
||||
protected ModemResponse atATWithResponse() throws CommunicationException {
|
||||
return write("AT\r");
|
||||
}
|
||||
|
||||
protected ModemResponse atEchoOff() throws CommunicationException {
|
||||
return write("ATE0\r", true);
|
||||
}
|
||||
|
||||
protected ModemResponse atGetSimStatus() throws CommunicationException {
|
||||
return write("AT+CPIN?\r");
|
||||
}
|
||||
|
||||
protected ModemResponse atEnterPin(String pin) throws CommunicationException {
|
||||
return write(String.format("AT+CPIN=\"%s\"\r", pin));
|
||||
}
|
||||
|
||||
protected ModemResponse atNetworkRegistration() throws CommunicationException {
|
||||
write("AT+CREG=1\r");
|
||||
countSheeps(Integer.valueOf(getModemSettings("wait_unit"))
|
||||
* Integer.valueOf(getModemSettings("delay_network_registration")));
|
||||
return write("AT+CREG?\r");
|
||||
}
|
||||
|
||||
protected ModemResponse atEnableClip() throws CommunicationException {
|
||||
return write("AT+CLIP=1\r");
|
||||
}
|
||||
|
||||
protected ModemResponse atVerboseOff() throws CommunicationException {
|
||||
return write("AT+CMEE=0\r");
|
||||
}
|
||||
|
||||
protected ModemResponse atSetPDUMode() throws CommunicationException {
|
||||
return write("AT+CMGF=0\r");
|
||||
}
|
||||
|
||||
protected ModemResponse atSetTEXTMode() throws CommunicationException {
|
||||
return write("AT+CMGF=1\r");
|
||||
}
|
||||
|
||||
protected ModemResponse atCnmiOff() throws CommunicationException {
|
||||
return write("AT+CNMI=2,0,0,0,0\r");
|
||||
}
|
||||
|
||||
protected ModemResponse atGetManufacturer() throws CommunicationException {
|
||||
return write("AT+CGMI\r");
|
||||
}
|
||||
|
||||
protected ModemResponse atGetModel() throws CommunicationException {
|
||||
return write("AT+CGMM\r");
|
||||
}
|
||||
|
||||
protected ModemResponse atGetImsi() throws CommunicationException {
|
||||
return write("AT+CIMI\r");
|
||||
}
|
||||
|
||||
protected ModemResponse atGetSerialNo() throws CommunicationException {
|
||||
return write("AT+CGSN\r");
|
||||
}
|
||||
|
||||
protected ModemResponse atGetSWVersion() throws CommunicationException {
|
||||
return write("AT+CGMR\r");
|
||||
}
|
||||
|
||||
protected ModemResponse atGetSignalStrengh() throws CommunicationException {
|
||||
return write("AT+CSQ\r");
|
||||
}
|
||||
|
||||
public int atSendPDUMessage(int size, String pdu) throws CommunicationException {
|
||||
write(String.format("AT+CMGS=%d\r", size), true);
|
||||
while (this.buffer.length() == 0) {
|
||||
countSheeps(Integer.valueOf(getModemSettings("wait_unit")));
|
||||
}
|
||||
countSheeps(Integer.valueOf(getModemSettings("wait_unit"))
|
||||
* Integer.valueOf(getModemSettings("delay_before_send_pdu")));
|
||||
clearResponses();
|
||||
write(pdu, true);
|
||||
write((byte) 26);
|
||||
String response = getResponse();
|
||||
if (this.responseOk && response.contains(":")) {
|
||||
return Integer.parseInt(response.substring(response.indexOf(":") + 1).trim());
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
public int atSendTEXTMessage(String recipient, String text) throws CommunicationException {
|
||||
write(String.format("AT+CSCS=\"%s\"\r", "UTF-8"), true);
|
||||
if (!this.responseOk) {
|
||||
throw new CommunicationException("Unsupported encoding: UTF-8");
|
||||
}
|
||||
write(String.format("AT+CMGS=\"%s\"\r", recipient), true);
|
||||
while (this.buffer.length() == 0) {
|
||||
countSheeps(Integer.valueOf(getModemSettings("wait_unit")));
|
||||
}
|
||||
countSheeps(Integer.valueOf(getModemSettings("wait_unit"))
|
||||
* Integer.valueOf(getModemSettings("delay_before_send_pdu")));
|
||||
clearResponses();
|
||||
write(text, true);
|
||||
write((byte) 26);
|
||||
String response = getResponse();
|
||||
if (this.responseOk) {
|
||||
return Integer.parseInt(response.substring(response.indexOf(":") + 1).trim());
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
public ModemResponse atGetMemoryLocations() throws CommunicationException {
|
||||
return write("AT+CPMS=?\r");
|
||||
}
|
||||
|
||||
public ModemResponse atSwitchMemoryLocation(String memoryLocation) throws CommunicationException {
|
||||
return write(String.format("AT+CPMS=\"%s\"\r", memoryLocation));
|
||||
}
|
||||
|
||||
public ModemResponse atGetMessages(String memoryLocation) throws CommunicationException {
|
||||
if (atSwitchMemoryLocation(memoryLocation).isResponseOk()) {
|
||||
return (this.modem.getDeviceInformation().getMode() == Modes.PDU ? write("AT+CMGL=4\r")
|
||||
: write("AT+CMGL=\"ALL\"\r"));
|
||||
}
|
||||
return new ModemResponse("", false);
|
||||
}
|
||||
|
||||
public ModemResponse atDeleteMessage(String memoryLocation, int memoryIndex) throws CommunicationException {
|
||||
if (atSwitchMemoryLocation(memoryLocation).isResponseOk()) {
|
||||
return write(String.format("AT+CMGD=%d\r", memoryIndex));
|
||||
}
|
||||
return new ModemResponse("", false);
|
||||
}
|
||||
|
||||
public ModemResponse atFromModemSettings(String key) throws CommunicationException {
|
||||
String atCommand = getModemSettings(key);
|
||||
if (!atCommand.isBlank()) {
|
||||
return write(atCommand);
|
||||
}
|
||||
return new ModemResponse("", true);
|
||||
}
|
||||
|
||||
public String getModemSettings(String key) {
|
||||
String fullSignature = getSignature(true);
|
||||
String shortSignature = getSignature(false);
|
||||
String value = "";
|
||||
if (!fullSignature.isBlank()) {
|
||||
value = modemProperties.getProperty(fullSignature + "." + key);
|
||||
}
|
||||
if ((value == null || value.isBlank()) && !shortSignature.isBlank()) {
|
||||
value = modemProperties.getProperty(shortSignature + "." + key);
|
||||
}
|
||||
if (value == null || value.isBlank()) {
|
||||
value = modemProperties.getProperty("default" + "." + key);
|
||||
}
|
||||
return ((value == null || value.isBlank()) ? "" : value);
|
||||
}
|
||||
|
||||
public void lock() {
|
||||
this.lock.lock();
|
||||
}
|
||||
|
||||
public void unlock() {
|
||||
this.lock.unlock();
|
||||
}
|
||||
|
||||
protected static void countSheeps(int n) {
|
||||
try {
|
||||
Thread.sleep(n);
|
||||
} catch (InterruptedException e) {
|
||||
// Nothing here...
|
||||
}
|
||||
}
|
||||
}
|
||||
85
bundles/org.openhab.binding.smsmodem/src/3rdparty/java/org/smslib/driver/IPModemDriver.java
vendored
Normal file
85
bundles/org.openhab.binding.smsmodem/src/3rdparty/java/org/smslib/driver/IPModemDriver.java
vendored
Normal file
@@ -0,0 +1,85 @@
|
||||
package org.smslib.driver;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.Socket;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.smslib.CommunicationException;
|
||||
import org.smslib.Modem;
|
||||
|
||||
/**
|
||||
* Extracted from SMSLib
|
||||
* Manage communication with ser2net (or equivalent)
|
||||
*
|
||||
* Extracted from SMSLib
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class IPModemDriver extends AbstractModemDriver {
|
||||
static Logger logger = LoggerFactory.getLogger(IPModemDriver.class);
|
||||
|
||||
String address;
|
||||
|
||||
int port;
|
||||
|
||||
@Nullable
|
||||
Socket socket;
|
||||
|
||||
public IPModemDriver(Modem modem, String address, int port) {
|
||||
super(modem);
|
||||
this.address = address;
|
||||
this.port = port;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void openPort() throws CommunicationException {
|
||||
logger.debug("Opening IP port: {}", getPortInfo());
|
||||
try {
|
||||
Socket openSocket = new Socket(this.address, this.port);
|
||||
openSocket.setReceiveBufferSize(Integer.valueOf(getModemSettings("port_buffer")));
|
||||
openSocket.setSendBufferSize(Integer.valueOf(getModemSettings("port_buffer")));
|
||||
openSocket.setSoTimeout(30000);
|
||||
openSocket.setTcpNoDelay(true);
|
||||
this.in = openSocket.getInputStream();
|
||||
this.out = openSocket.getOutputStream();
|
||||
this.socket = openSocket;
|
||||
} catch (IOException e) {
|
||||
throw new CommunicationException("Cannot open port", e);
|
||||
}
|
||||
countSheeps(Integer.valueOf(getModemSettings("after_ip_connect_wait_unit")));
|
||||
this.pollReader = new PollReader(this, getPortInfo());
|
||||
this.pollReader.setDaemon(true);
|
||||
this.pollReader.start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void closePort() {
|
||||
logger.debug("Closing IP port: {}", getPortInfo());
|
||||
try {
|
||||
this.pollReader.cancel();
|
||||
this.pollReader.join();
|
||||
if (in != null) {
|
||||
this.in.close();
|
||||
this.in = null;
|
||||
}
|
||||
if (out != null) {
|
||||
this.out.close();
|
||||
this.out = null;
|
||||
}
|
||||
Socket finalSocket = socket;
|
||||
if (finalSocket != null) {
|
||||
finalSocket.close();
|
||||
}
|
||||
} catch (InterruptedException | IOException e) {
|
||||
logger.debug("Cannot close port");
|
||||
}
|
||||
countSheeps(Integer.valueOf(getModemSettings("after_ip_connect_wait_unit")));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPortInfo() {
|
||||
return this.address + ":" + this.port;
|
||||
}
|
||||
}
|
||||
101
bundles/org.openhab.binding.smsmodem/src/3rdparty/java/org/smslib/driver/JSerialModemDriver.java
vendored
Normal file
101
bundles/org.openhab.binding.smsmodem/src/3rdparty/java/org/smslib/driver/JSerialModemDriver.java
vendored
Normal file
@@ -0,0 +1,101 @@
|
||||
package org.smslib.driver;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.core.io.transport.serial.PortInUseException;
|
||||
import org.openhab.core.io.transport.serial.SerialPort;
|
||||
import org.openhab.core.io.transport.serial.SerialPortIdentifier;
|
||||
import org.openhab.core.io.transport.serial.SerialPortManager;
|
||||
import org.openhab.core.io.transport.serial.UnsupportedCommOperationException;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.smslib.CommunicationException;
|
||||
import org.smslib.Modem;
|
||||
|
||||
/**
|
||||
* Manage communications with a serial modem
|
||||
* Extracted from SMSLib
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class JSerialModemDriver extends AbstractModemDriver {
|
||||
|
||||
private static final int ONE_STOP_BIT = 1;
|
||||
static final public int NO_PARITY = 0;
|
||||
static final public int FLOW_CONTROL_RTS_ENABLED = 0x00000001;
|
||||
static final public int FLOW_CONTROL_CTS_ENABLED = 0x00000010;
|
||||
|
||||
static Logger logger = LoggerFactory.getLogger(JSerialModemDriver.class);
|
||||
|
||||
String portName;
|
||||
|
||||
int baudRate;
|
||||
|
||||
private final SerialPortManager serialPortManager;
|
||||
|
||||
@Nullable
|
||||
SerialPort serialPort;
|
||||
|
||||
public JSerialModemDriver(SerialPortManager serialPortManager, Modem modem, String port, int baudRate) {
|
||||
super(modem);
|
||||
this.portName = port;
|
||||
this.baudRate = baudRate;
|
||||
this.serialPortManager = serialPortManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void openPort() throws CommunicationException {
|
||||
SerialPortIdentifier portIdentifier = serialPortManager.getIdentifier(portName);
|
||||
if (portIdentifier == null) {
|
||||
throw new CommunicationException("SMSModem cannot use serial port " + portName);
|
||||
}
|
||||
try {
|
||||
SerialPort openedSerialPort = portIdentifier.open("org.openhab.binding.smsmodem", 2000);
|
||||
openedSerialPort.setSerialPortParams(baudRate, 8, ONE_STOP_BIT, NO_PARITY);
|
||||
openedSerialPort.setFlowControlMode(FLOW_CONTROL_RTS_ENABLED | FLOW_CONTROL_CTS_ENABLED);
|
||||
this.in = openedSerialPort.getInputStream();
|
||||
this.out = openedSerialPort.getOutputStream();
|
||||
serialPort = openedSerialPort;
|
||||
this.pollReader = new PollReader(this, getPortInfo());
|
||||
this.pollReader.setDaemon(true);
|
||||
this.pollReader.start();
|
||||
|
||||
} catch (PortInUseException | UnsupportedCommOperationException | IOException e) {
|
||||
throw new CommunicationException("Cannot open port", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void closePort() {
|
||||
try {
|
||||
logger.debug("Closing comm port: {}", getPortInfo());
|
||||
this.pollReader.cancel();
|
||||
try {
|
||||
this.pollReader.join();
|
||||
} catch (InterruptedException ex) {
|
||||
logger.debug("PollReader closing exception", ex);
|
||||
}
|
||||
if (in != null) {
|
||||
this.in.close();
|
||||
this.in = null;
|
||||
}
|
||||
if (out != null) {
|
||||
this.out.close();
|
||||
this.out = null;
|
||||
}
|
||||
final SerialPort finalSerialPort = serialPort;
|
||||
if (finalSerialPort != null) {
|
||||
finalSerialPort.close();
|
||||
serialPort = null;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
logger.debug("Closing port exception", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPortInfo() {
|
||||
return this.portName + ":" + this.baudRate;
|
||||
}
|
||||
}
|
||||
75
bundles/org.openhab.binding.smsmodem/src/3rdparty/java/org/smslib/driver/PollReader.java
vendored
Normal file
75
bundles/org.openhab.binding.smsmodem/src/3rdparty/java/org/smslib/driver/PollReader.java
vendored
Normal file
@@ -0,0 +1,75 @@
|
||||
package org.smslib.driver;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.smslib.CommunicationException;
|
||||
|
||||
/**
|
||||
* Manage communications with a serial modem
|
||||
* Extracted from SMSLib
|
||||
*/
|
||||
public class PollReader extends Thread {
|
||||
|
||||
static Logger logger = LoggerFactory.getLogger(AbstractModemDriver.class);
|
||||
|
||||
private boolean shouldCancel = false;
|
||||
|
||||
private boolean foundClip = false;
|
||||
|
||||
public PollReader(AbstractModemDriver modemDriver, String threadId) {
|
||||
super();
|
||||
this.modemDriver = modemDriver;
|
||||
this.threadId = threadId;
|
||||
}
|
||||
|
||||
private AbstractModemDriver modemDriver;
|
||||
|
||||
private String threadId;
|
||||
|
||||
public void cancel() {
|
||||
this.shouldCancel = true;
|
||||
this.interrupt();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
logger.debug("Started!");
|
||||
currentThread().setName("OH-binding-smsmodem-" + threadId);
|
||||
while (!this.shouldCancel) {
|
||||
try {
|
||||
while (modemDriver.hasData()) {
|
||||
char c = (char) modemDriver.read();
|
||||
modemDriver.buffer.append(c);
|
||||
if (modemDriver.buffer.indexOf("+CLIP") >= 0) {
|
||||
if (!this.foundClip) {
|
||||
this.foundClip = true;
|
||||
new ClipReader().start();
|
||||
}
|
||||
} else {
|
||||
this.foundClip = false;
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
logger.debug("Cannot proceed to poll device", e);
|
||||
modemDriver.modem.error();
|
||||
}
|
||||
AbstractModemDriver.countSheeps(Integer.valueOf(modemDriver.getModemSettings("poll_reader")));
|
||||
}
|
||||
logger.debug("Stopped!");
|
||||
}
|
||||
|
||||
public class ClipReader extends Thread {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
Thread.sleep(1000);
|
||||
modemDriver.atATWithResponse();
|
||||
} catch (InterruptedException | CommunicationException e) {
|
||||
logger.debug("Cannot proceed to read clip", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
226
bundles/org.openhab.binding.smsmodem/src/3rdparty/java/org/smslib/message/AbstractMessage.java
vendored
Normal file
226
bundles/org.openhab.binding.smsmodem/src/3rdparty/java/org/smslib/message/AbstractMessage.java
vendored
Normal file
@@ -0,0 +1,226 @@
|
||||
package org.smslib.message;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.math.BigInteger;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.Date;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.smslib.UnrecoverableSmslibException;
|
||||
import org.smslib.message.OutboundMessage.SentStatus;
|
||||
|
||||
/**
|
||||
* Extracted from SMSLib
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public abstract class AbstractMessage implements Serializable {
|
||||
public enum Encoding {
|
||||
Enc7,
|
||||
Enc8,
|
||||
EncUcs2,
|
||||
EncCustom;
|
||||
}
|
||||
|
||||
public enum DcsClass {
|
||||
None,
|
||||
Flash,
|
||||
Me,
|
||||
Sim,
|
||||
Te
|
||||
}
|
||||
|
||||
public enum Type {
|
||||
Inbound,
|
||||
Outbound,
|
||||
StatusReport
|
||||
}
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
Date creationDate = new Date();
|
||||
|
||||
String id = UUID.randomUUID().toString();
|
||||
|
||||
MsIsdn originatorAddress = new MsIsdn();
|
||||
|
||||
@Nullable
|
||||
MsIsdn recipientAddress = new MsIsdn();
|
||||
|
||||
Payload payload = new Payload("");
|
||||
|
||||
Type type = Type.Inbound;
|
||||
|
||||
Encoding encoding = Encoding.Enc7;
|
||||
|
||||
DcsClass dcsClass = DcsClass.Sim;
|
||||
|
||||
String gatewayId = "";
|
||||
|
||||
int sourcePort = -1;
|
||||
|
||||
int destinationPort = -1;
|
||||
|
||||
@Nullable
|
||||
Date sentDate;
|
||||
|
||||
public AbstractMessage() {
|
||||
}
|
||||
|
||||
public AbstractMessage(Type type, MsIsdn originatorAddress, @Nullable MsIsdn recipientAddress,
|
||||
@Nullable Payload payload) {
|
||||
this.type = type;
|
||||
this.originatorAddress = originatorAddress;
|
||||
this.recipientAddress = recipientAddress;
|
||||
if (payload != null) {
|
||||
setPayload(payload);
|
||||
}
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return this.id;
|
||||
}
|
||||
|
||||
public void setId(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public Date getCreationDate() {
|
||||
return this.creationDate;
|
||||
}
|
||||
|
||||
public MsIsdn getOriginatorAddress() {
|
||||
return this.originatorAddress;
|
||||
}
|
||||
|
||||
public @Nullable MsIsdn getRecipientAddress() {
|
||||
return this.recipientAddress;
|
||||
}
|
||||
|
||||
public Payload getPayload() {
|
||||
return this.payload;
|
||||
}
|
||||
|
||||
public void setPayload(Payload payload) {
|
||||
this.payload = payload;
|
||||
}
|
||||
|
||||
public Type getType() {
|
||||
return this.type;
|
||||
}
|
||||
|
||||
public Encoding getEncoding() {
|
||||
return this.encoding;
|
||||
}
|
||||
|
||||
public void setEncoding(Encoding encoding) {
|
||||
this.encoding = encoding;
|
||||
}
|
||||
|
||||
public DcsClass getDcsClass() {
|
||||
return this.dcsClass;
|
||||
}
|
||||
|
||||
public void setDcsClass(DcsClass dcsClass) {
|
||||
this.dcsClass = dcsClass;
|
||||
}
|
||||
|
||||
public String getGatewayId() {
|
||||
return this.gatewayId;
|
||||
}
|
||||
|
||||
public void setGatewayId(String gatewayId) {
|
||||
this.gatewayId = gatewayId;
|
||||
}
|
||||
|
||||
public int getSourcePort() {
|
||||
return this.sourcePort;
|
||||
}
|
||||
|
||||
public void setSourcePort(int sourcePort) {
|
||||
this.sourcePort = sourcePort;
|
||||
}
|
||||
|
||||
public int getDestinationPort() {
|
||||
return this.destinationPort;
|
||||
}
|
||||
|
||||
public void setDestinationPort(int destinationPort) {
|
||||
this.destinationPort = destinationPort;
|
||||
}
|
||||
|
||||
public @Nullable Date getSentDate() {
|
||||
Date sentDateFinal = this.sentDate;
|
||||
return (sentDateFinal != null ? (Date) sentDateFinal.clone() : null);
|
||||
}
|
||||
|
||||
public void setSentDate(Date sentDate) {
|
||||
this.sentDate = new Date(sentDate.getTime());
|
||||
}
|
||||
|
||||
public abstract String getSignature();
|
||||
|
||||
public abstract String toShortString();
|
||||
|
||||
public String hashSignature(String s) {
|
||||
try {
|
||||
MessageDigest md = MessageDigest.getInstance("SHA-1");
|
||||
md.update(s.getBytes(), 0, s.length());
|
||||
BigInteger i = new BigInteger(1, md.digest());
|
||||
return String.format("%1$032x", i);
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new UnrecoverableSmslibException("Cannot find hash algorithm", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuffer b = new StringBuffer(1024);
|
||||
b.append(String
|
||||
.format("%n== MESSAGE START ======================================================================%n"));
|
||||
b.append(String.format("CLASS: %s%n", this.getClass().toString()));
|
||||
b.append(String.format("Message ID: %s%n", getId()));
|
||||
b.append(String.format("Message Signature: %s%n", getSignature()));
|
||||
b.append(String.format("Via Gateway: %s%n", getGatewayId()));
|
||||
b.append(String.format("Creation Date: %s%n", getCreationDate()));
|
||||
b.append(String.format("Type: %s%n", getType()));
|
||||
b.append(String.format("Encoding: %s%n", getEncoding()));
|
||||
b.append(String.format("DCS Class: %s%n", getDcsClass()));
|
||||
b.append(String.format("Source Port: %s%n", getSourcePort()));
|
||||
b.append(String.format("Destination Port: %s%n", getDestinationPort()));
|
||||
b.append(String.format("Originator Address: %s%n", getOriginatorAddress()));
|
||||
b.append(String.format("Recipient Address: %s%n", getRecipientAddress()));
|
||||
b.append(String.format("Payload Type: %s%n", payload.getType()));
|
||||
b.append(String.format("Text payload: %s%n", payload.getText() == null ? "null" : payload.getText()));
|
||||
if (this instanceof InboundMessage) {
|
||||
b.append(String.format("Sent Date: %s%n", getSentDate()));
|
||||
b.append(String.format("Memory Storage Location: %s%n", ((InboundMessage) this).getMemLocation()));
|
||||
b.append(String.format("Memory Index: %d%n", ((InboundMessage) this).getMemIndex()));
|
||||
b.append(String.format("Memory MP Index: %s%n", ((InboundMessage) this).getMpMemIndex()));
|
||||
}
|
||||
if (this instanceof OutboundMessage) {
|
||||
b.append(String.format("Sent Date: %s%n",
|
||||
(((OutboundMessage) this).getSentStatus() == SentStatus.Sent ? getSentDate() : "N/A")));
|
||||
String ids = "";
|
||||
for (String opId : ((OutboundMessage) this).getOperatorMessageIds()) {
|
||||
ids += (ids.length() == 0 ? opId : "," + opId);
|
||||
}
|
||||
b.append(String.format("Operator Message IDs: %s%n", ids));
|
||||
b.append(String.format("Status: %s%n", ((OutboundMessage) this).getSentStatus().toString()));
|
||||
b.append(String.format("Failure: %s%n", ((OutboundMessage) this).getFailureCause().toString()));
|
||||
b.append(String.format("Request Delivery Reports: %b%n",
|
||||
((OutboundMessage) this).getRequestDeliveryReport()));
|
||||
}
|
||||
if (this instanceof DeliveryReportMessage) {
|
||||
b.append(String.format("Original Operator Message Id: %s%n",
|
||||
((DeliveryReportMessage) this).getOriginalOperatorMessageId()));
|
||||
b.append(String.format("Delivery Date: %s%n", ((DeliveryReportMessage) this).getOriginalReceivedDate()));
|
||||
b.append(String.format("Delivery Status: %s%n", ((DeliveryReportMessage) this).getDeliveryStatus()));
|
||||
}
|
||||
b.append(String
|
||||
.format("== MESSAGE END ========================================================================%n"));
|
||||
return b.toString();
|
||||
}
|
||||
}
|
||||
122
bundles/org.openhab.binding.smsmodem/src/3rdparty/java/org/smslib/message/DeliveryReportMessage.java
vendored
Normal file
122
bundles/org.openhab.binding.smsmodem/src/3rdparty/java/org/smslib/message/DeliveryReportMessage.java
vendored
Normal file
@@ -0,0 +1,122 @@
|
||||
package org.smslib.message;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.smslib.pduUtils.gsm3040.SmsStatusReportPdu;
|
||||
|
||||
/**
|
||||
* Extracted from SMSLib
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class DeliveryReportMessage extends InboundMessage {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public enum DeliveryStatus {
|
||||
Unknown("U"),
|
||||
Pending("P"),
|
||||
Failed("F"),
|
||||
Delivered("D"),
|
||||
Expired("X"),
|
||||
Error("E");
|
||||
|
||||
private final String shortString;
|
||||
|
||||
private DeliveryStatus(String shortString) {
|
||||
this.shortString = shortString;
|
||||
}
|
||||
|
||||
public String toShortString() {
|
||||
return this.shortString;
|
||||
}
|
||||
}
|
||||
|
||||
DeliveryStatus deliveryStatus = DeliveryStatus.Unknown;
|
||||
|
||||
@Nullable
|
||||
String originalOperatorMessageId;
|
||||
|
||||
@Nullable
|
||||
Date originalReceivedDate;
|
||||
|
||||
public DeliveryReportMessage() {
|
||||
super(Type.StatusReport, "", 0);
|
||||
}
|
||||
|
||||
public DeliveryReportMessage(SmsStatusReportPdu pdu, String memLocation, int memIndex) {
|
||||
super(Type.StatusReport, memLocation, memIndex);
|
||||
setOriginalOperatorMessageId(String.valueOf(pdu.getMessageReference()));
|
||||
String address = pdu.getAddress();
|
||||
if (address == null) {
|
||||
throw new IllegalArgumentException("Recipient address cannot be null");
|
||||
}
|
||||
this.recipientAddress = new MsIsdn(address);
|
||||
Date timestamp = pdu.getTimestamp();
|
||||
if (timestamp == null) {
|
||||
throw new IllegalArgumentException("Cannot get timestamp for delivery report message");
|
||||
}
|
||||
setSentDate(timestamp);
|
||||
Date dischargeTime = pdu.getDischargeTime();
|
||||
if (dischargeTime == null) {
|
||||
throw new IllegalArgumentException("Cannot get discharge time for delivery report message");
|
||||
}
|
||||
setOriginalReceivedDate(dischargeTime);
|
||||
int i = pdu.getStatus();
|
||||
setPayload(new Payload(""));
|
||||
if ((i & 0x60) == 0) {
|
||||
this.deliveryStatus = DeliveryStatus.Delivered;
|
||||
} else if ((i & 0x20) == 0x20) {
|
||||
this.deliveryStatus = DeliveryStatus.Pending;
|
||||
} else if ((i & 0x40) == 0x40) {
|
||||
this.deliveryStatus = DeliveryStatus.Expired;
|
||||
} else if ((i & 0x60) == 0x60) {
|
||||
this.deliveryStatus = DeliveryStatus.Expired;
|
||||
} else {
|
||||
this.deliveryStatus = DeliveryStatus.Error;
|
||||
}
|
||||
}
|
||||
|
||||
public DeliveryReportMessage(String messageId, String recipientAddress, String memLocation, int memIndex,
|
||||
Date originalSentDate, Date receivedDate) {
|
||||
super(Type.StatusReport, memLocation, memIndex);
|
||||
setOriginalOperatorMessageId(messageId);
|
||||
this.recipientAddress = new MsIsdn(recipientAddress);
|
||||
setSentDate(originalSentDate);
|
||||
setOriginalReceivedDate(receivedDate);
|
||||
this.deliveryStatus = DeliveryStatus.Unknown;
|
||||
}
|
||||
|
||||
public DeliveryStatus getDeliveryStatus() {
|
||||
return this.deliveryStatus;
|
||||
}
|
||||
|
||||
public @Nullable String getOriginalOperatorMessageId() {
|
||||
return this.originalOperatorMessageId;
|
||||
}
|
||||
|
||||
public void setOriginalOperatorMessageId(String originalOperatorMessageId) {
|
||||
this.originalOperatorMessageId = originalOperatorMessageId;
|
||||
}
|
||||
|
||||
public @Nullable Date getOriginalReceivedDate() {
|
||||
Date finalOriginalReceivedDate = originalReceivedDate;
|
||||
return finalOriginalReceivedDate == null ? null : new Date(finalOriginalReceivedDate.getTime());
|
||||
}
|
||||
|
||||
public void setOriginalReceivedDate(Date originalReceivedDate) {
|
||||
this.originalReceivedDate = new Date(originalReceivedDate.getTime());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSignature() {
|
||||
return hashSignature(String.format("%s-%s-%s-%s", getOriginatorAddress(), getOriginalOperatorMessageId(),
|
||||
getOriginalReceivedDate(), getDeliveryStatus()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toShortString() {
|
||||
return String.format("[%s @ %s = %s @ %s]", getId(), getRecipientAddress(), getDeliveryStatus(),
|
||||
getOriginalReceivedDate());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package org.smslib.message;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.smslib.pduUtils.gsm3040.SmsDeliveryPdu;
|
||||
|
||||
/**
|
||||
* Extracted from SMSLib
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class InboundBinaryMessage extends InboundMessage {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public InboundBinaryMessage(SmsDeliveryPdu pdu, String memLocation, int memIndex) {
|
||||
super(pdu, memLocation, memIndex);
|
||||
setPayload(new Payload(pdu.getUserDataAsBytes()));
|
||||
}
|
||||
}
|
||||
164
bundles/org.openhab.binding.smsmodem/src/3rdparty/java/org/smslib/message/InboundMessage.java
vendored
Normal file
164
bundles/org.openhab.binding.smsmodem/src/3rdparty/java/org/smslib/message/InboundMessage.java
vendored
Normal file
@@ -0,0 +1,164 @@
|
||||
package org.smslib.message;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.smslib.pduUtils.gsm3040.PduUtils;
|
||||
import org.smslib.pduUtils.gsm3040.SmsDeliveryPdu;
|
||||
|
||||
/**
|
||||
* Extracted from SMSLib
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class InboundMessage extends AbstractMessage {
|
||||
static Logger logger = LoggerFactory.getLogger(InboundMessage.class);
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
int memIndex;
|
||||
|
||||
String memLocation;
|
||||
|
||||
int mpRefNo;
|
||||
|
||||
int mpMaxNo;
|
||||
|
||||
int mpSeqNo;
|
||||
|
||||
String mpMemIndex = "";
|
||||
|
||||
@Nullable
|
||||
MsIsdn smscNumber;
|
||||
|
||||
boolean endsWithMultiChar;
|
||||
|
||||
public InboundMessage(SmsDeliveryPdu pdu, String memLocation, int memIndex) {
|
||||
super(Type.Inbound, new MsIsdn(pdu.getAddress()), null, null);
|
||||
this.memLocation = memLocation;
|
||||
this.memIndex = memIndex;
|
||||
this.mpRefNo = 0;
|
||||
this.mpMaxNo = 0;
|
||||
this.mpSeqNo = 0;
|
||||
setMpMemIndex(-1);
|
||||
int dcsEncoding = PduUtils.extractDcsEncoding(pdu.getDataCodingScheme());
|
||||
switch (dcsEncoding) {
|
||||
case PduUtils.DCS_ENCODING_7BIT:
|
||||
setEncoding(Encoding.Enc7);
|
||||
break;
|
||||
case PduUtils.DCS_ENCODING_8BIT:
|
||||
setEncoding(Encoding.Enc8);
|
||||
break;
|
||||
case PduUtils.DCS_ENCODING_UCS2:
|
||||
setEncoding(Encoding.EncUcs2);
|
||||
break;
|
||||
default:
|
||||
logger.error("Unknown DCS Encoding: {}", dcsEncoding);
|
||||
}
|
||||
Date timestamp = pdu.getTimestamp();
|
||||
if (timestamp != null) {
|
||||
setSentDate(timestamp);
|
||||
}
|
||||
this.smscNumber = new MsIsdn(pdu.getSmscAddress());
|
||||
setPayload(new Payload(pdu.getDecodedText()));
|
||||
if (pdu.isConcatMessage()) {
|
||||
this.mpRefNo = pdu.getMpRefNo();
|
||||
this.mpMaxNo = pdu.getMpMaxNo();
|
||||
this.mpSeqNo = pdu.getMpSeqNo();
|
||||
}
|
||||
if (pdu.isPortedMessage()) {
|
||||
setSourcePort(pdu.getSrcPort());
|
||||
setDestinationPort(pdu.getDestPort());
|
||||
}
|
||||
if (getEncoding() == Encoding.Enc7) {
|
||||
byte[] udData = pdu.getUDData();
|
||||
if (udData == null) {
|
||||
throw new IllegalArgumentException("Cannot encode udData to construct message");
|
||||
}
|
||||
byte[] temp = PduUtils.encodedSeptetsToUnencodedSeptets(udData);
|
||||
if (temp.length == 0) {
|
||||
this.endsWithMultiChar = false;
|
||||
} else if (temp[temp.length - 1] == 0x1b) {
|
||||
this.endsWithMultiChar = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public InboundMessage(String originator, String text, Date sentDate, String memLocation, int memIndex) {
|
||||
super(Type.Inbound, new MsIsdn(originator), null, new Payload(text));
|
||||
this.memLocation = memLocation;
|
||||
this.memIndex = memIndex;
|
||||
this.sentDate = new Date(sentDate.getTime());
|
||||
}
|
||||
|
||||
public InboundMessage(Type type, String memLocation, int memIndex) {
|
||||
super(type, new MsIsdn(), null, null);
|
||||
this.memIndex = memIndex;
|
||||
this.memLocation = memLocation;
|
||||
this.mpRefNo = 0;
|
||||
this.mpMaxNo = 0;
|
||||
this.mpSeqNo = 0;
|
||||
setMpMemIndex(-1);
|
||||
this.smscNumber = new MsIsdn();
|
||||
}
|
||||
|
||||
public int getMemIndex() {
|
||||
return this.memIndex;
|
||||
}
|
||||
|
||||
public void setMemIndex(int memIndex) {
|
||||
this.memIndex = memIndex;
|
||||
}
|
||||
|
||||
public String getMemLocation() {
|
||||
return this.memLocation;
|
||||
}
|
||||
|
||||
public int getMpMaxNo() {
|
||||
return this.mpMaxNo;
|
||||
}
|
||||
|
||||
public String getMpMemIndex() {
|
||||
return this.mpMemIndex;
|
||||
}
|
||||
|
||||
public void setMpMemIndex(int myMpMemIndex) {
|
||||
if (myMpMemIndex == -1) {
|
||||
this.mpMemIndex = "";
|
||||
} else {
|
||||
this.mpMemIndex += (this.mpMemIndex.length() == 0 ? "" : ",") + myMpMemIndex;
|
||||
}
|
||||
}
|
||||
|
||||
public int getMpRefNo() {
|
||||
return this.mpRefNo;
|
||||
}
|
||||
|
||||
public int getMpSeqNo() {
|
||||
return this.mpSeqNo;
|
||||
}
|
||||
|
||||
public void setMpSeqNo(int myMpSeqNo) {
|
||||
this.mpSeqNo = myMpSeqNo;
|
||||
}
|
||||
|
||||
public boolean getEndsWithMultiChar() {
|
||||
return this.endsWithMultiChar;
|
||||
}
|
||||
|
||||
public void setEndsWithMultiChar(boolean b) {
|
||||
this.endsWithMultiChar = b;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSignature() {
|
||||
return hashSignature(String.format("%s-%s-%s", getOriginatorAddress(), getSentDate(), payload.getText()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toShortString() {
|
||||
return String.format("[%s @ %s]", getId(), getOriginatorAddress());
|
||||
}
|
||||
}
|
||||
89
bundles/org.openhab.binding.smsmodem/src/3rdparty/java/org/smslib/message/MsIsdn.java
vendored
Normal file
89
bundles/org.openhab.binding.smsmodem/src/3rdparty/java/org/smslib/message/MsIsdn.java
vendored
Normal file
@@ -0,0 +1,89 @@
|
||||
package org.smslib.message;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* Extracted from SMSLib
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class MsIsdn {
|
||||
public enum Type {
|
||||
National,
|
||||
International,
|
||||
Text,
|
||||
Void
|
||||
}
|
||||
|
||||
String address;
|
||||
|
||||
Type type = Type.International;
|
||||
|
||||
public MsIsdn() {
|
||||
this("", Type.Void);
|
||||
}
|
||||
|
||||
public MsIsdn(@Nullable String number) {
|
||||
if (number == null) {
|
||||
throw new IllegalArgumentException("Number cannot be null");
|
||||
}
|
||||
if (number.length() > 0 && number.charAt(0) == '+') {
|
||||
this.address = number.substring(1);
|
||||
this.type = Type.International;
|
||||
} else {
|
||||
this.address = number;
|
||||
this.type = typeOf(number);
|
||||
}
|
||||
}
|
||||
|
||||
public MsIsdn(String address, Type type) {
|
||||
this.address = address;
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public MsIsdn(MsIsdn msisdn) {
|
||||
this.type = msisdn.getType();
|
||||
this.address = msisdn.getAddress();
|
||||
}
|
||||
|
||||
public String getAddress() {
|
||||
return this.address;
|
||||
}
|
||||
|
||||
public Type getType() {
|
||||
return this.type;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(@Nullable Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (!(o instanceof MsIsdn)) {
|
||||
return false;
|
||||
}
|
||||
return (this.address.equalsIgnoreCase(((MsIsdn) o).getAddress()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("[%s / %s]", getType(), getAddress());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return this.address.hashCode() + (15 * this.type.hashCode());
|
||||
}
|
||||
|
||||
private static Type typeOf(String number) {
|
||||
if (number.trim().length() == 0) {
|
||||
return Type.Void;
|
||||
}
|
||||
for (int i = 0; i < number.length(); i++) {
|
||||
if (!Character.isDigit(number.charAt(i))) {
|
||||
return Type.Text;
|
||||
}
|
||||
}
|
||||
return Type.International;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package org.smslib.message;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* Extracted from SMSLib
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class OutboundBinaryMessage extends OutboundMessage {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public OutboundBinaryMessage() {
|
||||
}
|
||||
|
||||
public OutboundBinaryMessage(MsIsdn originatorAddress, MsIsdn recipientAddress, byte[] data) {
|
||||
super(originatorAddress, recipientAddress, new Payload(data));
|
||||
setEncoding(Encoding.Enc8);
|
||||
}
|
||||
}
|
||||
193
bundles/org.openhab.binding.smsmodem/src/3rdparty/java/org/smslib/message/OutboundMessage.java
vendored
Normal file
193
bundles/org.openhab.binding.smsmodem/src/3rdparty/java/org/smslib/message/OutboundMessage.java
vendored
Normal file
@@ -0,0 +1,193 @@
|
||||
package org.smslib.message;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.smslib.UnrecoverableSmslibException;
|
||||
import org.smslib.pduUtils.gsm3040.PduFactory;
|
||||
import org.smslib.pduUtils.gsm3040.PduGenerator;
|
||||
import org.smslib.pduUtils.gsm3040.PduUtils;
|
||||
import org.smslib.pduUtils.gsm3040.SmsSubmitPdu;
|
||||
import org.smslib.pduUtils.gsm3040.ie.InformationElementFactory;
|
||||
|
||||
/**
|
||||
* Extracted from SMSLib
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class OutboundMessage extends AbstractMessage {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public enum SentStatus {
|
||||
Sent("S"),
|
||||
Unsent("U"),
|
||||
Queued("Q"),
|
||||
Failed("F");
|
||||
|
||||
private final String shortString;
|
||||
|
||||
private SentStatus(String shortString) {
|
||||
this.shortString = shortString;
|
||||
}
|
||||
|
||||
public String toShortString() {
|
||||
return this.shortString;
|
||||
}
|
||||
}
|
||||
|
||||
public enum FailureCause {
|
||||
None("00"),
|
||||
BadNumber("01"),
|
||||
BadFormat("02"),
|
||||
GatewayFailure("03"),
|
||||
AuthFailure("04"),
|
||||
NoCredit("05"),
|
||||
OverQuota("06"),
|
||||
NoRoute("07"),
|
||||
Unavailable("08"),
|
||||
HttpError("09"),
|
||||
UnknownFailure("10"),
|
||||
Cancelled("11"),
|
||||
NoService("12"),
|
||||
MissingParms("13");
|
||||
|
||||
private final String shortString;
|
||||
|
||||
private FailureCause(String shortString) {
|
||||
this.shortString = shortString;
|
||||
}
|
||||
|
||||
public String toShortString() {
|
||||
return this.shortString;
|
||||
}
|
||||
}
|
||||
|
||||
SentStatus sentStatus = SentStatus.Unsent;
|
||||
|
||||
FailureCause failureCause = FailureCause.None;
|
||||
|
||||
List<String> operatorMessageIds = new ArrayList<>();
|
||||
|
||||
boolean requestDeliveryReport = false;
|
||||
|
||||
public OutboundMessage() {
|
||||
}
|
||||
|
||||
public OutboundMessage(MsIsdn originatorAddress, MsIsdn recipientAddress, Payload payload) {
|
||||
super(Type.Outbound, originatorAddress, recipientAddress, payload);
|
||||
}
|
||||
|
||||
public OutboundMessage(String recipientAddress, String text) {
|
||||
this(new MsIsdn(""), new MsIsdn(recipientAddress), new Payload(text));
|
||||
}
|
||||
|
||||
public SentStatus getSentStatus() {
|
||||
return this.sentStatus;
|
||||
}
|
||||
|
||||
public void setSentStatus(SentStatus sentStatus) {
|
||||
this.sentStatus = sentStatus;
|
||||
}
|
||||
|
||||
public FailureCause getFailureCause() {
|
||||
return this.failureCause;
|
||||
}
|
||||
|
||||
public void setFailureCause(FailureCause failureCode) {
|
||||
this.failureCause = failureCode;
|
||||
}
|
||||
|
||||
public List<String> getOperatorMessageIds() {
|
||||
return this.operatorMessageIds;
|
||||
}
|
||||
|
||||
public boolean getRequestDeliveryReport() {
|
||||
return this.requestDeliveryReport;
|
||||
}
|
||||
|
||||
public void setRequestDeliveryReport(boolean requestDeliveryReport) {
|
||||
this.requestDeliveryReport = requestDeliveryReport;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toShortString() {
|
||||
return String.format("[%s @ %s]", getId(), getRecipientAddress());
|
||||
}
|
||||
|
||||
public List<String> getPdus(MsIsdn smscNumber, int mpRefNo) {
|
||||
PduGenerator pduGenerator = new PduGenerator();
|
||||
SmsSubmitPdu pdu = createPduObject(getRequestDeliveryReport());
|
||||
initPduObject(pdu, smscNumber);
|
||||
return pduGenerator.generatePduList(pdu, mpRefNo);
|
||||
}
|
||||
|
||||
protected SmsSubmitPdu createPduObject(boolean extRequestDeliveryReport) {
|
||||
return (extRequestDeliveryReport ? PduFactory.newSmsSubmitPdu(PduUtils.TP_SRR_REPORT | PduUtils.TP_VPF_INTEGER)
|
||||
: PduFactory.newSmsSubmitPdu());
|
||||
}
|
||||
|
||||
protected void initPduObject(SmsSubmitPdu pdu, MsIsdn smscNumber) {
|
||||
if ((getSourcePort() > -1) && (getDestinationPort() > -1)) {
|
||||
pdu.addInformationElement(
|
||||
InformationElementFactory.generatePortInfo(getDestinationPort(), getSourcePort()));
|
||||
}
|
||||
String smscNumberForLengthCheck = smscNumber.getAddress();
|
||||
pdu.setSmscInfoLength(
|
||||
1 + (smscNumberForLengthCheck.length() / 2) + ((smscNumberForLengthCheck.length() % 2 == 1) ? 1 : 0));
|
||||
pdu.setSmscAddress(smscNumber.getAddress());
|
||||
pdu.setSmscAddressType(PduUtils.getAddressTypeFor(smscNumber));
|
||||
pdu.setMessageReference(0);
|
||||
MsIsdn finalRecipientAddress = recipientAddress;
|
||||
if (finalRecipientAddress == null) {
|
||||
throw new UnrecoverableSmslibException("Recipient adress cannot be null");
|
||||
}
|
||||
pdu.setAddress(finalRecipientAddress);
|
||||
MsIsdn recipientAddressFinal = this.recipientAddress;
|
||||
if (recipientAddressFinal == null) {
|
||||
throw new UnrecoverableSmslibException("Cannot set address type with no recipient");
|
||||
}
|
||||
pdu.setAddressType(PduUtils.getAddressTypeFor(recipientAddressFinal));
|
||||
pdu.setProtocolIdentifier(0);
|
||||
if (!pdu.isBinary()) {
|
||||
int dcs = 0;
|
||||
if (getEncoding() == Encoding.Enc7) {
|
||||
dcs = PduUtils.DCS_ENCODING_7BIT;
|
||||
} else if (getEncoding() == Encoding.Enc8) {
|
||||
dcs = PduUtils.DCS_ENCODING_8BIT;
|
||||
} else if (getEncoding() == Encoding.EncUcs2) {
|
||||
dcs = PduUtils.DCS_ENCODING_UCS2;
|
||||
} else if (getEncoding() == Encoding.EncCustom) {
|
||||
dcs = PduUtils.DCS_ENCODING_7BIT;
|
||||
}
|
||||
if (getDcsClass() == DcsClass.Flash) {
|
||||
dcs = dcs | PduUtils.DCS_MESSAGE_CLASS_FLASH;
|
||||
} else if (getDcsClass() == DcsClass.Me) {
|
||||
dcs = dcs | PduUtils.DCS_MESSAGE_CLASS_ME;
|
||||
} else if (getDcsClass() == DcsClass.Sim) {
|
||||
dcs = dcs | PduUtils.DCS_MESSAGE_CLASS_SIM;
|
||||
} else if (getDcsClass() == DcsClass.Te) {
|
||||
dcs = dcs | PduUtils.DCS_MESSAGE_CLASS_TE;
|
||||
}
|
||||
pdu.setDataCodingScheme(dcs);
|
||||
}
|
||||
pdu.setValidityPeriod(0);
|
||||
if (getEncoding() == Encoding.Enc8) {
|
||||
byte[] bytes = getPayload().getBytes();
|
||||
if (bytes == null) {
|
||||
throw new UnrecoverableSmslibException("Cannot init pdu object, wrong payload");
|
||||
}
|
||||
pdu.setDataBytes(bytes);
|
||||
} else {
|
||||
String text = getPayload().getText();
|
||||
if (text == null) {
|
||||
throw new UnrecoverableSmslibException("Cannot init pdu object, wrong payload");
|
||||
}
|
||||
pdu.setDecodedText(text);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSignature() {
|
||||
return hashSignature(String.format("%s-%s", getRecipientAddress(), getId()));
|
||||
}
|
||||
}
|
||||
54
bundles/org.openhab.binding.smsmodem/src/3rdparty/java/org/smslib/message/Payload.java
vendored
Normal file
54
bundles/org.openhab.binding.smsmodem/src/3rdparty/java/org/smslib/message/Payload.java
vendored
Normal file
@@ -0,0 +1,54 @@
|
||||
package org.smslib.message;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* Extracted from SMSLib
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class Payload {
|
||||
public enum Type {
|
||||
Text,
|
||||
Binary
|
||||
}
|
||||
|
||||
private @Nullable String textData;
|
||||
|
||||
private byte @Nullable [] binaryData;
|
||||
|
||||
private Type type;
|
||||
|
||||
public Payload(String data) {
|
||||
this.type = Type.Text;
|
||||
this.textData = data;
|
||||
}
|
||||
|
||||
public Payload(byte[] data) {
|
||||
this.type = Type.Binary;
|
||||
this.binaryData = data.clone();
|
||||
}
|
||||
|
||||
public Payload(Payload p) {
|
||||
this.type = p.getType();
|
||||
this.textData = (this.type == Type.Text ? p.getText() : "");
|
||||
byte[] bytes = p.getBytes();
|
||||
this.binaryData = (this.type == Type.Binary && bytes != null ? bytes.clone() : null);
|
||||
}
|
||||
|
||||
public Type getType() {
|
||||
return this.type;
|
||||
}
|
||||
|
||||
public @Nullable String getText() {
|
||||
return (this.type == Type.Text ? this.textData : null);
|
||||
}
|
||||
|
||||
public byte @Nullable [] getBytes() {
|
||||
return (this.type == Type.Binary ? this.binaryData : null);
|
||||
}
|
||||
|
||||
public boolean isMultipart() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
520
bundles/org.openhab.binding.smsmodem/src/3rdparty/java/org/smslib/pduUtils/gsm3040/Pdu.java
vendored
Normal file
520
bundles/org.openhab.binding.smsmodem/src/3rdparty/java/org/smslib/pduUtils/gsm3040/Pdu.java
vendored
Normal file
@@ -0,0 +1,520 @@
|
||||
package org.smslib.pduUtils.gsm3040;
|
||||
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Calendar;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.smslib.UnrecoverableSmslibException;
|
||||
import org.smslib.message.MsIsdn;
|
||||
import org.smslib.message.MsIsdn.Type;
|
||||
import org.smslib.pduUtils.gsm3040.ie.ConcatInformationElement;
|
||||
import org.smslib.pduUtils.gsm3040.ie.InformationElement;
|
||||
import org.smslib.pduUtils.gsm3040.ie.PortInformationElement;
|
||||
|
||||
//PduUtils Library - A Java library for generating GSM 3040 Protocol Data Units (PDUs)
|
||||
//
|
||||
//Copyright (C) 2008, Ateneo Java Wireless Competency Center/Blueblade Technologies, Philippines.
|
||||
//PduUtils is distributed under the terms of the Apache License version 2.0
|
||||
//
|
||||
//Licensed under the Apache License, Version 2.0 (the "License");
|
||||
//you may not use this file except in compliance with the License.
|
||||
//You may obtain a copy of the License at
|
||||
//
|
||||
//http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
//Unless required by applicable law or agreed to in writing, software
|
||||
//distributed under the License is distributed on an "AS IS" BASIS,
|
||||
//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//See the License for the specific language governing permissions and
|
||||
//limitations under the License.
|
||||
|
||||
/**
|
||||
* Extracted from SMSLib
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public abstract class Pdu {
|
||||
// PDU class
|
||||
// this class holds directly usable data only
|
||||
// - all lengths are ints
|
||||
// - dates and strings are already decoded
|
||||
// - byte[] for binary data that can interpreted later
|
||||
// an object of this type is created via a PduParser
|
||||
// or created raw, has its field set and supplied to a PduGenerator
|
||||
// ==================================================
|
||||
// SMSC INFO
|
||||
// ==================================================
|
||||
private int smscInfoLength;
|
||||
|
||||
private int smscAddressType;
|
||||
|
||||
@Nullable
|
||||
private String smscAddress;
|
||||
|
||||
public int getSmscInfoLength() {
|
||||
return this.smscInfoLength;
|
||||
}
|
||||
|
||||
public void setSmscInfoLength(int smscInfoLength) {
|
||||
this.smscInfoLength = smscInfoLength;
|
||||
}
|
||||
|
||||
public void setSmscAddressType(int smscAddressType) {
|
||||
this.smscAddressType = PduUtils.createAddressType(smscAddressType);
|
||||
}
|
||||
|
||||
public int getSmscAddressType() {
|
||||
return this.smscAddressType;
|
||||
}
|
||||
|
||||
public void setSmscAddress(@Nullable String smscAddress) {
|
||||
if (smscAddress == null || "".equals(smscAddress)) {
|
||||
this.smscAddress = null;
|
||||
this.smscAddressType = 0;
|
||||
this.smscInfoLength = 0;
|
||||
return;
|
||||
}
|
||||
// strip the + since it is not needed
|
||||
if (smscAddress.startsWith("+")) {
|
||||
this.smscAddress = smscAddress.substring(1);
|
||||
} else {
|
||||
this.smscAddress = smscAddress;
|
||||
}
|
||||
}
|
||||
|
||||
public @Nullable String getSmscAddress() {
|
||||
return this.smscAddress;
|
||||
}
|
||||
|
||||
// ==================================================
|
||||
// FIRST OCTET
|
||||
// ==================================================
|
||||
private int firstOctet = 0;
|
||||
|
||||
public int getFirstOctet() {
|
||||
return this.firstOctet;
|
||||
}
|
||||
|
||||
public void setFirstOctet(int value) {
|
||||
this.firstOctet = value;
|
||||
}
|
||||
|
||||
protected void setFirstOctetField(int fieldName, int fieldValue, int[] allowedValues) {
|
||||
for (int value : allowedValues) {
|
||||
if (value == fieldValue) {
|
||||
// clear the bits for this field
|
||||
this.firstOctet &= fieldName;
|
||||
// copy the new bits on to it
|
||||
this.firstOctet |= fieldValue;
|
||||
return;
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException("Invalid value for fieldName.");
|
||||
}
|
||||
|
||||
protected int getFirstOctetField(int fieldName) {
|
||||
return this.firstOctet & ~fieldName;
|
||||
}
|
||||
|
||||
protected void checkTpMti(int[] allowedTypes) {
|
||||
int tpMti = getTpMti();
|
||||
for (int type : allowedTypes) {
|
||||
if (tpMti == type) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException("Invalid message type : " + getTpMti());
|
||||
}
|
||||
|
||||
public int getTpMti() {
|
||||
return getFirstOctetField(PduUtils.TP_MTI_MASK);
|
||||
}
|
||||
|
||||
public void setTpUdhi(int value) {
|
||||
setFirstOctetField(PduUtils.TP_UDHI_MASK, value,
|
||||
new int[] { PduUtils.TP_UDHI_NO_UDH, PduUtils.TP_UDHI_WITH_UDH });
|
||||
}
|
||||
|
||||
public boolean hasTpUdhi() {
|
||||
return getFirstOctetField(PduUtils.TP_UDHI_MASK) == PduUtils.TP_UDHI_WITH_UDH;
|
||||
}
|
||||
|
||||
// ==================================================
|
||||
// PROTOCOL IDENTIFIER
|
||||
// ==================================================
|
||||
// usually just 0x00 for regular SMS
|
||||
private int protocolIdentifier = 0x00;
|
||||
|
||||
public void setProtocolIdentifier(int protocolIdentifier) {
|
||||
this.protocolIdentifier = protocolIdentifier;
|
||||
}
|
||||
|
||||
public int getProtocolIdentifier() {
|
||||
return this.protocolIdentifier;
|
||||
}
|
||||
|
||||
// ==================================================
|
||||
// DATA CODING SCHEME
|
||||
// ==================================================
|
||||
// usually just 0x00 for default GSM alphabet, phase 2
|
||||
private int dataCodingScheme = 0x00;
|
||||
|
||||
public void setDataCodingScheme(int encoding) {
|
||||
switch (encoding & ~PduUtils.DCS_ENCODING_MASK) {
|
||||
case PduUtils.DCS_ENCODING_7BIT:
|
||||
case PduUtils.DCS_ENCODING_8BIT:
|
||||
case PduUtils.DCS_ENCODING_UCS2:
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Invalid encoding value: " + PduUtils.byteToPdu(encoding));
|
||||
}
|
||||
this.dataCodingScheme = encoding;
|
||||
}
|
||||
|
||||
public int getDataCodingScheme() {
|
||||
return this.dataCodingScheme;
|
||||
}
|
||||
|
||||
// ==================================================
|
||||
// TYPE-OF-ADDRESS
|
||||
// ==================================================
|
||||
private int addressType;
|
||||
|
||||
public int getAddressType() {
|
||||
return this.addressType;
|
||||
}
|
||||
|
||||
public void setAddressType(int addressType) {
|
||||
// insure last bit is always set
|
||||
this.addressType = PduUtils.createAddressType(addressType);
|
||||
}
|
||||
|
||||
// ==================================================
|
||||
// ADDRESS
|
||||
// ==================================================
|
||||
// swapped BCD-format for numbers
|
||||
// 7-bit GSM string for alphanumeric
|
||||
private @Nullable String address;
|
||||
|
||||
public void setAddress(MsIsdn address) {
|
||||
if (address.getType() == Type.Void) {
|
||||
this.address = "";
|
||||
} else {
|
||||
this.address = address.getAddress();
|
||||
}
|
||||
setAddressType(PduUtils.getAddressTypeFor(address));
|
||||
}
|
||||
|
||||
public @Nullable String getAddress() {
|
||||
return this.address;
|
||||
}
|
||||
|
||||
// ==================================================
|
||||
// USER DATA SECTION
|
||||
// ==================================================
|
||||
// this is still needs to be stored since it does not always represent
|
||||
// length in octets, for 7-bit encoding this is length in SEPTETS
|
||||
// NOTE: udData.length may not equal udLength if 7-bit encoding is used
|
||||
private int udLength;
|
||||
|
||||
private byte @Nullable [] udData;
|
||||
|
||||
public int getUDLength() {
|
||||
return this.udLength;
|
||||
}
|
||||
|
||||
public void setUDLength(int udLength) {
|
||||
this.udLength = udLength;
|
||||
}
|
||||
|
||||
public byte @Nullable [] getUDData() {
|
||||
return this.udData;
|
||||
}
|
||||
|
||||
// NOTE: udData DOES NOT include the octet with the length
|
||||
public void setUDData(byte[] udData) {
|
||||
this.udData = udData;
|
||||
}
|
||||
|
||||
// ==================================================
|
||||
// USER DATA HEADER
|
||||
// ==================================================
|
||||
// all methods accessing UDH specific methods require the UDHI to be set
|
||||
// or else an exception will result
|
||||
private static final int UDH_CHECK_MODE_ADD_IF_NONE = 0;
|
||||
|
||||
private static final int UDH_CHECK_MODE_EXCEPTION_IF_NONE = 1;
|
||||
|
||||
private static final int UDH_CHECK_MODE_IGNORE_IF_NONE = 2;
|
||||
|
||||
private void checkForUDHI(int udhCheckMode) {
|
||||
if (!hasTpUdhi()) {
|
||||
switch (udhCheckMode) {
|
||||
case UDH_CHECK_MODE_EXCEPTION_IF_NONE:
|
||||
throw new IllegalStateException("PDU does not have a UDHI in the first octet");
|
||||
case UDH_CHECK_MODE_ADD_IF_NONE:
|
||||
setTpUdhi(PduUtils.TP_UDHI_WITH_UDH);
|
||||
break;
|
||||
case UDH_CHECK_MODE_IGNORE_IF_NONE:
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Invalid UDH check mode");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public int getTotalUDHLength() {
|
||||
int udhLength = getUDHLength();
|
||||
if (udhLength == 0) {
|
||||
return 0;
|
||||
}
|
||||
// also takes into account the field holding the length
|
||||
// it self
|
||||
return udhLength + 1;
|
||||
}
|
||||
|
||||
public int getUDHLength() {
|
||||
// compute based on the IEs
|
||||
int udhLength = 0;
|
||||
for (InformationElement ie : this.ieMap.values()) {
|
||||
// length + 2 to account for the octet holding the IE length and id
|
||||
udhLength = udhLength + ie.getLength() + 2;
|
||||
}
|
||||
return udhLength;
|
||||
}
|
||||
|
||||
public byte @Nullable [] getUDHData() {
|
||||
checkForUDHI(UDH_CHECK_MODE_IGNORE_IF_NONE);
|
||||
int totalUdhLength = getTotalUDHLength();
|
||||
if (totalUdhLength == 0) {
|
||||
return null;
|
||||
}
|
||||
byte[] retVal = new byte[totalUdhLength];
|
||||
byte[] finalUdData = this.udData;
|
||||
if (finalUdData != null) {
|
||||
System.arraycopy(finalUdData, 0, retVal, 0, totalUdhLength);
|
||||
} else {
|
||||
throw new IllegalArgumentException("Cannot get udhd data because udData is null");
|
||||
}
|
||||
return retVal;
|
||||
}
|
||||
|
||||
// UDH portion of UD if UDHI is present
|
||||
// only Concat and Port info will be treated specially
|
||||
// other IEs will have to get extracted from the map manually and parsed
|
||||
private HashMap<Integer, InformationElement> ieMap = new HashMap<>();
|
||||
|
||||
private ArrayList<InformationElement> ieList = new ArrayList<>();
|
||||
|
||||
public void addInformationElement(InformationElement ie) {
|
||||
checkForUDHI(UDH_CHECK_MODE_ADD_IF_NONE);
|
||||
this.ieMap.put(ie.getIdentifier(), ie);
|
||||
this.ieList.add(ie);
|
||||
}
|
||||
|
||||
public @Nullable InformationElement getInformationElement(int iei) {
|
||||
checkForUDHI(UDH_CHECK_MODE_IGNORE_IF_NONE);
|
||||
return this.ieMap.get(iei);
|
||||
}
|
||||
|
||||
// this is only used in the parser generator
|
||||
public Iterator<InformationElement> getInformationElements() {
|
||||
checkForUDHI(UDH_CHECK_MODE_IGNORE_IF_NONE);
|
||||
return this.ieList.iterator();
|
||||
}
|
||||
|
||||
// ==================================================
|
||||
// CONCAT INFO
|
||||
// ==================================================
|
||||
public boolean isConcatMessage() {
|
||||
// check if iei 0x00 or 0x08 is present
|
||||
return (getConcatInfo() != null);
|
||||
}
|
||||
|
||||
public @Nullable ConcatInformationElement getConcatInfo() {
|
||||
checkForUDHI(UDH_CHECK_MODE_IGNORE_IF_NONE);
|
||||
ConcatInformationElement concat = (ConcatInformationElement) getInformationElement(
|
||||
ConcatInformationElement.CONCAT_8BIT_REF);
|
||||
if (concat == null) {
|
||||
concat = (ConcatInformationElement) getInformationElement(ConcatInformationElement.CONCAT_16BIT_REF);
|
||||
}
|
||||
return concat;
|
||||
}
|
||||
|
||||
public int getMpRefNo() {
|
||||
ConcatInformationElement concat = getConcatInfo();
|
||||
if (concat != null) {
|
||||
return concat.getMpRefNo();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
public int getMpMaxNo() {
|
||||
ConcatInformationElement concat = getConcatInfo();
|
||||
if (concat != null) {
|
||||
return concat.getMpMaxNo();
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
public int getMpSeqNo() {
|
||||
ConcatInformationElement concat = getConcatInfo();
|
||||
if (concat != null) {
|
||||
return concat.getMpSeqNo();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ==================================================
|
||||
// PORT DATA
|
||||
// ==================================================
|
||||
public boolean isPortedMessage() {
|
||||
// check if iei 0x05 is present
|
||||
return (getPortInfo() != null);
|
||||
}
|
||||
|
||||
private @Nullable PortInformationElement getPortInfo() {
|
||||
checkForUDHI(UDH_CHECK_MODE_IGNORE_IF_NONE);
|
||||
return (PortInformationElement) getInformationElement(PortInformationElement.PORT_16BIT);
|
||||
}
|
||||
|
||||
public int getDestPort() {
|
||||
PortInformationElement portIe = getPortInfo();
|
||||
if (portIe == null) {
|
||||
return -1;
|
||||
}
|
||||
return portIe.getDestPort();
|
||||
}
|
||||
|
||||
public int getSrcPort() {
|
||||
PortInformationElement portIe = getPortInfo();
|
||||
if (portIe == null) {
|
||||
return -1;
|
||||
}
|
||||
return portIe.getSrcPort();
|
||||
}
|
||||
|
||||
// ==================================================
|
||||
// NON-UDH DATA
|
||||
// ==================================================
|
||||
// UD minus the UDH portion, same as userData if
|
||||
// no UDH
|
||||
// these fields store data for the generation step
|
||||
private @Nullable String decodedText;
|
||||
|
||||
private byte @Nullable [] dataBytes;
|
||||
|
||||
public void setDataBytes(byte[] dataBytes) {
|
||||
this.dataBytes = dataBytes;
|
||||
this.decodedText = null;
|
||||
// clear the encoding bits for this field 8-bit/data
|
||||
// this.dataCodingScheme &= PduUtils.DCS_ENCODING_MASK;
|
||||
// this.dataCodingScheme |= PduUtils.DCS_ENCODING_8BIT;
|
||||
// this.dataCodingScheme |= PduUtils.DCS_CODING_GROUP_DATA;
|
||||
}
|
||||
|
||||
public byte @Nullable [] getDataBytes() {
|
||||
return this.dataBytes;
|
||||
}
|
||||
|
||||
public boolean isBinary() {
|
||||
// use the DCS coding group or 8bit encoding
|
||||
// Changed following line according to http://code.google.com/p/smslib/issues/detail?id=187
|
||||
// return ((this.dataCodingScheme & PduUtils.DCS_CODING_GROUP_DATA) == PduUtils.DCS_CODING_GROUP_DATA ||
|
||||
// (this.dataCodingScheme & PduUtils.DCS_ENCODING_8BIT) == PduUtils.DCS_ENCODING_8BIT);
|
||||
if ((this.dataCodingScheme & PduUtils.DCS_CODING_GROUP_DATA) == PduUtils.DCS_CODING_GROUP_DATA
|
||||
|| (this.dataCodingScheme & PduUtils.DCS_ENCODING_8BIT) == PduUtils.DCS_ENCODING_8BIT) {
|
||||
if ((this.dataCodingScheme & PduUtils.DCS_ENCODING_8BIT) == PduUtils.DCS_ENCODING_8BIT) {
|
||||
return (true);
|
||||
}
|
||||
}
|
||||
return (false);
|
||||
}
|
||||
|
||||
public void setDecodedText(String decodedText) {
|
||||
this.decodedText = decodedText;
|
||||
this.dataBytes = null;
|
||||
// check if existing DCS indicates a flash message
|
||||
boolean flash = false;
|
||||
if (PduUtils.extractDcsFlash(this.dataCodingScheme) == PduUtils.DCS_MESSAGE_CLASS_FLASH) {
|
||||
flash = true;
|
||||
}
|
||||
// clears the coding group to be text again in case it was originally binary
|
||||
this.dataCodingScheme &= PduUtils.DCS_CODING_GROUP_MASK;
|
||||
// set the flash bit back since the above would clear it
|
||||
if (flash) {
|
||||
this.dataCodingScheme = this.dataCodingScheme | PduUtils.DCS_MESSAGE_CLASS_FLASH;
|
||||
}
|
||||
}
|
||||
|
||||
public String getDecodedText() {
|
||||
// this should be try-catched in case the ud data is
|
||||
// actually binary and might cause a decoding exception
|
||||
String decodedTextFinal = this.decodedText;
|
||||
if (decodedTextFinal != null) {
|
||||
return decodedTextFinal;
|
||||
}
|
||||
if (this.udData == null) {
|
||||
throw new UnrecoverableSmslibException("No udData to decode");
|
||||
}
|
||||
return decodeNonUDHDataAsString();
|
||||
}
|
||||
|
||||
public byte[] getUserDataAsBytes() {
|
||||
byte[] udDataFinal = this.udData;
|
||||
if (udDataFinal == null) {
|
||||
throw new UnrecoverableSmslibException("udData cannot be null");
|
||||
}
|
||||
int remainingLength = udDataFinal.length - (getTotalUDHLength());
|
||||
byte[] retVal = new byte[remainingLength];
|
||||
byte[] finalUdData = udData;
|
||||
if (finalUdData != null) {
|
||||
System.arraycopy(finalUdData, getTotalUDHLength(), retVal, 0, remainingLength);
|
||||
} else {
|
||||
throw new UnrecoverableSmslibException("Cannot get user data because udData is null");
|
||||
}
|
||||
return retVal;
|
||||
}
|
||||
|
||||
private String decodeNonUDHDataAsString() {
|
||||
// convert PDU to text depending on the encoding
|
||||
// must also take into account the octet holding the length
|
||||
byte[] udhDataFinal = getUDHData();
|
||||
byte[] udDataFinal = this.udData;
|
||||
if (udDataFinal == null) {
|
||||
throw new UnrecoverableSmslibException("Cannot decode with udData null");
|
||||
}
|
||||
switch (PduUtils.extractDcsEncoding(getDataCodingScheme())) {
|
||||
case PduUtils.DCS_ENCODING_7BIT:
|
||||
// unpack all septets to octets with MSB holes
|
||||
byte[] septets = PduUtils.encodedSeptetsToUnencodedSeptets(udDataFinal);
|
||||
int septetUDHLength = 0;
|
||||
if (udhDataFinal != null) {
|
||||
// work out how much of the UD is UDH
|
||||
septetUDHLength = udhDataFinal.length * 8 / 7;
|
||||
if (udhDataFinal.length * 8 % 7 > 0) {
|
||||
septetUDHLength++;
|
||||
}
|
||||
}
|
||||
byte[] septetsNoUDH = new byte[this.udLength - septetUDHLength];
|
||||
// src, srcStart, dest, destStart, length
|
||||
System.arraycopy(septets, septetUDHLength, septetsNoUDH, 0, septetsNoUDH.length);
|
||||
return PduUtils.unencodedSeptetsToString(septetsNoUDH);
|
||||
case PduUtils.DCS_ENCODING_8BIT:
|
||||
return PduUtils.decode8bitEncoding(udhDataFinal, udDataFinal);
|
||||
case PduUtils.DCS_ENCODING_UCS2:
|
||||
return PduUtils.decodeUcs2Encoding(udhDataFinal, udDataFinal);
|
||||
}
|
||||
throw new IllegalArgumentException("Invalid dataCodingScheme: " + getDataCodingScheme());
|
||||
}
|
||||
|
||||
protected String formatTimestamp(Calendar timestamp) {
|
||||
SimpleDateFormat sdf = new SimpleDateFormat();
|
||||
sdf.applyPattern("EEE dd-MMM-yyyy HH:mm:ss z");
|
||||
sdf.setTimeZone(timestamp.getTimeZone());
|
||||
return sdf.format(timestamp.getTime());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
package org.smslib.pduUtils.gsm3040;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
//PduUtils Library - A Java library for generating GSM 3040 Protocol Data Units (PDUs)
|
||||
//
|
||||
//Copyright (C) 2008, Ateneo Java Wireless Competency Center/Blueblade Technologies, Philippines.
|
||||
//PduUtils is distributed under the terms of the Apache License version 2.0
|
||||
//
|
||||
//Licensed under the Apache License, Version 2.0 (the "License");
|
||||
//you may not use this file except in compliance with the License.
|
||||
//You may obtain a copy of the License at
|
||||
//
|
||||
//http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
//Unless required by applicable law or agreed to in writing, software
|
||||
//distributed under the License is distributed on an "AS IS" BASIS,
|
||||
//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//See the License for the specific language governing permissions and
|
||||
//limitations under the License.
|
||||
|
||||
/**
|
||||
* Extracted from SMSLib
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class PduFactory {
|
||||
public static SmsSubmitPdu newSmsSubmitPdu() {
|
||||
// apply defaults
|
||||
int additionalFields = PduUtils.TP_RD_ACCEPT_DUPLICATES | PduUtils.TP_VPF_INTEGER;
|
||||
return newSmsSubmitPdu(additionalFields);
|
||||
}
|
||||
|
||||
public static SmsSubmitPdu newSmsSubmitPdu(int additionalFields) {
|
||||
// remove any TP_MTI values
|
||||
int firstOctet = PduUtils.TP_MTI_SMS_SUBMIT | additionalFields;
|
||||
return (SmsSubmitPdu) createPdu(firstOctet);
|
||||
}
|
||||
|
||||
private static int getFirstOctetField(int firstOctet, int fieldName) {
|
||||
return firstOctet & ~fieldName;
|
||||
}
|
||||
|
||||
// used to determine what Pdu to use based on the first octet
|
||||
// this is the only way to instantiate a Pdu object
|
||||
public static Pdu createPdu(int firstOctet) {
|
||||
Pdu pdu = null;
|
||||
int messageType = getFirstOctetField(firstOctet, PduUtils.TP_MTI_MASK);
|
||||
switch (messageType) {
|
||||
case PduUtils.TP_MTI_SMS_DELIVER:
|
||||
pdu = new SmsDeliveryPdu();
|
||||
break;
|
||||
case PduUtils.TP_MTI_SMS_STATUS_REPORT:
|
||||
pdu = new SmsStatusReportPdu();
|
||||
break;
|
||||
case PduUtils.TP_MTI_SMS_SUBMIT:
|
||||
pdu = new SmsSubmitPdu();
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Invalid TP-MTI value: " + messageType);
|
||||
}
|
||||
// once set, you can't change it
|
||||
// this method is only available in this package
|
||||
pdu.setFirstOctet(firstOctet);
|
||||
return pdu;
|
||||
}
|
||||
}
|
||||
545
bundles/org.openhab.binding.smsmodem/src/3rdparty/java/org/smslib/pduUtils/gsm3040/PduGenerator.java
vendored
Normal file
545
bundles/org.openhab.binding.smsmodem/src/3rdparty/java/org/smslib/pduUtils/gsm3040/PduGenerator.java
vendored
Normal file
@@ -0,0 +1,545 @@
|
||||
package org.smslib.pduUtils.gsm3040;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.TimeZone;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.smslib.UnrecoverableSmslibException;
|
||||
import org.smslib.pduUtils.gsm3040.ie.ConcatInformationElement;
|
||||
import org.smslib.pduUtils.gsm3040.ie.InformationElement;
|
||||
import org.smslib.pduUtils.gsm3040.ie.InformationElementFactory;
|
||||
|
||||
//PduUtils Library - A Java library for generating GSM 3040 Protocol Data Units (PDUs)
|
||||
//
|
||||
//Copyright (C) 2008, Ateneo Java Wireless Competency Center/Blueblade Technologies, Philippines.
|
||||
//PduUtils is distributed under the terms of the Apache License version 2.0
|
||||
//
|
||||
//Licensed under the Apache License, Version 2.0 (the "License");
|
||||
//you may not use this file except in compliance with the License.
|
||||
//You may obtain a copy of the License at
|
||||
//
|
||||
//http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
//Unless required by applicable law or agreed to in writing, software
|
||||
//distributed under the License is distributed on an "AS IS" BASIS,
|
||||
//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//See the License for the specific language governing permissions and
|
||||
//limitations under the License.
|
||||
|
||||
/**
|
||||
* Extracted from SMSLib
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class PduGenerator {
|
||||
private ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
|
||||
private int firstOctetPosition = -1;
|
||||
|
||||
private boolean updateFirstOctet = false;
|
||||
|
||||
protected void writeSmscInfo(Pdu pdu) {
|
||||
String smscAddress = pdu.getSmscAddress();
|
||||
if (smscAddress != null) {
|
||||
writeBCDAddress(smscAddress, pdu.getSmscAddressType(), pdu.getSmscInfoLength());
|
||||
} else {
|
||||
writeByte(0);
|
||||
}
|
||||
}
|
||||
|
||||
protected void writeFirstOctet(Pdu pdu) {
|
||||
// store the position in case it will need to be updated later
|
||||
this.firstOctetPosition = pdu.getSmscInfoLength() + 1;
|
||||
writeByte(pdu.getFirstOctet());
|
||||
}
|
||||
|
||||
// validity period conversion from hours to the proper integer
|
||||
protected void writeValidityPeriodInteger(int validityPeriod) {
|
||||
if (validityPeriod == -1) {
|
||||
this.baos.write(0xFF);
|
||||
} else {
|
||||
int validityInt;
|
||||
if (validityPeriod <= 12) {
|
||||
validityInt = (validityPeriod * 12) - 1;
|
||||
} else if (validityPeriod <= 24) {
|
||||
validityInt = (((validityPeriod - 12) * 2) + 143);
|
||||
} else if (validityPeriod <= 720) {
|
||||
validityInt = (validityPeriod / 24) + 166;
|
||||
} else {
|
||||
validityInt = (validityPeriod / 168) + 192;
|
||||
}
|
||||
this.baos.write(validityInt);
|
||||
}
|
||||
}
|
||||
|
||||
protected void writeTimeStampStringForDate(Date timestamp) {
|
||||
Calendar cal = Calendar.getInstance();
|
||||
cal.setTime(timestamp);
|
||||
int year = cal.get(Calendar.YEAR) - 2000;
|
||||
int month = cal.get(Calendar.MONTH) + 1;
|
||||
int dayOfMonth = cal.get(Calendar.DAY_OF_MONTH);
|
||||
int hourOfDay = cal.get(Calendar.HOUR_OF_DAY);
|
||||
int minute = cal.get(Calendar.MINUTE);
|
||||
int sec = cal.get(Calendar.SECOND);
|
||||
TimeZone tz = cal.getTimeZone();
|
||||
int offset = tz.getOffset(timestamp.getTime());
|
||||
int minOffset = offset / 60000;
|
||||
int tzValue = minOffset / 15;
|
||||
// for negative offsets, add 128 to the absolute value
|
||||
if (tzValue < 0) {
|
||||
tzValue = 128 - tzValue;
|
||||
}
|
||||
// note: the nibbles are written as BCD style
|
||||
this.baos.write(PduUtils.createSwappedBCD(year));
|
||||
this.baos.write(PduUtils.createSwappedBCD(month));
|
||||
this.baos.write(PduUtils.createSwappedBCD(dayOfMonth));
|
||||
this.baos.write(PduUtils.createSwappedBCD(hourOfDay));
|
||||
this.baos.write(PduUtils.createSwappedBCD(minute));
|
||||
this.baos.write(PduUtils.createSwappedBCD(sec));
|
||||
this.baos.write(PduUtils.createSwappedBCD(tzValue));
|
||||
}
|
||||
|
||||
protected void writeAddress(String address, int addressType, int addressLength) throws IOException {
|
||||
switch (PduUtils.extractAddressType(addressType)) {
|
||||
case PduUtils.ADDRESS_TYPE_ALPHANUMERIC:
|
||||
byte[] textSeptets = PduUtils.stringToUnencodedSeptets(address);
|
||||
byte[] alphaNumBytes = PduUtils.encode7bitUserData(null, textSeptets);
|
||||
// ADDRESS LENGTH - should be the semi-octet count
|
||||
// - this type is not used for SMSCInfo
|
||||
this.baos.write(alphaNumBytes.length * 2);
|
||||
// ADDRESS TYPE
|
||||
this.baos.write(addressType);
|
||||
// ADDRESS TEXT
|
||||
this.baos.write(alphaNumBytes);
|
||||
break;
|
||||
default:
|
||||
// BCD-style
|
||||
writeBCDAddress(address, addressType, addressLength);
|
||||
}
|
||||
}
|
||||
|
||||
protected void writeBCDAddress(String address, int addressType, int addressLength) {
|
||||
// BCD-style
|
||||
// ADDRESS LENGTH - either an octet count or semi-octet count
|
||||
this.baos.write(addressLength);
|
||||
// ADDRESS TYPE
|
||||
this.baos.write(addressType);
|
||||
// ADDRESS NUMBERS
|
||||
// if address.length is not even, pad the string an with F at the end
|
||||
String myaddress = address;
|
||||
if (myaddress.length() % 2 == 1) {
|
||||
myaddress = myaddress + "F";
|
||||
}
|
||||
int digit = 0;
|
||||
for (int i = 0; i < myaddress.length(); i++) {
|
||||
char c = myaddress.charAt(i);
|
||||
if (i % 2 == 1) {
|
||||
digit |= ((Integer.parseInt(Character.toString(c), 16)) << 4);
|
||||
this.baos.write(digit);
|
||||
// clear it
|
||||
digit = 0;
|
||||
} else {
|
||||
digit |= (Integer.parseInt(Character.toString(c), 16) & 0x0F);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void writeUDData(Pdu pdu, int mpRefNo, int partNo) {
|
||||
int dcs = pdu.getDataCodingScheme();
|
||||
try {
|
||||
switch (PduUtils.extractDcsEncoding(dcs)) {
|
||||
case PduUtils.DCS_ENCODING_7BIT:
|
||||
writeUDData7bit(pdu, mpRefNo, partNo);
|
||||
break;
|
||||
case PduUtils.DCS_ENCODING_8BIT:
|
||||
writeUDData8bit(pdu, mpRefNo, partNo);
|
||||
break;
|
||||
case PduUtils.DCS_ENCODING_UCS2:
|
||||
writeUDDataUCS2(pdu, mpRefNo, partNo);
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Invalid DCS encoding: " + PduUtils.extractDcsEncoding(dcs));
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new UnrecoverableSmslibException("Cannot write uddata", e);
|
||||
}
|
||||
}
|
||||
|
||||
protected void writeUDH(Pdu pdu) throws IOException {
|
||||
// stream directly into the internal baos
|
||||
writeUDH(pdu, this.baos);
|
||||
}
|
||||
|
||||
protected void writeUDH(Pdu pdu, ByteArrayOutputStream udhBaos) throws IOException {
|
||||
// need to insure that proper concat info is inserted
|
||||
// before writing if needed
|
||||
// i.e. the reference number, maxseq and seq have to be set from
|
||||
// outside (OutboundMessage)
|
||||
udhBaos.write(pdu.getUDHLength());
|
||||
for (Iterator<InformationElement> ieIterator = pdu.getInformationElements(); ieIterator.hasNext();) {
|
||||
InformationElement ie = ieIterator.next();
|
||||
udhBaos.write(ie.getIdentifier());
|
||||
udhBaos.write(ie.getLength());
|
||||
udhBaos.write(ie.getData());
|
||||
}
|
||||
}
|
||||
|
||||
protected int computeOffset(Pdu pdu, int maxMessageLength, int partNo) {
|
||||
// computes offset to which part of the string is to be encoded into the PDU
|
||||
// also sets the MpMaxNo field of the concatInfo if message is multi-part
|
||||
int offset;
|
||||
int maxParts = 1;
|
||||
if (!pdu.isBinary()) {
|
||||
maxParts = pdu.getDecodedText().length() / maxMessageLength + 1;
|
||||
} else {
|
||||
byte[] pduDataBytes = pdu.getDataBytes();
|
||||
if (pduDataBytes == null) {
|
||||
throw new UnrecoverableSmslibException("Cannot compute offset for empty data bytes");
|
||||
}
|
||||
maxParts = pduDataBytes.length / maxMessageLength + 1;
|
||||
}
|
||||
if (pdu.hasTpUdhi()) {
|
||||
ConcatInformationElement concatInfoFinal = pdu.getConcatInfo();
|
||||
if (concatInfoFinal != null) {
|
||||
if (partNo > 0) {
|
||||
concatInfoFinal.setMpMaxNo(maxParts);
|
||||
}
|
||||
}
|
||||
}
|
||||
if ((maxParts > 1) && (partNo > 0)) {
|
||||
// - if partNo > maxParts
|
||||
// - error
|
||||
if (partNo > maxParts) {
|
||||
throw new IllegalArgumentException("Invalid partNo: " + partNo + ", maxParts=" + maxParts);
|
||||
}
|
||||
offset = ((partNo - 1) * maxMessageLength);
|
||||
} else {
|
||||
// just get from the start
|
||||
offset = 0;
|
||||
}
|
||||
return offset;
|
||||
}
|
||||
|
||||
protected void checkForConcat(Pdu pdu, int lengthOfText, int maxLength, int maxLengthWithUdh, int mpRefNo,
|
||||
int partNo) {
|
||||
if ((lengthOfText <= maxLengthWithUdh) || ((lengthOfText > maxLengthWithUdh) && (lengthOfText <= maxLength))) {
|
||||
} else {
|
||||
// need concat
|
||||
ConcatInformationElement concatInfoFinal = pdu.getConcatInfo();
|
||||
if (concatInfoFinal != null) {
|
||||
// if concatInfo is already present then just replace the values with the supplied
|
||||
concatInfoFinal.setMpRefNo(mpRefNo);
|
||||
concatInfoFinal.setMpSeqNo(partNo);
|
||||
} else {
|
||||
// add concat info with the specified mpRefNo, bogus maxSeqNo, and partNo
|
||||
// bogus maxSeqNo will be replaced once it is known in the later steps
|
||||
// this just needs to be added since its presence is needed to compute
|
||||
// the UDH length
|
||||
ConcatInformationElement concatInfo = InformationElementFactory.generateConcatInfo(mpRefNo, partNo);
|
||||
pdu.addInformationElement(concatInfo);
|
||||
this.updateFirstOctet = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected int computePotentialUdhLength(Pdu pdu) {
|
||||
int currentUdhLength = pdu.getTotalUDHLength();
|
||||
if (currentUdhLength == 0) {
|
||||
// add 1 for the UDH Length field
|
||||
return ConcatInformationElement.getDefaultConcatLength() + 1;
|
||||
}
|
||||
// this already has the UDH Length field, no need to add 1
|
||||
return currentUdhLength + ConcatInformationElement.getDefaultConcatLength();
|
||||
}
|
||||
|
||||
protected void writeUDData7bit(Pdu pdu, int mpRefNo, int partNo) throws IOException {
|
||||
String decodedText = pdu.getDecodedText();
|
||||
// partNo states what part of the unencoded text will be used
|
||||
// - max length is based on the size of the UDH
|
||||
// for 7bit => maxLength = 160 - total UDH septets
|
||||
// check if this message needs a concat
|
||||
byte[] textSeptetsForDecodedText = PduUtils.stringToUnencodedSeptets(decodedText);
|
||||
int potentialUdhLength = PduUtils.getNumSeptetsForOctets(computePotentialUdhLength(pdu));
|
||||
checkForConcat(pdu, textSeptetsForDecodedText.length,
|
||||
160 - PduUtils.getNumSeptetsForOctets(pdu.getTotalUDHLength()), // CHANGED
|
||||
160 - potentialUdhLength, mpRefNo, partNo);
|
||||
// given the IEs in the pdu derive the max message body length
|
||||
// this length will include the potential concat added in the previous step
|
||||
int totalUDHLength = pdu.getTotalUDHLength();
|
||||
int maxMessageLength = 160 - PduUtils.getNumSeptetsForOctets(totalUDHLength);
|
||||
// get septets for part
|
||||
byte[] textSeptets = getUnencodedSeptetsForPart(pdu, maxMessageLength, partNo);
|
||||
// udlength is the sum of udh septet length and the text septet length
|
||||
int udLength = PduUtils.getNumSeptetsForOctets(totalUDHLength) + textSeptets.length;
|
||||
this.baos.write(udLength);
|
||||
// generate UDH byte[]
|
||||
// UDHL (sum of all IE lengths)
|
||||
// IE list
|
||||
byte[] udhBytes = null;
|
||||
if (pdu.hasTpUdhi()) {
|
||||
ByteArrayOutputStream udhBaos = new ByteArrayOutputStream();
|
||||
writeUDH(pdu, udhBaos);
|
||||
// buffer the udh since this needs to be 7-bit encoded with the text
|
||||
udhBytes = udhBaos.toByteArray();
|
||||
}
|
||||
// encode both as one unit
|
||||
byte[] udBytes = PduUtils.encode7bitUserData(udhBytes, textSeptets);
|
||||
// write combined encoded array
|
||||
this.baos.write(udBytes);
|
||||
}
|
||||
|
||||
private byte[] getUnencodedSeptetsForPart(Pdu pdu, int maxMessageLength, int partNo) {
|
||||
// computes offset to which part of the string is to be encoded into the PDU
|
||||
// also sets the MpMaxNo field of the concatInfo if message is multi-part
|
||||
int offset;
|
||||
int maxParts = 1;
|
||||
// must use the unencoded septets not the actual string since
|
||||
// it is possible that some special characters in string are multi-septet
|
||||
byte[] unencodedSeptets = PduUtils.stringToUnencodedSeptets(pdu.getDecodedText());
|
||||
maxParts = (unencodedSeptets.length / maxMessageLength) + 1;
|
||||
if (pdu.hasTpUdhi()) {
|
||||
ConcatInformationElement concatInfoFinal = pdu.getConcatInfo();
|
||||
if (concatInfoFinal != null) {
|
||||
if (partNo > 0) {
|
||||
concatInfoFinal.setMpMaxNo(maxParts);
|
||||
}
|
||||
}
|
||||
}
|
||||
if ((maxParts > 1) && (partNo > 0)) {
|
||||
// - if partNo > maxParts
|
||||
// - error
|
||||
if (partNo > maxParts) {
|
||||
throw new UnrecoverableSmslibException("Invalid partNo: " + partNo + ", maxParts=" + maxParts);
|
||||
}
|
||||
offset = ((partNo - 1) * maxMessageLength);
|
||||
} else {
|
||||
// just get from the start
|
||||
offset = 0;
|
||||
}
|
||||
// copy the portion of the full unencoded septet array for this part
|
||||
byte[] septetsForPart = new byte[Math.min(maxMessageLength, unencodedSeptets.length - offset)];
|
||||
System.arraycopy(unencodedSeptets, offset, septetsForPart, 0, septetsForPart.length);
|
||||
return septetsForPart;
|
||||
}
|
||||
|
||||
protected void writeUDData8bit(Pdu pdu, int mpRefNo, int partNo) throws IOException {
|
||||
// NOTE: binary messages are also handled here
|
||||
byte[] data;
|
||||
if (pdu.isBinary()) {
|
||||
// use the supplied bytes
|
||||
byte[] dataBytesFinal = pdu.getDataBytes();
|
||||
if (dataBytesFinal == null) {
|
||||
throw new UnrecoverableSmslibException("Data cannot be null");
|
||||
}
|
||||
data = dataBytesFinal;
|
||||
} else {
|
||||
// encode the text
|
||||
data = PduUtils.encode8bitUserData(pdu.getDecodedText());
|
||||
}
|
||||
// partNo states what part of the unencoded text will be used
|
||||
// - max length is based on the size of the UDH
|
||||
// for 8bit => maxLength = 140 - the total UDH bytes
|
||||
// check if this message needs a concat
|
||||
int potentialUdhLength = computePotentialUdhLength(pdu);
|
||||
checkForConcat(pdu, data.length, 140 - pdu.getTotalUDHLength(), // CHANGED
|
||||
140 - potentialUdhLength, mpRefNo, partNo);
|
||||
// given the IEs in the pdu derive the max message body length
|
||||
// this length will include the potential concat added in the previous step
|
||||
int totalUDHLength = pdu.getTotalUDHLength();
|
||||
int maxMessageLength = 140 - totalUDHLength;
|
||||
// compute which portion of the message will be part of the message
|
||||
int offset = computeOffset(pdu, maxMessageLength, partNo);
|
||||
byte[] dataToWrite = new byte[Math.min(maxMessageLength, data.length - offset)];
|
||||
System.arraycopy(data, offset, dataToWrite, 0, dataToWrite.length);
|
||||
// generate udlength
|
||||
// based on partNo
|
||||
// udLength is an octet count for 8bit/ucs2
|
||||
int udLength = totalUDHLength + dataToWrite.length;
|
||||
// write udlength
|
||||
this.baos.write(udLength);
|
||||
// write UDH to the stream directly
|
||||
if (pdu.hasTpUdhi()) {
|
||||
writeUDH(pdu, this.baos);
|
||||
}
|
||||
// write data
|
||||
this.baos.write(dataToWrite);
|
||||
}
|
||||
|
||||
protected void writeUDDataUCS2(Pdu pdu, int mpRefNo, int partNo) throws IOException {
|
||||
String decodedText = pdu.getDecodedText();
|
||||
// partNo states what part of the unencoded text will be used
|
||||
// - max length is based on the size of the UDH
|
||||
// for ucs2 => maxLength = (140 - the total UDH bytes)/2
|
||||
// check if this message needs a concat
|
||||
int potentialUdhLength = computePotentialUdhLength(pdu);
|
||||
checkForConcat(pdu, decodedText.length(), (140 - pdu.getTotalUDHLength()) / 2, // CHANGED
|
||||
(140 - potentialUdhLength) / 2, mpRefNo, partNo);
|
||||
// given the IEs in the pdu derive the max message body length
|
||||
// this length will include the potential concat added in the previous step
|
||||
int totalUDHLength = pdu.getTotalUDHLength();
|
||||
int maxMessageLength = (140 - totalUDHLength) / 2;
|
||||
// compute which portion of the message will be part of the message
|
||||
int offset = computeOffset(pdu, maxMessageLength, partNo);
|
||||
String textToEncode = decodedText.substring(offset, Math.min(offset + maxMessageLength, decodedText.length()));
|
||||
// generate udlength
|
||||
// based on partNo
|
||||
// udLength is an octet count for 8bit/ucs2
|
||||
int udLength = totalUDHLength + (textToEncode.length() * 2);
|
||||
// write udlength
|
||||
this.baos.write(udLength);
|
||||
// write UDH to the stream directly
|
||||
if (pdu.hasTpUdhi()) {
|
||||
writeUDH(pdu, this.baos);
|
||||
}
|
||||
// write encoded text
|
||||
this.baos.write(PduUtils.encodeUcs2UserData(textToEncode));
|
||||
}
|
||||
|
||||
protected void writeByte(int i) {
|
||||
this.baos.write(i);
|
||||
}
|
||||
|
||||
protected void writeBytes(byte[] b) throws IOException {
|
||||
this.baos.write(b);
|
||||
}
|
||||
|
||||
public List<String> generatePduList(Pdu pdu, int mpRefNo) {
|
||||
// generate all required PDUs for a given message
|
||||
// mpRefNo comes from the ModemGateway
|
||||
ArrayList<String> pduList = new ArrayList<>();
|
||||
for (int i = 1; i <= pdu.getMpMaxNo(); i++) {
|
||||
String pduString = generatePduString(pdu, mpRefNo, i);
|
||||
pduList.add(pduString);
|
||||
}
|
||||
return pduList;
|
||||
}
|
||||
|
||||
// NOTE: partNo indicates which part of a multipart message to generate
|
||||
// assuming that the message is multipart, this will be ignored if the
|
||||
// message is not a concat message
|
||||
public String generatePduString(Pdu pdu, int mpRefNo, int partNo) {
|
||||
try {
|
||||
this.baos = new ByteArrayOutputStream();
|
||||
this.firstOctetPosition = -1;
|
||||
this.updateFirstOctet = false;
|
||||
// process the PDU
|
||||
switch (pdu.getTpMti()) {
|
||||
case PduUtils.TP_MTI_SMS_DELIVER:
|
||||
generateSmsDeliverPduString((SmsDeliveryPdu) pdu, mpRefNo, partNo);
|
||||
break;
|
||||
case PduUtils.TP_MTI_SMS_SUBMIT:
|
||||
generateSmsSubmitPduString((SmsSubmitPdu) pdu, mpRefNo, partNo);
|
||||
break;
|
||||
case PduUtils.TP_MTI_SMS_STATUS_REPORT:
|
||||
generateSmsStatusReportPduString((SmsStatusReportPdu) pdu);
|
||||
break;
|
||||
}
|
||||
// in case concat is detected in the writeUD() method
|
||||
// and there was no UDHI at the time of detection
|
||||
// the old firstOctet must be overwritten with the new value
|
||||
byte[] pduBytes = this.baos.toByteArray();
|
||||
if (this.updateFirstOctet) {
|
||||
pduBytes[this.firstOctetPosition] = (byte) (pdu.getFirstOctet() & 0xFF);
|
||||
}
|
||||
return PduUtils.bytesToPdu(pduBytes);
|
||||
} catch (IOException e) {
|
||||
throw new UnrecoverableSmslibException("Cannot generate pdu", e);
|
||||
}
|
||||
}
|
||||
|
||||
protected void generateSmsSubmitPduString(SmsSubmitPdu pdu, int mpRefNo, int partNo) throws IOException {
|
||||
String address = pdu.getAddress();
|
||||
if (address == null) {
|
||||
throw new IllegalArgumentException("adress cannot be null");
|
||||
}
|
||||
// SMSC address info
|
||||
writeSmscInfo(pdu);
|
||||
// first octet
|
||||
writeFirstOctet(pdu);
|
||||
// message reference
|
||||
writeByte(pdu.getMessageReference());
|
||||
// destination address info
|
||||
writeAddress(address, pdu.getAddressType(), address.length());
|
||||
// protocol id
|
||||
writeByte(pdu.getProtocolIdentifier());
|
||||
// data coding scheme
|
||||
writeByte(pdu.getDataCodingScheme());
|
||||
// validity period
|
||||
switch (pdu.getTpVpf()) {
|
||||
case PduUtils.TP_VPF_INTEGER:
|
||||
writeValidityPeriodInteger(pdu.getValidityPeriod());
|
||||
break;
|
||||
case PduUtils.TP_VPF_TIMESTAMP:
|
||||
Date validityDate = pdu.getValidityDate();
|
||||
if (validityDate == null) {
|
||||
throw new IllegalArgumentException("Cannot get validity date for pdu");
|
||||
}
|
||||
writeTimeStampStringForDate(validityDate);
|
||||
break;
|
||||
}
|
||||
// user data
|
||||
// headers
|
||||
writeUDData(pdu, mpRefNo, partNo);
|
||||
}
|
||||
|
||||
// NOTE: the following are just for validation of the PduParser
|
||||
// - there is no normal scenario where these are used
|
||||
protected void generateSmsDeliverPduString(SmsDeliveryPdu pdu, int mpRefNo, int partNo) throws IOException {
|
||||
// SMSC address info
|
||||
writeSmscInfo(pdu);
|
||||
// first octet
|
||||
writeFirstOctet(pdu);
|
||||
// originator address info
|
||||
String address = pdu.getAddress();
|
||||
if (address == null) {
|
||||
throw new IllegalArgumentException("Address cannot be null");
|
||||
}
|
||||
writeAddress(address, pdu.getAddressType(), address.length());
|
||||
// protocol id
|
||||
writeByte(pdu.getProtocolIdentifier());
|
||||
// data coding scheme
|
||||
writeByte(pdu.getDataCodingScheme());
|
||||
// timestamp
|
||||
Date timestamp = pdu.getTimestamp();
|
||||
if (timestamp != null) {
|
||||
writeTimeStampStringForDate(timestamp);
|
||||
}
|
||||
// user data
|
||||
// headers
|
||||
writeUDData(pdu, mpRefNo, partNo);
|
||||
}
|
||||
|
||||
protected void generateSmsStatusReportPduString(SmsStatusReportPdu pdu) throws IOException {
|
||||
// SMSC address info
|
||||
writeSmscInfo(pdu);
|
||||
// first octet
|
||||
writeFirstOctet(pdu);
|
||||
// message reference
|
||||
writeByte(pdu.getMessageReference());
|
||||
// destination address info
|
||||
String address = pdu.getAddress();
|
||||
if (address == null) {
|
||||
throw new IllegalArgumentException("Address cannot be null");
|
||||
}
|
||||
writeAddress(address, pdu.getAddressType(), address.length());
|
||||
// timestamp
|
||||
Date timestamp = pdu.getTimestamp();
|
||||
if (timestamp == null) {
|
||||
throw new IllegalArgumentException("cannot write null timestamp");
|
||||
}
|
||||
writeTimeStampStringForDate(timestamp);
|
||||
// discharge time(timestamp)
|
||||
Date dischargeTime = pdu.getDischargeTime();
|
||||
if (dischargeTime == null) {
|
||||
throw new IllegalArgumentException("cannot write null dischargeTime");
|
||||
}
|
||||
writeTimeStampStringForDate(dischargeTime);
|
||||
// status
|
||||
writeByte(pdu.getStatus());
|
||||
}
|
||||
}
|
||||
343
bundles/org.openhab.binding.smsmodem/src/3rdparty/java/org/smslib/pduUtils/gsm3040/PduParser.java
vendored
Normal file
343
bundles/org.openhab.binding.smsmodem/src/3rdparty/java/org/smslib/pduUtils/gsm3040/PduParser.java
vendored
Normal file
@@ -0,0 +1,343 @@
|
||||
package org.smslib.pduUtils.gsm3040;
|
||||
|
||||
import java.util.Calendar;
|
||||
import java.util.TimeZone;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.smslib.UnrecoverableSmslibException;
|
||||
import org.smslib.message.MsIsdn;
|
||||
import org.smslib.pduUtils.gsm3040.ie.InformationElement;
|
||||
import org.smslib.pduUtils.gsm3040.ie.InformationElementFactory;
|
||||
|
||||
//PduUtils Library - A Java library for generating GSM 3040 Protocol Data Units (PDUs)
|
||||
//
|
||||
//Copyright (C) 2008, Ateneo Java Wireless Competency Center/Blueblade Technologies, Philippines.
|
||||
//PduUtils is distributed under the terms of the Apache License version 2.0
|
||||
//
|
||||
//Licensed under the Apache License, Version 2.0 (the "License");
|
||||
//you may not use this file except in compliance with the License.
|
||||
//You may obtain a copy of the License at
|
||||
//
|
||||
//http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
//Unless required by applicable law or agreed to in writing, software
|
||||
//distributed under the License is distributed on an "AS IS" BASIS,
|
||||
//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//See the License for the specific language governing permissions and
|
||||
//limitations under the License.
|
||||
|
||||
/**
|
||||
* Extracted from SMSLib
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class PduParser {
|
||||
// ==================================================
|
||||
// RAW PDU PARSER
|
||||
// ==================================================
|
||||
// increments as methods are called
|
||||
private int position;
|
||||
|
||||
private byte @Nullable [] pduByteArray;
|
||||
|
||||
// possible types of data
|
||||
// BCD digits
|
||||
// byte
|
||||
// gsm-septets
|
||||
// timestamp info
|
||||
private int readByte() {
|
||||
// read 8-bits forward
|
||||
byte[] pduByteArrayFinal = this.pduByteArray;
|
||||
if (pduByteArrayFinal == null) {
|
||||
throw new UnrecoverableSmslibException("Cannot read byte from null data");
|
||||
}
|
||||
int retVal = pduByteArrayFinal[this.position] & 0xFF;
|
||||
this.position++;
|
||||
return retVal;
|
||||
}
|
||||
|
||||
private int readSwappedNibbleBCDByte() {
|
||||
// read 8-bits forward, swap the nibbles
|
||||
int data = readByte();
|
||||
data = PduUtils.swapNibbles((byte) data);
|
||||
int retVal = 0;
|
||||
retVal += ((data >>> 4) & 0xF) * 10;
|
||||
retVal += ((data & 0xF));
|
||||
return retVal;
|
||||
}
|
||||
|
||||
private Calendar readTimeStamp() {
|
||||
// reads timestamp info
|
||||
// 7 bytes in semi-octet(BCD) style
|
||||
int year = readSwappedNibbleBCDByte();
|
||||
int month = readSwappedNibbleBCDByte();
|
||||
int day = readSwappedNibbleBCDByte();
|
||||
int hour = readSwappedNibbleBCDByte();
|
||||
int minute = readSwappedNibbleBCDByte();
|
||||
int second = readSwappedNibbleBCDByte();
|
||||
// special treatment for timezone due to sign bit
|
||||
// swap nibbles, clear the sign bit, convert remaining bits to BCD
|
||||
int timestamp = readByte();
|
||||
boolean negative = (timestamp & 0x08) == 0x08; // check bit 3
|
||||
int timezone = PduUtils.swapNibbles(timestamp) & 0x7F; // remove last bit since this is just a sign
|
||||
// time zone computation
|
||||
TimeZone tz = null;
|
||||
if (negative) {
|
||||
// bit 3 of unswapped value represents the sign (1 == negative, 0 == positive)
|
||||
// when swapped this will now be bit 7 (128)
|
||||
int bcdTimeZone = 0;
|
||||
bcdTimeZone += (((timezone >>> 4) & 0xF) * 10);
|
||||
bcdTimeZone += ((timezone & 0xF));
|
||||
timezone = bcdTimeZone;
|
||||
int totalMinutes = timezone * 15;
|
||||
int hours = totalMinutes / 60;
|
||||
int minutes = totalMinutes % 60;
|
||||
String gmtString = "GMT-" + hours + ":" + (minutes < 10 ? "0" : "") + minutes;
|
||||
// System.out.println(gmtString);
|
||||
tz = TimeZone.getTimeZone(gmtString);
|
||||
} else {
|
||||
int bcdTimeZone = 0;
|
||||
bcdTimeZone += ((timezone >>> 4) & 0xF) * 10;
|
||||
bcdTimeZone += ((timezone & 0xF));
|
||||
timezone = bcdTimeZone;
|
||||
int totalMinutes = timezone * 15;
|
||||
int hours = totalMinutes / 60;
|
||||
int minutes = totalMinutes % 60;
|
||||
String gmtString = "GMT+" + hours + ":" + (minutes < 10 ? "0" : "") + minutes;
|
||||
// System.out.println(gmtString);
|
||||
tz = TimeZone.getTimeZone(gmtString);
|
||||
}
|
||||
Calendar cal = Calendar.getInstance(tz);
|
||||
cal.set(Calendar.YEAR, year + 2000);
|
||||
cal.set(Calendar.MONTH, month - 1);
|
||||
cal.set(Calendar.DAY_OF_MONTH, day);
|
||||
cal.set(Calendar.HOUR_OF_DAY, hour);
|
||||
cal.set(Calendar.MINUTE, minute);
|
||||
cal.set(Calendar.SECOND, second);
|
||||
return cal;
|
||||
}
|
||||
|
||||
private @Nullable String readAddress(int addressLength, int addressType) {
|
||||
// NOTE: the max number of octets on an address is 12 octets
|
||||
// this means that an address field need only be 12 octets long
|
||||
// what about for 7-bit? This would be 13 chars at 12 octets?
|
||||
if (addressLength > 0) {
|
||||
// length is a semi-octet count
|
||||
int addressDataOctetLength = addressLength / 2 + ((addressLength % 2 == 1) ? 1 : 0);
|
||||
// extract data and increment position
|
||||
byte[] addressData = new byte[addressDataOctetLength];
|
||||
byte[] pduByteArrayFinal = this.pduByteArray;
|
||||
if (pduByteArrayFinal != null) {
|
||||
System.arraycopy(pduByteArrayFinal, this.position, addressData, 0, addressDataOctetLength);
|
||||
} else {
|
||||
throw new UnrecoverableSmslibException("Cannot read address because pdu data is null");
|
||||
}
|
||||
this.position = this.position + addressDataOctetLength;
|
||||
switch (PduUtils.extractAddressType(addressType)) {
|
||||
case PduUtils.ADDRESS_TYPE_ALPHANUMERIC:
|
||||
// extract and process encoded bytes
|
||||
byte[] uncompressed = PduUtils.encodedSeptetsToUnencodedSeptets(addressData);
|
||||
int septets = addressLength * 4 / 7;
|
||||
byte[] choppedAddressData = new byte[septets];
|
||||
System.arraycopy(uncompressed, 0, choppedAddressData, 0, septets);
|
||||
return PduUtils.unencodedSeptetsToString(choppedAddressData);
|
||||
default:
|
||||
// process BCD style data any other
|
||||
return PduUtils.readBCDNumbers(addressLength, addressData);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private int readValidityPeriodInt() {
|
||||
// this will convert the VP to #MINUTES
|
||||
int validity = readByte();
|
||||
int minutes = 0;
|
||||
if ((validity > 0) && (validity <= 143)) {
|
||||
// groups of 5 min
|
||||
minutes = (validity + 1) * 5;
|
||||
} else if ((validity > 143) && (validity <= 167)) {
|
||||
// groups of 30 min + 12 hrs
|
||||
minutes = (12 * 60) + (validity - 143) * 30;
|
||||
} else if ((validity > 167) && (validity <= 196)) {
|
||||
// days
|
||||
minutes = (validity - 166) * 24 * 60;
|
||||
} else if ((validity > 197) && (validity <= 255)) {
|
||||
// weeks
|
||||
minutes = (validity - 192) * 7 * 24 * 60;
|
||||
}
|
||||
return minutes;
|
||||
}
|
||||
|
||||
public Pdu parsePdu(String rawPdu) {
|
||||
// encode pdu to byte[] for easier processing
|
||||
this.pduByteArray = PduUtils.pduToBytes(rawPdu);
|
||||
this.position = 0;
|
||||
// parse start and determine what type of pdu it is
|
||||
Pdu pdu = parseStart();
|
||||
// parse depending on the pdu type
|
||||
switch (pdu.getTpMti()) {
|
||||
case PduUtils.TP_MTI_SMS_DELIVER:
|
||||
parseSmsDeliverMessage((SmsDeliveryPdu) pdu);
|
||||
break;
|
||||
case PduUtils.TP_MTI_SMS_SUBMIT:
|
||||
parseSmsSubmitMessage((SmsSubmitPdu) pdu);
|
||||
break;
|
||||
case PduUtils.TP_MTI_SMS_STATUS_REPORT:
|
||||
parseSmsStatusReportMessage((SmsStatusReportPdu) pdu);
|
||||
break;
|
||||
}
|
||||
return pdu;
|
||||
}
|
||||
|
||||
private Pdu parseStart() {
|
||||
// SMSC info
|
||||
// length
|
||||
// address type
|
||||
// smsc data
|
||||
int addressLength = readByte();
|
||||
Pdu pdu = null;
|
||||
if (addressLength > 0) {
|
||||
int addressType = readByte();
|
||||
String smscAddress = readAddress((addressLength - 1) * 2, addressType);
|
||||
// first octet - determine how to parse and how to store
|
||||
int firstOctet = readByte();
|
||||
pdu = PduFactory.createPdu(firstOctet);
|
||||
// generic methods
|
||||
pdu.setSmscAddressType(addressType);
|
||||
pdu.setSmscAddress(smscAddress);
|
||||
pdu.setSmscInfoLength(addressLength);
|
||||
} else {
|
||||
// first octet - determine how to parse and how to store
|
||||
int firstOctet = readByte();
|
||||
pdu = PduFactory.createPdu(firstOctet);
|
||||
}
|
||||
return pdu;
|
||||
}
|
||||
|
||||
private void parseUserData(Pdu pdu) {
|
||||
// ud length
|
||||
// NOTE: - the udLength value is just stored, it is not used to determine the length
|
||||
// of the remaining data (it may be a septet length not an octet length)
|
||||
// - parser just assumes that the remaining PDU data is for the User-Data field
|
||||
int udLength = readByte();
|
||||
pdu.setUDLength(udLength);
|
||||
// user data
|
||||
// NOTE: UD Data does not contain the length octet
|
||||
byte[] pduByteArrayFinal = this.pduByteArray;
|
||||
if (pduByteArrayFinal != null) {
|
||||
int udOctetLength = pduByteArrayFinal.length - this.position;
|
||||
byte[] udData = new byte[udOctetLength];
|
||||
System.arraycopy(pduByteArrayFinal, this.position, udData, 0, udOctetLength);
|
||||
// save the UD data
|
||||
pdu.setUDData(udData);
|
||||
} else {
|
||||
throw new UnrecoverableSmslibException("Cannot parse user data because pdu data is null");
|
||||
}
|
||||
// user data header (if present)
|
||||
// position is still at the start of the UD
|
||||
if (pdu.hasTpUdhi()) {
|
||||
// udh length
|
||||
int udhLength = readByte();
|
||||
// udh data (iterate till udh is consumed)
|
||||
// iei id
|
||||
// iei data length
|
||||
// iei data
|
||||
int endUdh = this.position + udhLength;
|
||||
while (this.position < endUdh) {
|
||||
int iei = readByte();
|
||||
int iedl = readByte();
|
||||
byte[] ieData = new byte[iedl];
|
||||
System.arraycopy(pduByteArrayFinal, this.position, ieData, 0, iedl);
|
||||
InformationElement ie = InformationElementFactory.createInformationElement(iei, ieData);
|
||||
pdu.addInformationElement(ie);
|
||||
this.position = this.position + iedl;
|
||||
if (this.position > endUdh) {
|
||||
// at the end, position after adding should be exactly at endUdh
|
||||
throw new UnrecoverableSmslibException(
|
||||
"UDH is shorter than expected endUdh=" + endUdh + ", position=" + this.position);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void parseSmsDeliverMessage(SmsDeliveryPdu pdu) {
|
||||
// originator address info
|
||||
// address length
|
||||
// type of address
|
||||
// address data
|
||||
int addressLength = readByte();
|
||||
int addressType = readByte();
|
||||
String originatorAddress = readAddress(addressLength, addressType);
|
||||
pdu.setAddressType(addressType);
|
||||
if (originatorAddress != null) {
|
||||
pdu.setAddress(new MsIsdn(originatorAddress));
|
||||
}
|
||||
// protocol id
|
||||
int protocolId = readByte();
|
||||
pdu.setProtocolIdentifier(protocolId);
|
||||
// data coding scheme
|
||||
int dcs = readByte();
|
||||
pdu.setDataCodingScheme(dcs);
|
||||
// timestamp
|
||||
Calendar timestamp = readTimeStamp();
|
||||
pdu.setTimestamp(timestamp);
|
||||
// user data
|
||||
parseUserData(pdu);
|
||||
}
|
||||
|
||||
private void parseSmsStatusReportMessage(SmsStatusReportPdu pdu) {
|
||||
// message reference
|
||||
int messageReference = readByte();
|
||||
pdu.setMessageReference(messageReference);
|
||||
// destination address info
|
||||
int addressLength = readByte();
|
||||
int addressType = readByte();
|
||||
String destinationAddress = readAddress(addressLength, addressType);
|
||||
pdu.setAddressType(addressType);
|
||||
pdu.setAddress(new MsIsdn(destinationAddress));
|
||||
// timestamp
|
||||
Calendar timestamp = readTimeStamp();
|
||||
pdu.setTimestamp(timestamp);
|
||||
// discharge time(timestamp)
|
||||
Calendar timestamp2 = readTimeStamp();
|
||||
pdu.setDischargeTime(timestamp2);
|
||||
// status
|
||||
int status = readByte();
|
||||
pdu.setStatus(status);
|
||||
}
|
||||
|
||||
// NOTE: the following is just for validation of the PduGenerator
|
||||
// - there is no normal scenario where this is used
|
||||
private void parseSmsSubmitMessage(SmsSubmitPdu pdu) {
|
||||
// message reference
|
||||
int messageReference = readByte();
|
||||
pdu.setMessageReference(messageReference);
|
||||
// destination address info
|
||||
int addressLength = readByte();
|
||||
int addressType = readByte();
|
||||
String destinationAddress = readAddress(addressLength, addressType);
|
||||
pdu.setAddressType(addressType);
|
||||
pdu.setAddress(new MsIsdn(destinationAddress));
|
||||
// protocol id
|
||||
int protocolId = readByte();
|
||||
pdu.setProtocolIdentifier(protocolId);
|
||||
// data coding scheme
|
||||
int dcs = readByte();
|
||||
pdu.setDataCodingScheme(dcs);
|
||||
// validity period
|
||||
switch (pdu.getTpVpf()) {
|
||||
case PduUtils.TP_VPF_NONE:
|
||||
break;
|
||||
case PduUtils.TP_VPF_INTEGER:
|
||||
int validityInt = readValidityPeriodInt();
|
||||
pdu.setValidityPeriod(validityInt / 60); // pdu assumes hours
|
||||
break;
|
||||
case PduUtils.TP_VPF_TIMESTAMP:
|
||||
Calendar validityDate = readTimeStamp();
|
||||
pdu.setValidityTimestamp(validityDate);
|
||||
break;
|
||||
}
|
||||
parseUserData(pdu);
|
||||
}
|
||||
}
|
||||
761
bundles/org.openhab.binding.smsmodem/src/3rdparty/java/org/smslib/pduUtils/gsm3040/PduUtils.java
vendored
Normal file
761
bundles/org.openhab.binding.smsmodem/src/3rdparty/java/org/smslib/pduUtils/gsm3040/PduUtils.java
vendored
Normal file
@@ -0,0 +1,761 @@
|
||||
package org.smslib.pduUtils.gsm3040;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.util.BitSet;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.smslib.UnrecoverableSmslibException;
|
||||
import org.smslib.message.MsIsdn;
|
||||
|
||||
//PduUtils Library - A Java library for generating GSM 3040 Protocol Data Units (PDUs)
|
||||
//
|
||||
//Copyright (C) 2008, Ateneo Java Wireless Competency Center/Blueblade Technologies, Philippines.
|
||||
//PduUtils is distributed under the terms of the Apache License version 2.0
|
||||
//
|
||||
//Licensed under the Apache License, Version 2.0 (the "License");
|
||||
//you may not use this file except in compliance with the License.
|
||||
//You may obtain a copy of the License at
|
||||
//
|
||||
//http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
//Unless required by applicable law or agreed to in writing, software
|
||||
//distributed under the License is distributed on an "AS IS" BASIS,
|
||||
//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//See the License for the specific language governing permissions and
|
||||
//limitations under the License.
|
||||
|
||||
/**
|
||||
* Extracted from SMSLib
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class PduUtils {
|
||||
// ==================================================
|
||||
// GSM ALPHABET
|
||||
// ==================================================
|
||||
private static final char[][] grcAlphabetRemapping = { { '\u0386', '\u0041' }, // GREEK CAPITAL LETTER ALPHA WITH
|
||||
// TONOS
|
||||
{ '\u0388', '\u0045' }, // GREEK CAPITAL LETTER EPSILON WITH TONOS
|
||||
{ '\u0389', '\u0048' }, // GREEK CAPITAL LETTER ETA WITH TONOS
|
||||
{ '\u038A', '\u0049' }, // GREEK CAPITAL LETTER IOTA WITH TONOS
|
||||
{ '\u038C', '\u004F' }, // GREEK CAPITAL LETTER OMICRON WITH TONOS
|
||||
{ '\u038E', '\u0059' }, // GREEK CAPITAL LETTER UPSILON WITH TONOS
|
||||
{ '\u038F', '\u03A9' }, // GREEK CAPITAL LETTER OMEGA WITH TONOS
|
||||
{ '\u0390', '\u0049' }, // GREEK SMALL LETTER IOTA WITH DIALYTIKA AND TONOS
|
||||
{ '\u0391', '\u0041' }, // GREEK CAPITAL LETTER ALPHA
|
||||
{ '\u0392', '\u0042' }, // GREEK CAPITAL LETTER BETA
|
||||
{ '\u0393', '\u0393' }, // GREEK CAPITAL LETTER GAMMA
|
||||
{ '\u0394', '\u0394' }, // GREEK CAPITAL LETTER DELTA
|
||||
{ '\u0395', '\u0045' }, // GREEK CAPITAL LETTER EPSILON
|
||||
{ '\u0396', '\u005A' }, // GREEK CAPITAL LETTER ZETA
|
||||
{ '\u0397', '\u0048' }, // GREEK CAPITAL LETTER ETA
|
||||
{ '\u0398', '\u0398' }, // GREEK CAPITAL LETTER THETA
|
||||
{ '\u0399', '\u0049' }, // GREEK CAPITAL LETTER IOTA
|
||||
{ '\u039A', '\u004B' }, // GREEK CAPITAL LETTER KAPPA
|
||||
{ '\u039B', '\u039B' }, // GREEK CAPITAL LETTER LAMDA
|
||||
{ '\u039C', '\u004D' }, // GREEK CAPITAL LETTER MU
|
||||
{ '\u039D', '\u004E' }, // GREEK CAPITAL LETTER NU
|
||||
{ '\u039E', '\u039E' }, // GREEK CAPITAL LETTER XI
|
||||
{ '\u039F', '\u004F' }, // GREEK CAPITAL LETTER OMICRON
|
||||
{ '\u03A0', '\u03A0' }, // GREEK CAPITAL LETTER PI
|
||||
{ '\u03A1', '\u0050' }, // GREEK CAPITAL LETTER RHO
|
||||
{ '\u03A3', '\u03A3' }, // GREEK CAPITAL LETTER SIGMA
|
||||
{ '\u03A4', '\u0054' }, // GREEK CAPITAL LETTER TAU
|
||||
{ '\u03A5', '\u0059' }, // GREEK CAPITAL LETTER UPSILON
|
||||
{ '\u03A6', '\u03A6' }, // GREEK CAPITAL LETTER PHI
|
||||
{ '\u03A7', '\u0058' }, // GREEK CAPITAL LETTER CHI
|
||||
{ '\u03A8', '\u03A8' }, // GREEK CAPITAL LETTER PSI
|
||||
{ '\u03A9', '\u03A9' }, // GREEK CAPITAL LETTER OMEGA
|
||||
{ '\u03AA', '\u0049' }, // GREEK CAPITAL LETTER IOTA WITH DIALYTIKA
|
||||
{ '\u03AB', '\u0059' }, // GREEK CAPITAL LETTER UPSILON WITH DIALYTIKA
|
||||
{ '\u03AC', '\u0041' }, // GREEK SMALL LETTER ALPHA WITH TONOS
|
||||
{ '\u03AD', '\u0045' }, // GREEK SMALL LETTER EPSILON WITH TONOS
|
||||
{ '\u03AE', '\u0048' }, // GREEK SMALL LETTER ETA WITH TONOS
|
||||
{ '\u03AF', '\u0049' }, // GREEK SMALL LETTER IOTA WITH TONOS
|
||||
{ '\u03B0', '\u0059' }, // GREEK SMALL LETTER UPSILON WITH DIALYTIKA AND TONOS
|
||||
{ '\u03B1', '\u0041' }, // GREEK SMALL LETTER ALPHA
|
||||
{ '\u03B2', '\u0042' }, // GREEK SMALL LETTER BETA
|
||||
{ '\u03B3', '\u0393' }, // GREEK SMALL LETTER GAMMA
|
||||
{ '\u03B4', '\u0394' }, // GREEK SMALL LETTER DELTA
|
||||
{ '\u03B5', '\u0045' }, // GREEK SMALL LETTER EPSILON
|
||||
{ '\u03B6', '\u005A' }, // GREEK SMALL LETTER ZETA
|
||||
{ '\u03B7', '\u0048' }, // GREEK SMALL LETTER ETA
|
||||
{ '\u03B8', '\u0398' }, // GREEK SMALL LETTER THETA
|
||||
{ '\u03B9', '\u0049' }, // GREEK SMALL LETTER IOTA
|
||||
{ '\u03BA', '\u004B' }, // GREEK SMALL LETTER KAPPA
|
||||
{ '\u03BB', '\u039B' }, // GREEK SMALL LETTER LAMDA
|
||||
{ '\u03BC', '\u004D' }, // GREEK SMALL LETTER MU
|
||||
{ '\u03BD', '\u004E' }, // GREEK SMALL LETTER NU
|
||||
{ '\u03BE', '\u039E' }, // GREEK SMALL LETTER XI
|
||||
{ '\u03BF', '\u004F' }, // GREEK SMALL LETTER OMICRON
|
||||
{ '\u03C0', '\u03A0' }, // GREEK SMALL LETTER PI
|
||||
{ '\u03C1', '\u0050' }, // GREEK SMALL LETTER RHO
|
||||
{ '\u03C2', '\u03A3' }, // GREEK SMALL LETTER FINAL SIGMA
|
||||
{ '\u03C3', '\u03A3' }, // GREEK SMALL LETTER SIGMA
|
||||
{ '\u03C4', '\u0054' }, // GREEK SMALL LETTER TAU
|
||||
{ '\u03C5', '\u0059' }, // GREEK SMALL LETTER UPSILON
|
||||
{ '\u03C6', '\u03A6' }, // GREEK SMALL LETTER PHI
|
||||
{ '\u03C7', '\u0058' }, // GREEK SMALL LETTER CHI
|
||||
{ '\u03C8', '\u03A8' }, // GREEK SMALL LETTER PSI
|
||||
{ '\u03C9', '\u03A9' }, // GREEK SMALL LETTER OMEGA
|
||||
{ '\u03CA', '\u0049' }, // GREEK SMALL LETTER IOTA WITH DIALYTIKA
|
||||
{ '\u03CB', '\u0059' }, // GREEK SMALL LETTER UPSILON WITH DIALYTIKA
|
||||
{ '\u03CC', '\u004F' }, // GREEK SMALL LETTER OMICRON WITH TONOS
|
||||
{ '\u03CD', '\u0059' }, // GREEK SMALL LETTER UPSILON WITH TONOS
|
||||
{ '\u03CE', '\u03A9' } // GREEK SMALL LETTER OMEGA WITH TONOS
|
||||
};
|
||||
|
||||
private static final char[] extAlphabet = { '\u000c', // FORM FEED
|
||||
'\u005e', // CIRCUMFLEX ACCENT
|
||||
'\u007b', // LEFT CURLY BRACKET
|
||||
'\u007d', // RIGHT CURLY BRACKET
|
||||
'\\', // REVERSE SOLIDUS
|
||||
'\u005b', // LEFT SQUARE BRACKET
|
||||
'\u007e', // TILDE
|
||||
'\u005d', // RIGHT SQUARE BRACKET
|
||||
'\u007c', // VERTICAL LINES
|
||||
'\u20ac', // EURO SIGN
|
||||
};
|
||||
|
||||
private static final String[] extBytes = { "1b0a", // FORM FEED
|
||||
"1b14", // CIRCUMFLEX ACCENT
|
||||
"1b28", // LEFT CURLY BRACKET
|
||||
"1b29", // RIGHT CURLY BRACKET
|
||||
"1b2f", // REVERSE SOLIDUS
|
||||
"1b3c", // LEFT SQUARE BRACKET
|
||||
"1b3d", // TILDE
|
||||
"1b3e", // RIGHT SQUARE BRACKET
|
||||
"1b40", // VERTICAL LINES
|
||||
"1b65", // EURO SIGN
|
||||
};
|
||||
|
||||
// NOTE: this is an adjustment required to compensate for
|
||||
// multi-byte characters split across the end of a pdu part
|
||||
// if the previous part is noted to be ending in a '1b'
|
||||
// call this method on the first char of the next part
|
||||
// to adjust it for the missing '1b'
|
||||
public static String getMultiCharFor(char c) {
|
||||
switch (c) {
|
||||
// GSM 0x0A (line feed) ==> form feed
|
||||
case '\n':
|
||||
return "'\u000c'";
|
||||
// GSM 0x14 (greek capital lamda) ==> circumflex
|
||||
case '\u039B':
|
||||
return "^";
|
||||
// GSM 0x28 (left parenthesis) ==> left curly brace
|
||||
case '(':
|
||||
return "{";
|
||||
// GSM 0x29 (right parenthesis) ==> right curly brace
|
||||
case ')':
|
||||
return "}";
|
||||
// GSM 0x2f (solidus or slash) ==> reverse solidus or backslash
|
||||
case '/':
|
||||
return "\\";
|
||||
// GSM 0x3c (less than sign) ==> left square bracket
|
||||
case '<':
|
||||
return "[";
|
||||
// GSM 0x3d (equals sign) ==> tilde
|
||||
case '=':
|
||||
return "~";
|
||||
// GSM 0x3e (greater than sign) ==> right square bracket
|
||||
case '>':
|
||||
return "]";
|
||||
// GSM 0x40 (inverted exclamation point) ==> pipe
|
||||
case '\u00A1':
|
||||
return "|";
|
||||
// GSM 0x65 (latin small e) ==> euro
|
||||
case 'e':
|
||||
return "\u20ac";
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
private static final char[] stdAlphabet = { '\u0040', // COMMERCIAL AT
|
||||
'\u00A3', // POUND SIGN
|
||||
'\u0024', // DOLLAR SIGN
|
||||
'\u00A5', // YEN SIGN
|
||||
'\u00E8', // LATIN SMALL LETTER E WITH GRAVE
|
||||
'\u00E9', // LATIN SMALL LETTER E WITH ACUTE
|
||||
'\u00F9', // LATIN SMALL LETTER U WITH GRAVE
|
||||
'\u00EC', // LATIN SMALL LETTER I WITH GRAVE
|
||||
'\u00F2', // LATIN SMALL LETTER O WITH GRAVE
|
||||
'\u00E7', // LATIN SMALL LETTER C WITH CEDILLA
|
||||
'\n', // LINE FEED
|
||||
'\u00D8', // LATIN CAPITAL LETTER O WITH STROKE
|
||||
'\u00F8', // LATIN SMALL LETTER O WITH STROKE
|
||||
'\r', // CARRIAGE RETURN
|
||||
'\u00C5', // LATIN CAPITAL LETTER A WITH RING ABOVE
|
||||
'\u00E5', // LATIN SMALL LETTER A WITH RING ABOVE
|
||||
'\u0394', // GREEK CAPITAL LETTER DELTA
|
||||
'\u005F', // LOW LINE
|
||||
'\u03A6', // GREEK CAPITAL LETTER PHI
|
||||
'\u0393', // GREEK CAPITAL LETTER GAMMA
|
||||
'\u039B', // GREEK CAPITAL LETTER LAMDA
|
||||
'\u03A9', // GREEK CAPITAL LETTER OMEGA
|
||||
'\u03A0', // GREEK CAPITAL LETTER PI
|
||||
'\u03A8', // GREEK CAPITAL LETTER PSI
|
||||
'\u03A3', // GREEK CAPITAL LETTER SIGMA
|
||||
'\u0398', // GREEK CAPITAL LETTER THETA
|
||||
'\u039E', // GREEK CAPITAL LETTER XI
|
||||
'\u00A0', // ESCAPE TO EXTENSION TABLE (or displayed as NBSP, see
|
||||
// note
|
||||
// above)
|
||||
'\u00C6', // LATIN CAPITAL LETTER AE
|
||||
'\u00E6', // LATIN SMALL LETTER AE
|
||||
'\u00DF', // LATIN SMALL LETTER SHARP S (German)
|
||||
'\u00C9', // LATIN CAPITAL LETTER E WITH ACUTE
|
||||
'\u0020', // SPACE
|
||||
'\u0021', // EXCLAMATION MARK
|
||||
'\u0022', // QUOTATION MARK
|
||||
'\u0023', // NUMBER SIGN
|
||||
'\u00A4', // CURRENCY SIGN
|
||||
'\u0025', // PERCENT SIGN
|
||||
'\u0026', // AMPERSAND
|
||||
'\'', // APOSTROPHE
|
||||
'\u0028', // LEFT PARENTHESIS
|
||||
'\u0029', // RIGHT PARENTHESIS
|
||||
'\u002A', // ASTERISK
|
||||
'\u002B', // PLUS SIGN
|
||||
'\u002C', // COMMA
|
||||
'\u002D', // HYPHEN-MINUS
|
||||
'\u002E', // FULL STOP
|
||||
'\u002F', // SOLIDUS
|
||||
'\u0030', // DIGIT ZERO
|
||||
'\u0031', // DIGIT ONE
|
||||
'\u0032', // DIGIT TWO
|
||||
'\u0033', // DIGIT THREE
|
||||
'\u0034', // DIGIT FOUR
|
||||
'\u0035', // DIGIT FIVE
|
||||
'\u0036', // DIGIT SIX
|
||||
'\u0037', // DIGIT SEVEN
|
||||
'\u0038', // DIGIT EIGHT
|
||||
'\u0039', // DIGIT NINE
|
||||
'\u003A', // COLON
|
||||
'\u003B', // SEMICOLON
|
||||
'\u003C', // LESS-THAN SIGN
|
||||
'\u003D', // EQUALS SIGN
|
||||
'\u003E', // GREATER-THAN SIGN
|
||||
'\u003F', // QUESTION MARK
|
||||
'\u00A1', // INVERTED EXCLAMATION MARK
|
||||
'\u0041', // LATIN CAPITAL LETTER A
|
||||
'\u0042', // LATIN CAPITAL LETTER B
|
||||
'\u0043', // LATIN CAPITAL LETTER C
|
||||
'\u0044', // LATIN CAPITAL LETTER D
|
||||
'\u0045', // LATIN CAPITAL LETTER E
|
||||
'\u0046', // LATIN CAPITAL LETTER F
|
||||
'\u0047', // LATIN CAPITAL LETTER G
|
||||
'\u0048', // LATIN CAPITAL LETTER H
|
||||
'\u0049', // LATIN CAPITAL LETTER I
|
||||
'\u004A', // LATIN CAPITAL LETTER J
|
||||
'\u004B', // LATIN CAPITAL LETTER K
|
||||
'\u004C', // LATIN CAPITAL LETTER L
|
||||
'\u004D', // LATIN CAPITAL LETTER M
|
||||
'\u004E', // LATIN CAPITAL LETTER N
|
||||
'\u004F', // LATIN CAPITAL LETTER O
|
||||
'\u0050', // LATIN CAPITAL LETTER P
|
||||
'\u0051', // LATIN CAPITAL LETTER Q
|
||||
'\u0052', // LATIN CAPITAL LETTER R
|
||||
'\u0053', // LATIN CAPITAL LETTER S
|
||||
'\u0054', // LATIN CAPITAL LETTER T
|
||||
'\u0055', // LATIN CAPITAL LETTER U
|
||||
'\u0056', // LATIN CAPITAL LETTER V
|
||||
'\u0057', // LATIN CAPITAL LETTER W
|
||||
'\u0058', // LATIN CAPITAL LETTER X
|
||||
'\u0059', // LATIN CAPITAL LETTER Y
|
||||
'\u005A', // LATIN CAPITAL LETTER Z
|
||||
'\u00C4', // LATIN CAPITAL LETTER A WITH DIAERESIS
|
||||
'\u00D6', // LATIN CAPITAL LETTER O WITH DIAERESIS
|
||||
'\u00D1', // LATIN CAPITAL LETTER N WITH TILDE
|
||||
'\u00DC', // LATIN CAPITAL LETTER U WITH DIAERESIS
|
||||
'\u00A7', // SECTION SIGN
|
||||
'\u00BF', // INVERTED QUESTION MARK
|
||||
'\u0061', // LATIN SMALL LETTER A
|
||||
'\u0062', // LATIN SMALL LETTER B
|
||||
'\u0063', // LATIN SMALL LETTER C
|
||||
'\u0064', // LATIN SMALL LETTER D
|
||||
'\u0065', // LATIN SMALL LETTER E
|
||||
'\u0066', // LATIN SMALL LETTER F
|
||||
'\u0067', // LATIN SMALL LETTER G
|
||||
'\u0068', // LATIN SMALL LETTER H
|
||||
'\u0069', // LATIN SMALL LETTER I
|
||||
'\u006A', // LATIN SMALL LETTER J
|
||||
'\u006B', // LATIN SMALL LETTER K
|
||||
'\u006C', // LATIN SMALL LETTER L
|
||||
'\u006D', // LATIN SMALL LETTER M
|
||||
'\u006E', // LATIN SMALL LETTER N
|
||||
'\u006F', // LATIN SMALL LETTER O
|
||||
'\u0070', // LATIN SMALL LETTER P
|
||||
'\u0071', // LATIN SMALL LETTER Q
|
||||
'\u0072', // LATIN SMALL LETTER R
|
||||
'\u0073', // LATIN SMALL LETTER S
|
||||
'\u0074', // LATIN SMALL LETTER T
|
||||
'\u0075', // LATIN SMALL LETTER U
|
||||
'\u0076', // LATIN SMALL LETTER V
|
||||
'\u0077', // LATIN SMALL LETTER W
|
||||
'\u0078', // LATIN SMALL LETTER X
|
||||
'\u0079', // LATIN SMALL LETTER Y
|
||||
'\u007A', // LATIN SMALL LETTER Z
|
||||
'\u00E4', // LATIN SMALL LETTER A WITH DIAERESIS
|
||||
'\u00F6', // LATIN SMALL LETTER O WITH DIAERESIS
|
||||
'\u00F1', // LATIN SMALL LETTER N WITH TILDE
|
||||
'\u00FC', // LATIN SMALL LETTER U WITH DIAERESIS
|
||||
'\u00E0', // LATIN SMALL LETTER A WITH GRAVE
|
||||
};
|
||||
|
||||
// ==================================================
|
||||
// FIRST OCTET CONSTANTS
|
||||
// ==================================================
|
||||
// to add, use the & with MASK to clear bits on original value
|
||||
// and | this cleared value with constant specified
|
||||
// TP-MTI xxxxxx00 = SMS-DELIVER
|
||||
// xxxxxx10 = SMS-STATUS-REPORT
|
||||
// xxxxxx01 = SMS-SUBMIT
|
||||
public static final int TP_MTI_MASK = 0xFC;
|
||||
|
||||
public static final int TP_MTI_SMS_DELIVER = 0x00;
|
||||
|
||||
public static final int TP_MTI_SMS_SUBMIT = 0x01;
|
||||
|
||||
public static final int TP_MTI_SMS_STATUS_REPORT = 0x02;
|
||||
|
||||
// TP-RD xxxxx0xx = accept duplicate messages
|
||||
// xxxxx1xx = reject duplicate messages
|
||||
// for SMS-SUBMIT only
|
||||
public static final int TP_RD_ACCEPT_DUPLICATES = 0x00;
|
||||
|
||||
// TP-VPF xxx00xxx = no validity period
|
||||
// xxx10xxx = validity period integer-representation
|
||||
// xxx11xxx = validity period timestamp-representation
|
||||
public static final int TP_VPF_MASK = 0xE7;
|
||||
|
||||
public static final int TP_VPF_NONE = 0x00;
|
||||
|
||||
public static final int TP_VPF_INTEGER = 0x10;
|
||||
|
||||
public static final int TP_VPF_TIMESTAMP = 0x18;
|
||||
|
||||
// TP-SRI xx0xxxxx = no status report to SME (for SMS-DELIVER only)
|
||||
// xx1xxxxx = status report to SME
|
||||
public static final int TP_SRI_MASK = 0xDF;
|
||||
|
||||
// TP-SRR xx0xxxxx = no status report (for SMS-SUBMIT only)
|
||||
// xx1xxxxx = status report
|
||||
|
||||
public static final int TP_SRR_NO_REPORT = 0x00;
|
||||
|
||||
public static final int TP_SRR_REPORT = 0x20;
|
||||
|
||||
// TP-UDHI x0xxxxxx = no UDH
|
||||
// x1xxxxxx = UDH present
|
||||
public static final int TP_UDHI_MASK = 0xBF;
|
||||
|
||||
public static final int TP_UDHI_NO_UDH = 0x00;
|
||||
|
||||
public static final int TP_UDHI_WITH_UDH = 0x40;
|
||||
|
||||
// ==================================================
|
||||
// ADDRESS-TYPE CONSTANTS
|
||||
// ==================================================
|
||||
// some typical ones used for sending, though receiving may get other types
|
||||
// usually 1 001 0001 (0x91) international format
|
||||
// 1 000 0001 (0x81) (unknown) short number (e.g. access codes)
|
||||
// 1 101 0000 (0xD0) alphanumeric (e.g. access code names like PasaLoad)
|
||||
public static final int ADDRESS_NUMBER_PLAN_ID_TELEPHONE = 0x01;
|
||||
|
||||
public static final int ADDRESS_TYPE_MASK = 0x70;
|
||||
|
||||
public static final int ADDRESS_TYPE_UNKNOWN = 0x00;
|
||||
|
||||
public static final int ADDRESS_TYPE_INTERNATIONAL = 0x10;
|
||||
|
||||
public static final int ADDRESS_TYPE_NATIONAL = 0x20;
|
||||
|
||||
public static final int ADDRESS_TYPE_ALPHANUMERIC = 0x50;
|
||||
|
||||
public static int getAddressTypeFor(MsIsdn number) {
|
||||
switch (number.getType()) {
|
||||
case International:
|
||||
return createAddressType(ADDRESS_TYPE_INTERNATIONAL | ADDRESS_NUMBER_PLAN_ID_TELEPHONE);
|
||||
case National:
|
||||
return createAddressType(ADDRESS_TYPE_NATIONAL | ADDRESS_NUMBER_PLAN_ID_TELEPHONE);
|
||||
default:
|
||||
return createAddressType(ADDRESS_TYPE_UNKNOWN | ADDRESS_NUMBER_PLAN_ID_TELEPHONE);
|
||||
}
|
||||
}
|
||||
|
||||
public static int extractAddressType(int addressType) {
|
||||
return addressType & ADDRESS_TYPE_MASK;
|
||||
}
|
||||
|
||||
public static int createAddressType(int addressType) {
|
||||
// last bit is always set
|
||||
return 0x80 | addressType;
|
||||
}
|
||||
|
||||
// ==================================================
|
||||
// DCS ENCODING CONSTANTS
|
||||
// ==================================================
|
||||
public static final int DCS_CODING_GROUP_MASK = 0x0F;
|
||||
|
||||
public static final int DCS_CODING_GROUP_DATA = 0xF0;
|
||||
|
||||
public static final int DCS_CODING_GROUP_GENERAL = 0xC0;
|
||||
|
||||
public static final int DCS_ENCODING_MASK = 0xF3;
|
||||
|
||||
public static final int DCS_ENCODING_7BIT = 0x00;
|
||||
|
||||
public static final int DCS_ENCODING_8BIT = 0x04;
|
||||
|
||||
public static final int DCS_ENCODING_UCS2 = 0x08;
|
||||
|
||||
public static final int DCS_MESSAGE_CLASS_MASK = 0xEC;
|
||||
|
||||
public static final int DCS_MESSAGE_CLASS_FLASH = 0x10;
|
||||
|
||||
public static final int DCS_MESSAGE_CLASS_ME = 0x11;
|
||||
|
||||
public static final int DCS_MESSAGE_CLASS_SIM = 0x12;
|
||||
|
||||
public static final int DCS_MESSAGE_CLASS_TE = 0x13;
|
||||
|
||||
public static int extractDcsEncoding(int dataCodingScheme) {
|
||||
return dataCodingScheme & ~PduUtils.DCS_ENCODING_MASK;
|
||||
}
|
||||
|
||||
public static int extractDcsClass(int dataCodingScheme) {
|
||||
return dataCodingScheme & ~DCS_MESSAGE_CLASS_MASK;
|
||||
}
|
||||
|
||||
public static int extractDcsFlash(int dataCodingScheme) {
|
||||
// this is only useful if DCS != 0
|
||||
return dataCodingScheme & ~DCS_MESSAGE_CLASS_MASK;
|
||||
}
|
||||
|
||||
public static String decodeDataCodingScheme(Pdu pdu) {
|
||||
StringBuffer sb = new StringBuffer();
|
||||
switch (PduUtils.extractDcsEncoding(pdu.getDataCodingScheme())) {
|
||||
case PduUtils.DCS_ENCODING_7BIT:
|
||||
sb.append("7-bit GSM Alphabet");
|
||||
break;
|
||||
case PduUtils.DCS_ENCODING_8BIT:
|
||||
sb.append("8-bit encoding");
|
||||
break;
|
||||
case PduUtils.DCS_ENCODING_UCS2:
|
||||
sb.append("UCS2 encoding");
|
||||
break;
|
||||
}
|
||||
// are flash messages are only applicable to general coding group?
|
||||
if ((pdu.getDataCodingScheme() & ~PduUtils.DCS_CODING_GROUP_GENERAL) == 0) {
|
||||
switch (PduUtils.extractDcsClass(pdu.getDataCodingScheme())) {
|
||||
case PduUtils.DCS_MESSAGE_CLASS_FLASH:
|
||||
sb.append(", (Flash Message)");
|
||||
break;
|
||||
case PduUtils.DCS_MESSAGE_CLASS_ME:
|
||||
sb.append(", (Class1 ME Message)");
|
||||
break;
|
||||
case PduUtils.DCS_MESSAGE_CLASS_SIM:
|
||||
sb.append(", (Class2 SIM Message)");
|
||||
break;
|
||||
case PduUtils.DCS_MESSAGE_CLASS_TE:
|
||||
sb.append(", (Class3 TE Message)");
|
||||
break;
|
||||
}
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public static byte[] encode8bitUserData(String text) {
|
||||
try {
|
||||
return text.getBytes("ISO8859_1");
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
throw new UnrecoverableSmslibException("Cannot encode user data", e);
|
||||
}
|
||||
}
|
||||
|
||||
public static byte[] encodeUcs2UserData(String text) {
|
||||
try {
|
||||
// UTF-16 Big-Endian, no Byte Order Marker at start
|
||||
return text.getBytes("UTF-16BE");
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
throw new UnrecoverableSmslibException("Cannot encode user data", e);
|
||||
}
|
||||
}
|
||||
|
||||
public static byte[] encode7bitUserData(byte @Nullable [] udhOctets, byte[] textSeptets) {
|
||||
// UDH octets and text have to be encoded together in a single pass
|
||||
// UDH octets will need to be converted to unencoded septets in order
|
||||
// to properly pad the data
|
||||
if (udhOctets == null) {
|
||||
// convert string to uncompressed septets
|
||||
return unencodedSeptetsToEncodedSeptets(textSeptets);
|
||||
}
|
||||
// convert UDH octets as if they were encoded septets
|
||||
// NOTE: DO NOT DISCARD THE LAST SEPTET IF IT IS ZERO
|
||||
byte[] udhSeptets = PduUtils.encodedSeptetsToUnencodedSeptets(udhOctets, false);
|
||||
// combine the two arrays and encode them as a whole
|
||||
byte[] combined = new byte[udhSeptets.length + textSeptets.length];
|
||||
System.arraycopy(udhSeptets, 0, combined, 0, udhSeptets.length);
|
||||
System.arraycopy(textSeptets, 0, combined, udhSeptets.length, textSeptets.length);
|
||||
// convert encoded byte[] to a PDU string
|
||||
return unencodedSeptetsToEncodedSeptets(combined);
|
||||
}
|
||||
|
||||
public static String decode8bitEncoding(byte @Nullable [] udhData, byte[] pduData) {
|
||||
// standard 8-bit characters
|
||||
try {
|
||||
int udhLength = ((udhData == null) ? 0 : udhData.length);
|
||||
return new String(pduData, udhLength, pduData.length - udhLength, "ISO8859_1");
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
throw new UnrecoverableSmslibException("Cannot decode user data", e);
|
||||
}
|
||||
}
|
||||
|
||||
public static String decodeUcs2Encoding(byte @Nullable [] udhData, byte[] pduData) {
|
||||
try {
|
||||
int udhLength = ((udhData == null) ? 0 : udhData.length);
|
||||
// standard unicode
|
||||
return new String(pduData, udhLength, pduData.length - udhLength, "UTF-16");
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
throw new UnrecoverableSmslibException("Cannot decode user data", e);
|
||||
}
|
||||
}
|
||||
|
||||
public static byte swapNibbles(int b) {
|
||||
return (byte) (((b << 4) & 0xF0) | ((b >>> 4) & 0x0F));
|
||||
}
|
||||
|
||||
public static String readBCDNumbers(int numDigits, byte[] addressData) {
|
||||
// reads length BCD numbers from the current position
|
||||
StringBuffer sb = new StringBuffer();
|
||||
for (int i = 0; i < addressData.length; i++) {
|
||||
int b = addressData[i];
|
||||
int num1 = b & 0x0F;
|
||||
sb.append(num1);
|
||||
int num2 = (b >>> 4) & 0x0F;
|
||||
if (num2 != 0x0F) {
|
||||
// check if fillbits
|
||||
sb.append(num2);
|
||||
}
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public static int createSwappedBCD(int decimal) {
|
||||
// creates a swapped BCD representation of a 2-digit decimal
|
||||
int tens = (decimal & 0xFF) / 10;
|
||||
int ones = (decimal & 0xFF) - (tens * 10);
|
||||
return (ones << 4) | tens;
|
||||
}
|
||||
|
||||
// from Java String to uncompressed septets (GSM characters)
|
||||
public static byte[] stringToUnencodedSeptets(String s) {
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
int i, j, index;
|
||||
char ch;
|
||||
String myS = s;
|
||||
myS = myS.replace('\u00C7', // LATIN CAPITAL LETTER C WITH CEDILLA
|
||||
'\u00E7' // LATIN SMALL LETTER C WITH CEDILLA
|
||||
);
|
||||
for (i = 0; i < myS.length(); i++) {
|
||||
ch = myS.charAt(i);
|
||||
index = -1;
|
||||
for (j = 0; j < extAlphabet.length; j++) {
|
||||
if (extAlphabet[j] == ch) {
|
||||
index = j;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (index != -1) // An extended char...
|
||||
{
|
||||
baos.write((byte) Integer.parseInt(extBytes[index].substring(0, 2), 16));
|
||||
baos.write((byte) Integer.parseInt(extBytes[index].substring(2, 4), 16));
|
||||
} else
|
||||
// Maybe a standard char...
|
||||
{
|
||||
index = -1;
|
||||
for (j = 0; j < stdAlphabet.length; j++) {
|
||||
if (stdAlphabet[j] == ch) {
|
||||
index = j;
|
||||
baos.write((byte) j);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (index == -1) // Maybe a Greek Char...
|
||||
{
|
||||
for (j = 0; j < grcAlphabetRemapping.length; j++) {
|
||||
if (grcAlphabetRemapping[j][0] == ch) {
|
||||
index = j;
|
||||
ch = grcAlphabetRemapping[j][1];
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (index != -1) {
|
||||
for (j = 0; j < stdAlphabet.length; j++) {
|
||||
if (stdAlphabet[j] == ch) {
|
||||
index = j;
|
||||
baos.write((byte) j);
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else
|
||||
// Unknown char replacement...
|
||||
{
|
||||
baos.write((byte) ' ');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return baos.toByteArray();
|
||||
}
|
||||
|
||||
// from compress unencoded septets
|
||||
public static byte[] unencodedSeptetsToEncodedSeptets(byte[] septetBytes) {
|
||||
byte[] txtBytes;
|
||||
byte[] txtSeptets;
|
||||
int txtBytesLen;
|
||||
BitSet bits;
|
||||
int i, j;
|
||||
txtBytes = septetBytes;
|
||||
txtBytesLen = txtBytes.length;
|
||||
bits = new BitSet();
|
||||
for (i = 0; i < txtBytesLen; i++) {
|
||||
for (j = 0; j < 7; j++) {
|
||||
if ((txtBytes[i] & (1 << j)) != 0) {
|
||||
bits.set((i * 7) + j);
|
||||
}
|
||||
}
|
||||
}
|
||||
// big diff here
|
||||
int encodedSeptetByteArrayLength = txtBytesLen * 7 / 8 + ((txtBytesLen * 7 % 8 != 0) ? 1 : 0);
|
||||
txtSeptets = new byte[encodedSeptetByteArrayLength];
|
||||
for (i = 0; i < encodedSeptetByteArrayLength; i++) {
|
||||
for (j = 0; j < 8; j++) {
|
||||
txtSeptets[i] |= (byte) ((bits.get((i * 8) + j) ? 1 : 0) << j);
|
||||
}
|
||||
}
|
||||
return txtSeptets;
|
||||
}
|
||||
|
||||
// from GSM characters to java string
|
||||
public static String unencodedSeptetsToString(byte[] bytes) {
|
||||
StringBuffer text;
|
||||
String extChar;
|
||||
int i, j;
|
||||
text = new StringBuffer();
|
||||
for (i = 0; i < bytes.length; i++) {
|
||||
if (bytes[i] == 0x1b) {
|
||||
// NOTE: - ++i can be a problem if the '1b'
|
||||
// is right at the end of a PDU
|
||||
// - this will be an issue for displaying
|
||||
// partial PDUs e.g. via toString()
|
||||
if (i < bytes.length - 1) {
|
||||
extChar = "1b" + Integer.toHexString(bytes[++i]);
|
||||
for (j = 0; j < extBytes.length; j++) {
|
||||
if (extBytes[j].equalsIgnoreCase(extChar)) {
|
||||
text.append(extAlphabet[j]);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
text.append(stdAlphabet[bytes[i]]);
|
||||
}
|
||||
}
|
||||
return text.toString();
|
||||
}
|
||||
|
||||
public static int getNumSeptetsForOctets(int numOctets) {
|
||||
return numOctets * 8 / 7 + ((numOctets * 8 % 7 != 0) ? 1 : 0);
|
||||
// return numOctets + (numOctets/7);
|
||||
}
|
||||
|
||||
// decompress encoded septets to unencoded form
|
||||
public static byte[] encodedSeptetsToUnencodedSeptets(byte[] octetBytes) {
|
||||
return encodedSeptetsToUnencodedSeptets(octetBytes, true);
|
||||
}
|
||||
|
||||
public static byte[] encodedSeptetsToUnencodedSeptets(byte[] octetBytes, boolean discardLast) {
|
||||
byte newBytes[];
|
||||
BitSet bitSet;
|
||||
int i, j, value1, value2;
|
||||
bitSet = new BitSet(octetBytes.length * 8);
|
||||
value1 = 0;
|
||||
for (i = 0; i < octetBytes.length; i++) {
|
||||
for (j = 0; j < 8; j++) {
|
||||
value1 = (i * 8) + j;
|
||||
if ((octetBytes[i] & (1 << j)) != 0) {
|
||||
bitSet.set(value1);
|
||||
}
|
||||
}
|
||||
}
|
||||
value1++;
|
||||
// this is a bit count NOT a byte count
|
||||
value2 = value1 / 7 + ((value1 % 7 != 0) ? 1 : 0); // big diff here
|
||||
// System.out.println(octetBytes.length);
|
||||
// System.out.println(value1+" --> "+value2);
|
||||
if (value2 == 0) {
|
||||
value2++;
|
||||
}
|
||||
newBytes = new byte[value2];
|
||||
for (i = 0; i < value2; i++) {
|
||||
for (j = 0; j < 7; j++) {
|
||||
if ((value1 + 1) > (i * 7 + j)) {
|
||||
if (bitSet.get(i * 7 + j)) {
|
||||
newBytes[i] |= (byte) (1 << j);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (discardLast && octetBytes.length * 8 % 7 > 0) {
|
||||
// when decoding a 7bit encoded string
|
||||
// the last septet may become 0, this should be discarded
|
||||
// since this is an artifact of the encoding not part of the
|
||||
// original string
|
||||
// this is only done for decoding 7bit encoded text NOT for
|
||||
// reversing octets to septets (e.g. for the encoding the UDH)
|
||||
if (newBytes[newBytes.length - 1] == 0) {
|
||||
byte[] retVal = new byte[newBytes.length - 1];
|
||||
System.arraycopy(newBytes, 0, retVal, 0, retVal.length);
|
||||
return retVal;
|
||||
}
|
||||
}
|
||||
return newBytes;
|
||||
}
|
||||
|
||||
// converts a PDU style string to a byte array
|
||||
public static byte[] pduToBytes(String s) {
|
||||
byte[] bytes = new byte[s.length() / 2];
|
||||
for (int i = 0; i < s.length(); i += 2) {
|
||||
bytes[i / 2] = (byte) (Integer.parseInt(s.substring(i, i + 2), 16));
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
|
||||
// converts a byte array to PDU style string
|
||||
public static String bytesToPdu(byte[] bytes) {
|
||||
StringBuffer sb = new StringBuffer();
|
||||
for (int i = 0; i < bytes.length; i++) {
|
||||
sb.append(byteToPdu(bytes[i] & 0xFF));
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public static String byteToBits(byte b) {
|
||||
String bits = Integer.toBinaryString(b & 0xFF);
|
||||
while (bits.length() < 8) {
|
||||
bits = "0" + bits;
|
||||
}
|
||||
return bits;
|
||||
}
|
||||
|
||||
public static String byteToPdu(int b) {
|
||||
StringBuffer sb = new StringBuffer();
|
||||
String s = Integer.toHexString(b & 0xFF);
|
||||
if (s.length() == 1) {
|
||||
sb.append("0");
|
||||
}
|
||||
sb.append(s);
|
||||
return sb.toString().toUpperCase();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package org.smslib.pduUtils.gsm3040;
|
||||
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
//PduUtils Library - A Java library for generating GSM 3040 Protocol Data Units (PDUs)
|
||||
//
|
||||
//Copyright (C) 2008, Ateneo Java Wireless Competency Center/Blueblade Technologies, Philippines.
|
||||
//PduUtils is distributed under the terms of the Apache License version 2.0
|
||||
//
|
||||
//Licensed under the Apache License, Version 2.0 (the "License");
|
||||
//you may not use this file except in compliance with the License.
|
||||
//You may obtain a copy of the License at
|
||||
//
|
||||
//http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
//Unless required by applicable law or agreed to in writing, software
|
||||
//distributed under the License is distributed on an "AS IS" BASIS,
|
||||
//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//See the License for the specific language governing permissions and
|
||||
//limitations under the License.
|
||||
|
||||
/**
|
||||
* Extracted from SMSLib
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class SmsDeliveryPdu extends Pdu {
|
||||
// can only create via the factory
|
||||
SmsDeliveryPdu() {
|
||||
}
|
||||
|
||||
// ==================================================
|
||||
// TIMESTAMP
|
||||
// ==================================================
|
||||
private @Nullable Calendar timestamp;
|
||||
|
||||
public void setTimestamp(Calendar timestamp) {
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
|
||||
public @Nullable Date getTimestamp() {
|
||||
Calendar timestampFinal = this.timestamp;
|
||||
return timestampFinal == null ? null : timestampFinal.getTime();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
package org.smslib.pduUtils.gsm3040;
|
||||
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
//PduUtils Library - A Java library for generating GSM 3040 Protocol Data Units (PDUs)
|
||||
//
|
||||
//Copyright (C) 2008, Ateneo Java Wireless Competency Center/Blueblade Technologies, Philippines.
|
||||
//PduUtils is distributed under the terms of the Apache License version 2.0
|
||||
//
|
||||
//Licensed under the Apache License, Version 2.0 (the "License");
|
||||
//you may not use this file except in compliance with the License.
|
||||
//You may obtain a copy of the License at
|
||||
//
|
||||
//http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
//Unless required by applicable law or agreed to in writing, software
|
||||
//distributed under the License is distributed on an "AS IS" BASIS,
|
||||
//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//See the License for the specific language governing permissions and
|
||||
//limitations under the License.
|
||||
|
||||
/**
|
||||
* Extracted from SMSLib
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class SmsStatusReportPdu extends Pdu {
|
||||
// can only create via the factory
|
||||
SmsStatusReportPdu() {
|
||||
}
|
||||
|
||||
// ==================================================
|
||||
// MESSAGE REFERENCE
|
||||
// ==================================================
|
||||
// usually just 0x00 to let MC supply
|
||||
private int messageReference = 0x00;
|
||||
|
||||
public void setMessageReference(int reference) {
|
||||
this.messageReference = reference;
|
||||
}
|
||||
|
||||
public int getMessageReference() {
|
||||
return this.messageReference;
|
||||
}
|
||||
|
||||
// ==================================================
|
||||
// STATUS
|
||||
// ==================================================
|
||||
private int status = 0x00;
|
||||
|
||||
public void setStatus(int status) {
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
public int getStatus() {
|
||||
return this.status;
|
||||
}
|
||||
|
||||
// ==================================================
|
||||
// TIMESTAMP
|
||||
// ==================================================
|
||||
private @Nullable Calendar timestamp;
|
||||
|
||||
public void setTimestamp(Calendar timestamp) {
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
|
||||
public @Nullable Date getTimestamp() {
|
||||
Calendar timestampFinal = this.timestamp;
|
||||
return timestampFinal == null ? null : timestampFinal.getTime();
|
||||
}
|
||||
|
||||
// ==================================================
|
||||
// DISCHARGE TIME
|
||||
// ==================================================
|
||||
private @Nullable Calendar dischargeTime;
|
||||
|
||||
public void setDischargeTime(Calendar myDischargeTime) {
|
||||
this.dischargeTime = myDischargeTime;
|
||||
}
|
||||
|
||||
public @Nullable Date getDischargeTime() {
|
||||
Calendar dischargeTimeFinal = this.dischargeTime;
|
||||
return dischargeTimeFinal == null ? null : dischargeTimeFinal.getTime();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
package org.smslib.pduUtils.gsm3040;
|
||||
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
//PduUtils Library - A Java library for generating GSM 3040 Protocol Data Units (PDUs)
|
||||
//
|
||||
//Copyright (C) 2008, Ateneo Java Wireless Competency Center/Blueblade Technologies, Philippines.
|
||||
//PduUtils is distributed under the terms of the Apache License version 2.0
|
||||
//
|
||||
//Licensed under the Apache License, Version 2.0 (the "License");
|
||||
//you may not use this file except in compliance with the License.
|
||||
//You may obtain a copy of the License at
|
||||
//
|
||||
//http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
//Unless required by applicable law or agreed to in writing, software
|
||||
//distributed under the License is distributed on an "AS IS" BASIS,
|
||||
//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//See the License for the specific language governing permissions and
|
||||
//limitations under the License.
|
||||
|
||||
/**
|
||||
* Extracted from SMSLib
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class SmsSubmitPdu extends Pdu {
|
||||
// ==================================================
|
||||
// FIRST OCTET UTILITIES
|
||||
// ==================================================
|
||||
|
||||
public int getTpVpf() {
|
||||
return getFirstOctetField(PduUtils.TP_VPF_MASK);
|
||||
}
|
||||
|
||||
// ==================================================
|
||||
// MESSAGE REFERENCE
|
||||
// ==================================================
|
||||
// usually just 0x00 to let MC supply
|
||||
private int messageReference = 0x00;
|
||||
|
||||
public void setMessageReference(int reference) {
|
||||
this.messageReference = reference;
|
||||
}
|
||||
|
||||
public int getMessageReference() {
|
||||
return this.messageReference;
|
||||
}
|
||||
|
||||
// ==================================================
|
||||
// VALIDITY PERIOD
|
||||
// ==================================================
|
||||
// which one is used depends of validity period format (TP-VPF)
|
||||
private int validityPeriod = -1;
|
||||
|
||||
@Nullable
|
||||
private Calendar validityPeriodTimeStamp;
|
||||
|
||||
public int getValidityPeriod() {
|
||||
return this.validityPeriod;
|
||||
}
|
||||
|
||||
public void setValidityPeriod(int validityPeriod) {
|
||||
this.validityPeriod = validityPeriod;
|
||||
}
|
||||
|
||||
public void setValidityTimestamp(Calendar date) {
|
||||
this.validityPeriodTimeStamp = date;
|
||||
}
|
||||
|
||||
public @Nullable Date getValidityDate() {
|
||||
Calendar validityPeriodTimeStampFinal = this.validityPeriodTimeStamp;
|
||||
return validityPeriodTimeStampFinal == null ? null : validityPeriodTimeStampFinal.getTime();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,188 @@
|
||||
package org.smslib.pduUtils.gsm3040.ie;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.smslib.UnrecoverableSmslibException;
|
||||
|
||||
//PduUtils Library - A Java library for generating GSM 3040 Protocol Data Units (PDUs)
|
||||
//
|
||||
//Copyright (C) 2008, Ateneo Java Wireless Competency Center/Blueblade Technologies, Philippines.
|
||||
//PduUtils is distributed under the terms of the Apache License version 2.0
|
||||
//
|
||||
//Licensed under the Apache License, Version 2.0 (the "License");
|
||||
//you may not use this file except in compliance with the License.
|
||||
//You may obtain a copy of the License at
|
||||
//
|
||||
//http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
//Unless required by applicable law or agreed to in writing, software
|
||||
//distributed under the License is distributed on an "AS IS" BASIS,
|
||||
//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//See the License for the specific language governing permissions and
|
||||
//limitations under the License.
|
||||
|
||||
/**
|
||||
* Extracted from SMSLib
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ConcatInformationElement extends InformationElement {
|
||||
|
||||
private static final int CONCAT_IE_LENGTH_8BIT = 5;
|
||||
|
||||
public static final int CONCAT_8BIT_REF = 0x00;
|
||||
|
||||
public static final int CONCAT_16BIT_REF = 0x08;
|
||||
|
||||
private static int defaultConcatType = CONCAT_8BIT_REF;
|
||||
|
||||
private static int defaultConcatLength = CONCAT_IE_LENGTH_8BIT;
|
||||
|
||||
public static int getDefaultConcatLength() {
|
||||
return defaultConcatLength;
|
||||
}
|
||||
|
||||
public static int getDefaultConcatType() {
|
||||
return defaultConcatType;
|
||||
}
|
||||
|
||||
ConcatInformationElement(byte identifier, byte[] data) {
|
||||
super(identifier, data);
|
||||
if (getIdentifier() == CONCAT_8BIT_REF) {
|
||||
// iei
|
||||
// iel
|
||||
// ref
|
||||
// max
|
||||
// seq
|
||||
if (data.length != 3) {
|
||||
throw new IllegalArgumentException("Invalid data length in: " + getClass().getSimpleName());
|
||||
}
|
||||
} else if (getIdentifier() == CONCAT_16BIT_REF) {
|
||||
// iei
|
||||
// iel
|
||||
// ref(2 bytes)
|
||||
// max
|
||||
// seq
|
||||
if (data.length != 4) {
|
||||
throw new IllegalArgumentException("Invalid data length in: " + getClass().getSimpleName());
|
||||
}
|
||||
} else {
|
||||
throw new IllegalArgumentException("Invalid identifier in data in: " + getClass().getSimpleName());
|
||||
}
|
||||
validate();
|
||||
}
|
||||
|
||||
ConcatInformationElement(int identifier, int mpRefNo, int mpMaxNo, int mpSeqNo) {
|
||||
super((byte) (identifier & 0xFF), getData(identifier, mpRefNo, mpMaxNo, mpSeqNo));
|
||||
validate();
|
||||
}
|
||||
|
||||
private static byte[] getData(int identifier, int mpRefNo, int mpMaxNo, int mpSeqNo) {
|
||||
byte[] data = null;
|
||||
switch (identifier) {
|
||||
case CONCAT_8BIT_REF:
|
||||
data = new byte[3];
|
||||
data[0] = (byte) (mpRefNo & 0xFF);
|
||||
data[1] = (byte) (mpMaxNo & 0xFF);
|
||||
data[2] = (byte) (mpSeqNo & 0xFF);
|
||||
break;
|
||||
case CONCAT_16BIT_REF:
|
||||
data = new byte[4];
|
||||
data[0] = (byte) ((mpRefNo & 0xFF00) >>> 8);
|
||||
data[1] = (byte) (mpRefNo & 0xFF);
|
||||
data[2] = (byte) (mpMaxNo & 0xFF);
|
||||
data[3] = (byte) (mpSeqNo & 0xFF);
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Invalid identifier for ConcatInformationElement");
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
public int getMpRefNo() {
|
||||
// this is 8-bit in 0x00 and 16-bit in 0x08
|
||||
byte[] data = getData();
|
||||
if (getIdentifier() == CONCAT_8BIT_REF) {
|
||||
return (data[0] & (0xFF));
|
||||
} else if (getIdentifier() == CONCAT_16BIT_REF) {
|
||||
return ((data[0] << 8) | data[1]) & (0xFFFF);
|
||||
}
|
||||
throw new UnrecoverableSmslibException("Invalid identifier");
|
||||
}
|
||||
|
||||
public void setMpRefNo(int mpRefNo) {
|
||||
// this is 8-bit in 0x00 and 16-bit in 0x08
|
||||
byte[] data = getData();
|
||||
if (getIdentifier() == CONCAT_8BIT_REF) {
|
||||
data[0] = (byte) (mpRefNo & (0xFF));
|
||||
} else if (getIdentifier() == CONCAT_16BIT_REF) {
|
||||
data[0] = (byte) ((mpRefNo >>> 8) & (0xFF));
|
||||
data[1] = (byte) ((mpRefNo) & (0xFF));
|
||||
} else {
|
||||
throw new UnrecoverableSmslibException("Invalid identifier");
|
||||
}
|
||||
}
|
||||
|
||||
public int getMpMaxNo() {
|
||||
byte[] data = getData();
|
||||
if (getIdentifier() == CONCAT_8BIT_REF) {
|
||||
return (data[1] & (0xFF));
|
||||
} else if (getIdentifier() == CONCAT_16BIT_REF) {
|
||||
return (data[2] & (0xFF));
|
||||
}
|
||||
throw new UnrecoverableSmslibException("Invalid identifier");
|
||||
}
|
||||
|
||||
public void setMpMaxNo(int mpMaxNo) {
|
||||
byte[] data = getData();
|
||||
if (getIdentifier() == CONCAT_8BIT_REF) {
|
||||
data[1] = (byte) (mpMaxNo & 0xFF);
|
||||
} else if (getIdentifier() == CONCAT_16BIT_REF) {
|
||||
data[2] = (byte) (mpMaxNo & 0xFF);
|
||||
} else {
|
||||
throw new UnrecoverableSmslibException("Invalid identifier");
|
||||
}
|
||||
}
|
||||
|
||||
public int getMpSeqNo() {
|
||||
byte[] data = getData();
|
||||
if (getIdentifier() == CONCAT_8BIT_REF) {
|
||||
return (data[2] & (0xFF));
|
||||
} else if (getIdentifier() == CONCAT_16BIT_REF) {
|
||||
return (data[3] & (0xFF));
|
||||
}
|
||||
throw new UnrecoverableSmslibException("Invalid identifier");
|
||||
}
|
||||
|
||||
public void setMpSeqNo(int mpSeqNo) {
|
||||
byte[] data = getData();
|
||||
if (getIdentifier() == CONCAT_8BIT_REF) {
|
||||
data[2] = (byte) (mpSeqNo & (0xFF));
|
||||
} else if (getIdentifier() == CONCAT_16BIT_REF) {
|
||||
data[3] = (byte) (mpSeqNo & (0xFF));
|
||||
} else {
|
||||
throw new UnrecoverableSmslibException("Invalid identifier");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuffer sb = new StringBuffer();
|
||||
sb.append(super.toString());
|
||||
sb.append("[MpRefNo: ");
|
||||
sb.append(getMpRefNo());
|
||||
sb.append(", MpMaxNo: ");
|
||||
sb.append(getMpMaxNo());
|
||||
sb.append(", MpSeqNo: ");
|
||||
sb.append(getMpSeqNo());
|
||||
sb.append("]");
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private void validate() {
|
||||
if (getMpMaxNo() == 0) {
|
||||
throw new IllegalArgumentException("mpMaxNo must be > 0");
|
||||
}
|
||||
if (getMpSeqNo() == 0) {
|
||||
throw new IllegalArgumentException("mpSeqNo must be > 0");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
package org.smslib.pduUtils.gsm3040.ie;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.smslib.pduUtils.gsm3040.PduUtils;
|
||||
|
||||
//PduUtils Library - A Java library for generating GSM 3040 Protocol Data Units (PDUs)
|
||||
//
|
||||
//Copyright (C) 2008, Ateneo Java Wireless Competency Center/Blueblade Technologies, Philippines.
|
||||
//PduUtils is distributed under the terms of the Apache License version 2.0
|
||||
//
|
||||
//Licensed under the Apache License, Version 2.0 (the "License");
|
||||
//you may not use this file except in compliance with the License.
|
||||
//You may obtain a copy of the License at
|
||||
//
|
||||
//http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
//Unless required by applicable law or agreed to in writing, software
|
||||
//distributed under the License is distributed on an "AS IS" BASIS,
|
||||
//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//See the License for the specific language governing permissions and
|
||||
//limitations under the License.
|
||||
|
||||
/**
|
||||
* Extracted from SMSLib
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class InformationElement {
|
||||
private byte identifier;
|
||||
|
||||
private byte[] data;
|
||||
|
||||
// iei
|
||||
// iel (implicit length of data)
|
||||
// ied (raw ie data)
|
||||
InformationElement(byte id, byte[] ieData) {
|
||||
this.identifier = id;
|
||||
this.data = ieData;
|
||||
}
|
||||
|
||||
// for outgoing messages
|
||||
void initialize(byte id, byte[] ieData) {
|
||||
this.identifier = id;
|
||||
this.data = ieData;
|
||||
}
|
||||
|
||||
public int getIdentifier() {
|
||||
return (this.identifier & 0xFF);
|
||||
}
|
||||
|
||||
public int getLength() {
|
||||
return this.data.length;
|
||||
}
|
||||
|
||||
public byte[] getData() {
|
||||
return this.data;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuffer sb = new StringBuffer();
|
||||
sb.append(getClass().getSimpleName() + "[");
|
||||
sb.append(PduUtils.byteToPdu(this.identifier));
|
||||
sb.append(", ");
|
||||
sb.append(PduUtils.byteToPdu(this.data.length));
|
||||
sb.append(", ");
|
||||
sb.append(PduUtils.bytesToPdu(this.data));
|
||||
sb.append("]");
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package org.smslib.pduUtils.gsm3040.ie;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
//PduUtils Library - A Java library for generating GSM 3040 Protocol Data Units (PDUs)
|
||||
//
|
||||
//Copyright (C) 2008, Ateneo Java Wireless Competency Center/Blueblade Technologies, Philippines.
|
||||
//PduUtils is distributed under the terms of the Apache License version 2.0
|
||||
//
|
||||
//Licensed under the Apache License, Version 2.0 (the "License");
|
||||
//you may not use this file except in compliance with the License.
|
||||
//You may obtain a copy of the License at
|
||||
//
|
||||
//http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
//Unless required by applicable law or agreed to in writing, software
|
||||
//distributed under the License is distributed on an "AS IS" BASIS,
|
||||
//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//See the License for the specific language governing permissions and
|
||||
//limitations under the License.
|
||||
|
||||
/**
|
||||
* Extracted from SMSLib
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class InformationElementFactory {
|
||||
// used to determine what InformationElement to use based on bytes from a UDH
|
||||
// assumes the supplied bytes are correct
|
||||
public static InformationElement createInformationElement(int id, byte[] data) {
|
||||
byte iei = (byte) (id & 0xFF);
|
||||
switch (iei) {
|
||||
case ConcatInformationElement.CONCAT_8BIT_REF:
|
||||
case ConcatInformationElement.CONCAT_16BIT_REF:
|
||||
return new ConcatInformationElement(iei, data);
|
||||
case PortInformationElement.PORT_16BIT:
|
||||
return new PortInformationElement(iei, data);
|
||||
default:
|
||||
return new InformationElement(iei, data);
|
||||
}
|
||||
}
|
||||
|
||||
public static ConcatInformationElement generateConcatInfo(int mpRefNo, int partNo) {
|
||||
ConcatInformationElement concatInfo = new ConcatInformationElement(
|
||||
ConcatInformationElement.getDefaultConcatType(), mpRefNo, 1, partNo);
|
||||
return concatInfo;
|
||||
}
|
||||
|
||||
public static PortInformationElement generatePortInfo(int destPort, int srcPort) {
|
||||
PortInformationElement portInfo = new PortInformationElement(PortInformationElement.PORT_16BIT, destPort,
|
||||
srcPort);
|
||||
return portInfo;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
package org.smslib.pduUtils.gsm3040.ie;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
//PduUtils Library - A Java library for generating GSM 3040 Protocol Data Units (PDUs)
|
||||
//
|
||||
//Copyright (C) 2008, Ateneo Java Wireless Competency Center/Blueblade Technologies, Philippines.
|
||||
//PduUtils is distributed under the terms of the Apache License version 2.0
|
||||
//
|
||||
//Licensed under the Apache License, Version 2.0 (the "License");
|
||||
//you may not use this file except in compliance with the License.
|
||||
//You may obtain a copy of the License at
|
||||
//
|
||||
//http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
//Unless required by applicable law or agreed to in writing, software
|
||||
//distributed under the License is distributed on an "AS IS" BASIS,
|
||||
//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//See the License for the specific language governing permissions and
|
||||
//limitations under the License.
|
||||
|
||||
/**
|
||||
* Extracted from SMSLib
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class PortInformationElement extends InformationElement {
|
||||
public static final int PORT_16BIT = 0x05;
|
||||
|
||||
PortInformationElement(byte id, byte[] data) {
|
||||
super(id, data);
|
||||
if (getIdentifier() != PORT_16BIT) {
|
||||
throw new IllegalArgumentException(
|
||||
"Invalid identifier " + getIdentifier() + " in data in: " + getClass().getSimpleName());
|
||||
}
|
||||
// iei
|
||||
// iel
|
||||
// dest(2 bytes)
|
||||
// src (2 bytes)
|
||||
if (data.length != 4) {
|
||||
throw new IllegalArgumentException("Invalid data length in: " + getClass().getSimpleName());
|
||||
}
|
||||
}
|
||||
|
||||
PortInformationElement(int identifier, int destPort, int srcPort) {
|
||||
super((byte) (identifier & 0xFF), getData(identifier, destPort, srcPort));
|
||||
}
|
||||
|
||||
private static byte[] getData(int identifier, int destPort, int srcPort) {
|
||||
byte[] data = null;
|
||||
switch (identifier) {
|
||||
case PORT_16BIT:
|
||||
data = new byte[4];
|
||||
data[0] = (byte) ((destPort & 0xFF00) >>> 8);
|
||||
data[1] = (byte) (destPort & 0xFF);
|
||||
data[2] = (byte) ((srcPort & 0xFF00) >>> 8);
|
||||
data[3] = (byte) (srcPort & 0xFF);
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Invalid identifier for PortInformationElement");
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
public int getDestPort() {
|
||||
// first 2 bytes of data
|
||||
byte[] data = getData();
|
||||
return (((data[0] & 0xFF) << 8) | (data[1] & 0xFF));
|
||||
}
|
||||
|
||||
public int getSrcPort() {
|
||||
// next 2 bytes of data
|
||||
byte[] data = getData();
|
||||
return (((data[2] & 0xFF) << 8) | (data[3] & 0xFF));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuffer sb = new StringBuffer();
|
||||
sb.append(super.toString());
|
||||
sb.append("[Dst Port: ");
|
||||
sb.append(getDestPort());
|
||||
sb.append(", Src Port: ");
|
||||
sb.append(getSrcPort());
|
||||
sb.append("]");
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user