PJSUA2: Contact header uri length limit - android

I'm building an android VOIP application with push notifications support, based on PJSUA2.
I need to send the push notification (FCM) token to the server (Asterisk in my case) as contact uri parameter, so that I can retrieve it with a script from the server and send notification to wake up client before sending an incoming call request.
I put the parameters in the contact uri parameters with
acfg.getSipConfig().setContactUriParams(buildParams(contactParameters));
contactParams is a HashMap<String, String> with parameters name and value, while buildParams is the following method:
private String buildParams(Map<String, String> params) {
StringBuilder builder = new StringBuilder();
for (String k : params.keySet()) {
builder.append(';');
builder.append(k);
String v = params.get(k);
if (v != null && v.trim().length() > 0) {
builder.append("=\"");
builder.append(v);
builder.append('\"');
}
}
return builder.toString();
}
without FCM parameters everything works well, but
building the contact uri with the following parameters
;pn-provider="fcm";pn-tok="LONG FCM TOKEN"
makes the calls hangup after 32 seconds (see question PJSUA2 Android - Incoming calls drop after 32 seconds)
removing ;pn-provider="fcm" works
sending just a portion of the token works (in pn-tok, together with the pn-provider parameter)
I thought it may be a "invalid characters issue", but it actually seems to be a "max length issue".
Is there a Contact header max length or a URI max length for Contact header? If yes, is it a PJSIP limit or a SIP limit?

Related

Writing data to an NFC tag that does not fit

My Android app at the moment consumes a URL for a Google spreadsheet through pasting from the clipboard, reading a QR code, or reading from NFC. I'm having trouble writing to an NFC tag and I get this error:
[ERROR:nfa_rw_act.cc(1571)] Unable to write NDEF. Tag maxsize=137, request write size=171
I cannot write to this tag because the payload I'm trying to write it is larger than the writable space on it.
All I'm trying to do is write the URL I've already read (from clipboard or QR) to an NFC tag, and also add my app record so it launches the Play Store to my app in case the user doesn't have it installed already. Unfortunately, it seems this is too much data. I thought about maybe only including the spreadsheetID value, but I think this will add complications in the future when I inevitably want to add support for spreadsheets outside of Google Sheets.
Here's how I'm writing it currently:
public NdefMessage createNdefMessage() {
String text = "https://docs.google.com/spreadsheets/d/1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms/edit?usp=sharing";
NdefRecord appRecord = NdefRecord.createApplicationRecord(context.getPackageName());
NdefRecord relayRecord = new NdefRecord(NdefRecord.TNF_MIME_MEDIA, new String("application/" + context.getPackageName()).getBytes(Charset.forName("US-ASCII")), null, text.getBytes());
return new NdefMessage(new NdefRecord[] {relayRecord, appRecord});
}
Is writing just the ID ("1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms" in this case) my only option? I'm also assuming the NFC tag I'm using is an average size and not a super tiny one for 2018.
EDIT:
Thanks to Michael I was able to get it to fit, though barely (134/137 bytes). I write the URI to NFC via this:
NdefRecord relayRecord = NdefRecord.createUri(text);
I added this intent filter to catch it:
<intent-filter>
<action android:name="android.nfc.action.NDEF_DISCOVERED"/>
<category android:name="android.intent.category.DEFAULT"/>
<data android:scheme="https" android:host="docs.google.com"/>
</intent-filter>
And I read the NFC tag with this:
NdefMessage[] messages = new NdefMessage[rawMessages.length];
for (int i = 0; i < rawMessages.length; i++) {
messages[i] = (NdefMessage) rawMessages[i];
}
for (NdefRecord r : messages[0].getRecords()) {
if (r.getTnf() == NdefRecord.TNF_WELL_KNOWN) {
byte[] payload = r.getPayload();
try {
String payloadText = new String(payload, 1, payload.length - 1, "UTF-8");
int firstByte = payload[0];
return getUriPrefix(firstByte) + payloadText;
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
return "Read error";
}
}
}
And the first byte from the URI compression I get via this, even though I only ever assign "04" (4 when I read it as int) in my app:
private String getUriPrefix(int firstByte) {
if (firstByte == 0) {
return "";
} else if (firstByte == 1) {
return "http://www.";
} else if (firstByte == 2) {
return "https://www.";
} else if (firstByte == 3) {
return "http://";
} else if (firstByte == 4) {
return "https://";
} else {
return "";
}
}
The error message that you got in the log is pretty clear. Your NDEF message is too large to fit the data area of the tag. The obvious solution would be to use NFC tags with a sufficiently large storage capacity -- there's quite a few larger tags available. There's nothing else you could do about it if you want to store exactly that NDEF message.
However, there is ways to improve the NDEF message itself. You currently use a MIME type record to store a URL. That's definitely not the best choice of a record type to store a URL. (or actually any application-specific data). The MIME type that you chose costs ("application/" + context.getPackageName()).length() = 31 bytes (assuming your package name is 19 bytes).
If you used an NFC Forum external type record instead, you could create a much shorter type name of the form "mydomain.tld:appurl", so this would save quite a few bytes.
relayRecord = NdefRecord.createExternal("mydomain.tld", "appurl", text.getBytes());
Since you want to store a URL, there's even a more efficient (and readily available) record type: the NFC Forum URI well-known type. The type name of that record consists only of the single letter "U", so this would already save 30 bytes compared to your MIME type record. Moreover, the URI record type uses a compression scheme to further reduce the size of the record: there is a list of well-known URI prefixes (e.g. "https://") that can be represented as a single byte in the URI record (the method NdefRecord.createUri() will automatically take care of that compression). Consequently, you would save another 7 bytes.
relayRecord = NdefRecord.createUri(text);

Sending large amount of data from Android to MVC Application

I'm currently developing an android application that takes in a file and converts it into a string. The string is then sent over to a server and I'm using a MVC application in this case. The string has to be sent to a server first because I needed to do some processing before storing it. Below is my code for the MVC application.
[HttpPost]
public HttpResponseMessage RetrieveFile(String fileString)
{
var response = new HttpResponseMessage()
{
Content = new StringContent("{\"File\":\"" + fileString + "\"}")
};
response.Content.Headers.ContentType = new MediaTypeHeaderValue("application/json");
return response;
}
However, whenever I call the http://localhost/api/TestCon/RetrieveFile?fileString=*Very Long String* . it throws a "Request URL Too Long" error message. What do I have to do to send a large about of data, possibly a few MBs, over to the server?

IHE and HL7. PCD-01 ACK

I'm trying to get data from a monitor to an Android application and I've took the IHE - PCD-01 transaction as a model.
The scheme is simple, is based on achieve the interconnection between the monitor and the tablet, where the monitor sends constantly information and the application is listening.
But what I don't understand is if I need an ACK or not after every message. Does anyone can help me with this?
TL;DR yes, nothing special here, support the usual HL7 ACK/NACK driven by MSH-15, MSH-16 fields. ACK-ing everything by default is "better safe then sorry"
The document "IHE Patient Care Device (PCD), Technical Framework, Volume 2 (PCD TF-2) Transactions, Revision 1.0 - Final Text, August 12, 2011" available at http://www.ihe.net/technical_framework/upload/ihe_pcd_tf_vol2_ft_2011-08-12.pdf says
..The common static definition of the HL7 acknowledgement (ACK) message is described in Appendix G, "HL7 Implementation Notes"..
which says
G.1 Network Guidelines
The HL7 2.6 standard does not define a network communications protocol. Beginning with HL7 2.2, the definitions of lower layer protocols were moved to the Implementation Guide, but are not HL7 requirements. The IHE Framework makes these recommendations:
Applications shall use the Minimal Lower Layer Protocol defined in Appendix C of the HL7 Implementation Guide.
An application that wants to send a message (initiate a transaction) will initiate a network connection to start the transaction. The receiver application will respond with an acknowledgement or response to query but will not initiate new transactions on this network connection
G.1.1 Acknowledgment Modes
ACKNOWLEDGMENT MESSAGES
Acknowledgment messages may be defined on an application basis. However the simple general acknowledgment message (ACK) may be used where the application does not define a special message (application level acknowledgment) and in other cases as described in Section 2.9, "Message Processing Rules".
The IHE PCD transaction PCD-03 supports „enhanced mode‟ acknowledgements. See discussion under PCD-03 Transactions as well as in B.1 MSH – Message Header Segment and B.2 MSA – Message Acknowledgement Segment
and document "Health Level Seven, Version 2.6 © 2007, Chapter 2: Control" coming from the "HL7 Messaging Standard Version 2.6" package which can be downloaded from http://www.hl7.org/implement/standards/product_brief.cfm?product_id=185 describes the accept and validate behavior in
2.9.2 Message response using the original processing rules
..too long to quote..
2.9.3 Response using enhanced acknowledgement
..too long to quote..
depending on the values of MSH-15 Accept Acknowledgement Type and MSH-16 Application Acknowledgment Type fields in the HL7 message
The above chapters from the HL7 standard contain what you want to read and implement/support.
EDIT:
Simply put, in HL7 protocol in every message sent the sender may request an ACK receipt by flagging appropriate fields in the message header segment. IHE does not remove this rule and does not enforce any other but enables any other convention to be defined on an application basis. Correct expected behavior is defined by the HL7 specification and in order to get it right and create a conforming implementation (without hidden surprises for your 3rd parties) you may need to read it several times (see also Stack Overflow: How can I make my system HL7 certified?)
For example this is how HAPI library handles the ACKing, snippet comes from http://sourceforge.net/p/hl7api/code/764/tree/tags/Root_REL_1_2/hapi-mvn/hapi-base/src/main/java/ca/uhn/hl7v2/protocol/impl/ProcessorImpl.java
/**
* #see ca.uhn.hl7v2.protocol.Processor#cycle(boolean)
*/
public void cycle(boolean expectingAck) throws HL7Exception {
log.debug("In cycle({})", expectingAck);
cleanReservations();
cleanAcceptAcks();
cleanReservedMessages();
Transportable in = null;
try {
if (expectingAck) {
in = tryReceive(myContext.getLocallyDrivenTransportLayer());
} else {
in = tryReceive(myContext.getRemotelyDrivenTransportLayer());
}
} catch (TransportException e) {
try {
Thread.sleep(1000);
} catch (InterruptedException e1) {}
throw e;
}
// log
if (in != null) {
log.debug("Received message: {}", in.getMessage());
} else {
log.debug("Received no message");
}
// If we have a message, handle it
if (in != null) {
String acceptAckNeeded = null;
// String appAckNeeded = null;
String ackCode = null;
String ackId = null;
try {
String[] fieldPaths = {"MSH-15", "MSH-16", "MSA-1", "MSA-2"};
String[] fields = PreParser.getFields(in.getMessage(), fieldPaths);
acceptAckNeeded = fields[0];
// appAckNeeded = fields[1];
ackCode = fields[2];
ackId = fields[3];
} catch (HL7Exception e) {
log.warn("Failed to parse accept ack fields in incoming message", e);
}
if (ackId != null && ackCode != null && ackCode.startsWith("C")) {
long expiryTime = System.currentTimeMillis() + 1000 * 60;
myAcceptAcks.put(ackId, new ExpiringTransportable(in, expiryTime));
} else {
AcceptAcknowledger.AcceptACK ack = AcceptAcknowledger.validate(getContext(), in);
if ((acceptAckNeeded != null && acceptAckNeeded.equals(AL))
|| (acceptAckNeeded != null && acceptAckNeeded.equals(ER) && !ack.isAcceptable())
|| (acceptAckNeeded != null && acceptAckNeeded.equals(SU) && ack.isAcceptable())) {
trySend(myContext.getRemotelyDrivenTransportLayer(), ack.getMessage());
}
if (ack.isAcceptable()) {
if (isReserved(ackId)) {
log.debug("Received expected ACK message with ACK ID: {}", ackId);
removeReservation(ackId);
long expiryTime = System.currentTimeMillis() + 1000 * 60 * 5;
myAvailableMessages.put(ackId, new ExpiringTransportable(in, expiryTime));
} else {
log.debug("Sending message to router");
Transportable out = myContext.getRouter().processMessage(in);
sendAppResponse(out);
}
} else {
// TODO: should we do something more here? Might be nice to
// allow a configurable handler for this situation
log.warn("Incoming message was not acceptable");
}
}
} else {
String transport = expectingAck ? " Locally driven " : "Remotely driven";
log.debug("{} TransportLayer.receive() returned null.", transport);
}
sleepIfNeeded();
log.debug("Exiting cycle()");
}
Thanks for your answer :)
of course that it is better to use an ACK to make sure if the receiver is getting the message but what I wanted to know if it was mandatory or not using the PCD-01 transaction.
I've read your documents and what I've understood is that the use of ACK depends on the MSH-15 and MSH-16 fields content, but with the following information:
An application that wants to send a message (initiate a transaction) will initiate a network connection to start the transaction. The receiver application will respond with an acknowledgement or response to query but will not initiate new transactions on this network connection
I understand that the ACK is only at the beginning of the connection not after every message, is it right?

javamail fetch mail pop3 android

I want to fetch emails in android by using javamail.
but I encountered some problems.
in POP3, if I want to fetch the content of a certain mail, the javamail will download all the content of the mail including the attachments. if the attachments are large enough, the android program will throw oom exception. But I tried in IMAP protocal, it only fetch the skeleton of the content first and even if there are some large attachments, it works well.
so, how could I fetch the content of a certain email using pop3 protocal when there are large attachment in the email?
following codes work well with imap, but not with pop3.
public void getContent(Part part) throws Exception {
String contentType = part.getContentType();
int nameindex = contentType.indexOf("name");
boolean conname = false;
if (nameindex != -1)
conname = true;
if (part.isMimeType("text/plain") && !conname) {
bodytext.append((String) part.getContent());
} else if (part.isMimeType("text/html") && !conname) {
String html = (String) part.getContent();
Spanned plainText = Html.fromHtml(html);
bodytext.append(plainText);
} else if (part.isMimeType("multipart/*")) {
Multipart multipart = (Multipart) part.getContent();
int counts = multipart.getCount();
for (int i = 0; i < counts; i++) {
BodyPart mpart = multipart.getBodyPart(i);
Log.d("type,"," "+ i+mpart.getContentType());
String disposition = mpart.getDisposition();
if ((disposition != null)
&& ((disposition.equals(Part.ATTACHMENT)) || (disposition
.equals(Part.INLINE))))
continue;
String contype = mpart.getContentType();
if(contype.toLowerCase().indexOf("application")!=-1||contype.toLowerCase().indexOf("name")!=-1)
continue;
getContent(multipart.getBodyPart(i));
}
} else if (part.isMimeType("message/rfc822")) {
getContent((Part) part.getContent());
}
}
I do not know the specifics of JavaMail, however:
IMAP provides methods in it's protocol for downloading MIME Parts of a message, allowing the message to be picked apart on the server and downloaded in parts.
POP3s basic protocol only has support for downloading the entire message. With the optional TOP extension, it will allow you to download the first n lines of a message.
If JavaMail has specific extensions to stream the download of a message to disk (rather than all in memory), that should allow you to get around your Out Of Memory error.
JavaMail 1.4.4 and later can cache POP3 messages in a tmp file on disk, thus using less memory. See the javadocs for the com.sun.mail.pop3 package for the properties to set. You'll still need to be careful how you process the contents of the mail, e.g., using getInputStream instead of getContent to process large attachments.

JavaMail Message getHeader() bug

I am writing an Android GMail client application.
When it creates a message, I add a header to it like this:
MimeMessage msg = new MimeMessage(session);
msg.setFrom(new InternetAddress(username));
msg.setSubject(subject);
msg.setText(message);
msg.setRecipient(Message.RecipientType.TO, new InternetAddress(to));
long time = someTime;
msg.addHeader("My_Header", Long.toString(time));
//IMAPFolder f declaration & initialization
f.open(Folder.READ_WRITE);
f.addMessages(new Message[]{msg});
f.close(true);
It all works fine and I am able to see the header correctly added to the message when viewing my GMail account on my PC. However, when I later try to retrieve the header information, it gets very weird.
When I call
String[] str = msg.getHeader("My_Header");
getHeader() returns null if I am running the app. However, when I debug the app and set a breakpoint before the getHeader() call, it returns the header value corrently.
My source code:
MimeMessage msg = getNextMessage();
String subject = msg.getSubject();
InternetAddresses to[] = msg.getAllRecipients()
String when[] = msg.getHeader(GMailClient.TIME_TO_SEND);
if (when == null) {
Log.i(TAG, "Null Header");
} else {
long time = Long.parseLong(when[0]);
Log.i(TAG, "Value retrieved: " + when[0]);
}
Possibly Gmail isn't returning the header information correctly via IMAP? You might want to look at the protocol trace to see exactly what the server is returning for your request.
Also, try writing out the entire message using (e.g.)
msg.writeTo(new FileOutputStream("msg.txt"));
and see if the header is there. If it is, and the protocol trace shows that it's not being returned for the getHeader call, it's a bug in the server. In that case, see the JavaMail FAQ entry for how to work around such server bugs.

Categories

Resources