I am trying to implement the new 'Command 2' push notification in Java and cannot have it push multiple alerts. First alert is pushed successfully. Please help if you can spot any issue on this code
Apple specs
https://developer.apple.com/library/ios/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/Chapters/CommunicatingWIthAPS.html#//apple_ref/doc/uid/TP40008194-CH101-SW1
for (DeviceApps deviceApps : deviceAppsList) {
outputStream.write(getByteArray(deviceApps, pushAlert));
}
private byte[] getByteArray(DeviceApps deviceApps, PushAlert pushAlert) {
ByteArrayOutputStream dataBao = new ByteArrayOutputStream();
// Write the TokenLength as a 16bits unsigned int, in big endian
dataBao.write((byte)1);
dataBao.write(intTo2ByteArray(32));
dataBao.write(deviceTokenAsBytes);
// Write the PayloadLength as a 16bits unsigned int, in big endian
dataBao.write((byte)2);
dataBao.write(intTo2ByteArray(payLoadAsBytes.length));
dataBao.write(payLoadAsBytes);
// 4 bytes. Notification identifier
dataBao.write((byte)3);
dataBao.write(intTo2ByteArray(4));
dataBao.write(intTo4ByteArray(random.nextInt()));
// 4 bytes Expiration date
dataBao.write((byte)4);
dataBao.write(intTo2ByteArray(4));
dataBao.write(intTo4ByteArray(pushAlert.getUtcExpireTime()));
LOG.error("UtcExpireTime="+ pushAlert.getUtcExpireTime());
// 1 bytes Priority
dataBao.write((byte)5);
dataBao.write(intTo2ByteArray(1));
dataBao.write((byte)10);
//Frame Info
bao = new ByteArrayOutputStream();
bao.write((byte)2);
byte [] data = dataBao.toByteArray();
bao.write(intTo4ByteArray(data.length));
LOG.error(" data.length "+data.length);
bao.write(data);
return bao.toByteArray();
}
Support Methods
private static final byte[] intTo4ByteArray(int value) {
return ByteBuffer.allocate(4).putInt(value).array();
}
private static final byte[] intTo2ByteArray(int value) {
int s1 = (value & 0xFF00) >> 8;
int s2 = value & 0xFF;
return new byte[] { (byte) s1, (byte) s2 };
}
It looks like you are writing a single notification to bao, so why do you expect it to push multiple alerts? If you want to push multiple alerts, you have to repeat that sequence of bytes that you write into bao multiple times.
The command 2 and the frame data length applies to each message. If you send multiple messages in one connection, then for each message: send command 2, the message's frame data length, and the 5 parts (token, payload, id, expiry, priority)
Since you are getting back a an error code from APNS, the connection should be dropped at that point and APNS will ignore everything after the error. When you receive an error back, the identifier is the identifier you are currently using a random number for.
There's no easy solution here -- you have to rearchitect what you have so that when you receive the error, you can figure out everything after that point and resend -- I'd recommend using a sequential number for the Identifier and then storing the packets in a queue that you purge periodically (you have to keep them around for say 30 seconds to guarantee that Apple Accepted them).
Related
I'm trying to send multiple packets at once to a server, but the socket keeps "merging" all sync calls to write as a single call, I did a minimal reproducible example:
import 'dart:io';
void main() async {
// <Server-side> Create server in the local network at port <any available port>.
final ServerSocket server =
await ServerSocket.bind(InternetAddress.anyIPv4, 0);
server.listen((Socket client) {
int i = 1;
client.map(String.fromCharCodes).listen((String message) {
print('Got a new message (${i++}): $message');
});
});
// <Client-side> Connects to the server.
final Socket socket = await Socket.connect('localhost', server.port);
socket.write('Hi World');
socket.write('Hello World');
}
The result is:
> dart example.dart
> Got a new message (1): Hi WorldHello World
What I expect is:
> dart example.dart
> Got a new message (1): Hi World
> Got a new message (2): Hello World
Unfortunately dart.dev doesn't support dart:io library, so you need to run in your machine to see it working.
But in summary:
It creates a new tcp server at a random port.
Then creates a socket that connects to the previous created server.
The socket makes 2 synchronous calls to the write method.
The server only receives 1 call, which is the 2 messages concatenated.
Do we have some way to receive each synchronous write call in the server as separated packets instead buffering all sync calls into a single packet?
What I've already tried:
Using socket.setOption(SocketOption.tcpNoDelay, true); right after Socket.connect instantiation, this does modify the result:
final Socket socket = await Socket.connect('localhost', server.port);
socket.setOption(SocketOption.tcpNoDelay, true);
// ...
Using socket.add('Hi World'.codeUnits); instead of socket.write(...), also does not modify the result as expected, because write(...) seems to be just a short version add(...):
socket.add('Hi World'.codeUnits);
socket.add('Hello World'.codeUnits);
Side note:
Adding an async delay to avoid calling write synchronously:
socket.add('Hi World'.codeUnits);
await Future<void>.delayed(const Duration(milliseconds: 100));
socket.add('Hello World'.codeUnits);
make it works, but I am pretty sure this is not the right solution, and this isn't what I wanted.
Environment:
Dart SDK version: 2.18.4 (stable) (Tue Nov 1 15:15:07 2022 +0000) on "windows_x64"
This is a Dart-only environment, there is no Flutter attached to the workspace.
As Jeremy said:
Programmers coding directly to the TCP API have to implement this logic themselves (e.g. by prepending a fixed-length message-byte-count field to each of their application-level messages, and adding logic to the receiving program to parse these byte-count fields, read in that many additional bytes, and then present those bytes together to the next level of logic).
So I chose to:
Prefix each message with a - and suffix with ..
Use base64 to encode the real message to avoid conflict between the message and the previously defined separators.
And using this approach, I got this implementation:
// Send packets:
socket.write('-${base64Encode("Hi World".codeUnits)}.');
socket.write('-${base64Encode("Hello World".codeUnits)}.');
And to parse the packets:
// Cache the previous parsed packet data.
String parsed = '';
void _handleCompletePacket(String rawPacket) {
// Decode the original message from base64 using [base64Decode].
// And convert the [List<int>] to [String].
final String message = String.fromCharCodes(base64Decode(rawPacket));
print(message);
}
void _handleServerPacket(List<int> rawPacket) {
final String packet = String.fromCharCodes(rawPacket);
final String next = parsed + packet;
final List<String> items = <String>[];
final List<String> tokens = next.split('');
for (int i = 0; i < tokens.length; i++) {
final String char = tokens[i];
if (char == '-') {
if (items.isNotEmpty) {
// malformatted packet.
items.clear();
continue;
}
items.add('');
continue;
} else if (char == '.') {
if (items.isEmpty) {
// malformatted packet.
items.clear();
continue;
}
_handleCompletePacket(items.removeLast());
continue;
} else {
if (items.isEmpty) {
// malformatted packet.
items.clear();
continue;
}
items.last = items.last + char;
continue;
}
}
if (items.isNotEmpty) {
// the last data of this packet was left incomplete.
// cache it to complete with the next packet.
parsed = items.last;
}
}
client.listen(_handleServerPacket);
There are certainly more optimized solutions/approaches, but I got this just for chatting messages within [100-500] characters, so that's fine for now.
I'm trying to implement a security mechanism in RPL. For this, I need to log where did a packet come from. For example, if a packet is transmitted from A-->B-->C-->D, I want to find out at C that the packet came through B, and similarly for D.
I've added some code in uip6.c file to pull up the sender address from packetbuffer, but it is always null.
I'm storing the last node address and the time when the complete packet was received. These are the data structures.
struct packet_time_entry {
linkaddr_t *source;
uint32_t time;
};
MEMB(packet_time_mem, struct packet_time_entry, 16);
LIST(packet_time);
The main code I've written so far is this, in uip_process(), below line 1108 (master branch).
struct packet_time_entry *p = memb_alloc(&packet_time_mem);
p->time = clock_time();
linkaddr_copy(p->source, packetbuf_addr(PACKETBUF_ADDR_SENDER));
struct packet_time_entry *i;
for (i = list_head(packet_time); i != NULL; i = list_item_next(i)) {
if (linkaddr_cmp(i->source, p->source))
list_remove(packet_time, i);
}
list_add(packet_time, p);
PRINTF("Entry ");
PRINTLLADDR((uip_lladdr_t*) p->source); // always NULL
PRINTF("| %lu", p->time);
PRINTF("\n");
I expect it to give me the address from packetbuf, but it's always NULL. Also, I suspect it is run only at the destination node, but not at the intermediate nodes.
You can DEBUG the execution. For that just set DEBUG_NONE TO DEBUG_PRINT in uip6.c and then parse the serial log for finding the path taken by the packet.
I have a receipt in PKCS7 that I obtained from my iOS app. Apple says this is a PKCS7 structure, and within that, is information regarding past recurring purchases.
I have the raw receipt here, encoded in Base64.
I've sent this payload, with my secret key, to Apple and got this response. Based on WWDC videos, and documentation, I believe I should be able to read this receipt directly, and without sending it to apple.
I'm guessing that PEMReader in BC is the correct starting point parse it, however I'm not sure how to actually use it. I've scanned the BC source code for the strings "PKCS", and looked at unit tests, however all I ever see are casts from PEMReader into another format.
using (var stream1 = new MemoryStream(receipt.Data))
using (var stream2 = new StreamReader(stream1))
{
var pp = new PemReader(stream2);
pp.ReadObject();
}
Question
How do I use Bouncy Castle to verify a raw receipt payload generated from Apple Store?
Note to self: I intend to use this to inspect the actual binary to see if ApplicationUsername is included in the receipt, yet for some reason isn't returned in the JSON result when posting the server. (Bug on Apple's side?)
I've made this using Java 7 and BouncyCastle 1.56.
For the code below, consider that pemString is the PEM string you provided. But I had to make some modifications:
format (break lines for every 64 characters) - I've made a small program to do that
include BEGIN and END headers
So my PEM looks like:
-----BEGIN PKCS7-----
MIIv5gYJKoZIhvcNAQcCoIIv1zCCL9MCAQExCzAJBgUrDgMCGgUAMIIfhwYJKoZI
hvcNAQcBoIIfeASCH3Qxgh9wMAoCAQgCAQEEAhYAMAoCARQCAQEEAgwAMAsCAQEC
AQEEAwIBADALAgELAgEBBAMCAQAwCwIBDwIBAQQDAgEAMAsCARACAQEEAwIBADAL
....
gdTu2uzkTyT+vcBlaLHK1ZpjKozsBds7ys6Q4EFp7OLxtJTj7saEDYXCNQtXBjwl
UfSGvQkXeIbsaqSPvOVIE83K3ki5i64gccA=
-----END PKCS7-----
For the code below, I followed the definition in Apple's doc:
ReceiptAttribute ::= SEQUENCE {
type INTEGER,
version INTEGER,
value OCTET STRING
}
Payload ::= SET OF ReceiptAttribute
Code:
import org.bouncycastle.asn1.ASN1InputStream;
import org.bouncycastle.asn1.ASN1Object;
import org.bouncycastle.asn1.DEROctetString;
import org.bouncycastle.asn1.DLSequence;
import org.bouncycastle.asn1.DLSet;
import org.bouncycastle.cms.CMSSignedData;
import org.bouncycastle.util.io.pem.PemObject;
import org.bouncycastle.util.io.pem.PemReader;
String pemString = // PEM String as described above
PemReader reader = new PemReader(new StringReader(pemString));
PemObject pemObject = reader.readPemObject();
reader.close();
CMSSignedData s = new CMSSignedData(pemObject.getContent());
byte[] content = (byte[]) s.getSignedContent().getContent();
ASN1InputStream in = new ASN1InputStream(content);
// Payload: a SET of ReceiptAttribute
DLSet set = (DLSet) DLSet.fromByteArray(in.readObject().getEncoded());
int size = set.size();
for (int i = 0; i < size; i++) {
// ReceiptAttribute is a SEQUENCE
DLSequence seq = (DLSequence) set.getObjectAt(i);
// value is the third element of the sequence
DEROctetString oct = (DEROctetString) seq.getObjectAt(2);
ASN1Object obj = readObject(oct.getOctets()); // *** see comments below ***
}
in.close();
// readObject method
public ASN1Object readObject(byte[] b) throws IOException {
ASN1InputStream in = null;
try {
in = new ASN1InputStream(b);
return in.readObject();
} catch (Exception e) {
// if error occurs, just return the octet string
return new DEROctetString(b);
} finally {
in.close();
}
}
Variable obj will be the content of the ReceiptAttribute, and it can vary a lot - I've seen DERIA5String, DERUTF8String, ASN1Integer and many others. As I don't know all possible values of this field, I think it's up to you to check each value.
I've built a http server using netty. Everything is fine when it's running in my mac, but when I run it in a docker image, the http response always get truncated when great than 460k.
What's the problem will be? Please help.
Do you use aggregator to aggregate the http response or not? Take a look at the source code of HttpObjectDecoder. It will chunk bigger http response no matter if the http message itself is transfer coding or not.
The default maxChunk size is 8k.And even readable bytes is enough, it will chunk it. see the code below:
` case READ_FIXED_LENGTH_CONTENT: {
int readLimit = actualReadableBytes();
// Check if the buffer is readable first as we use the readable byte count
// to create the HttpChunk. This is needed as otherwise we may end up with
// create a HttpChunk instance that contains an empty buffer and so is
// handled like it is the last HttpChunk.
//
// See https://github.com/netty/netty/issues/433
if (readLimit == 0) {
return;
}
int toRead = Math.min(readLimit, maxChunkSize);
if (toRead > chunkSize) {
toRead = (int) chunkSize;
}
ByteBuf content = readBytes(ctx.alloc(), buffer, toRead);
chunkSize -= toRead;
`
I am attempting to send the an MDM push notification to an iPad using the production APN server. However, Push Sharp says that the notification failed because the identifier is equal to 1. The following code from the PushSharp code base illustrates how it comes to that conclusion...
//We now expect apple to close the connection on us anyway, so let's try and close things
// up here as well to get a head start
//Hopefully this way we have less messages written to the stream that we have to requeue
try { stream.Close(); stream.Dispose(); }
catch { }
//Get the enhanced format response
// byte 0 is always '1', byte 1 is the status, bytes 2,3,4,5 are the identifier of the notification
var identifier = IPAddress.NetworkToHostOrder(BitConverter.ToInt32(readBuffer, 2));
int failedNotificationIndex = -1;
SentNotification failedNotification = null;
//Try and find the failed notification in our sent list
for (int i = 0; i < sentNotifications.Count; i++)
{
var n = sentNotifications[i];
if (n.Identifier.Equals(identifier))
{
failedNotificationIndex = i;
failedNotification = n;
break;
}
}
Basically, after the writing the payload to the stream, it attempts to close the connection, during which it expects a response from the APN service, which I think it refers to as the notification identifier.
I have plugged the device into the iPhone Device Configuration utility, but nothing appears in the console, hence I assume that it never receives this notification.
My questions are...
What is this identifier that it is expecting ?
Is there anything that I am doing wrong ?
The device is running iOS 6. The structure of the payload is as follows...
{"aps":{},"mdm":"80369651-5802-40A2-A0AE-FCCF02F99589"}
The values in the returned byte[] of 6 bytes are as follows 8,8,0,0,0,1
No idea, I've never looked into the details how PushSharp deals with the APNS internals.
You shouldn't send the "aps":{} part in the notification payload, so maybe that's the reason the APNS fails the notification.
I'm sucessfully using PushSharp 1.0.17 with the following code for MDM notifications, so it definitely works in general.
var pushService = new PushService();
// attach event listeners
// override the production/development auto-detection as it doesn't
// work for MDM certificates
var cert = null; // load your push client certificate
var channel = new ApplePushChannelSettings(true, cert, true);
pushService.StartApplePushService(channel);
// create and send the notification
var notification = NotificationFactory
.Apple()
.ForDeviceToken("your-device-token-received-from-checkin")
.WithExpiry(DateTime.UtcNow.AddDays(1))
.WithCustomItem("mdm", "your-push-magic-received-in-checkin");
pushService.QueueNotification(notification);
For PushSharp v3.0+, you should be able to include directly in the Payload of the ApnsNotification.
public void SendIosMdm(string deviceToken, string pushMagic)
{
_apnsBroker.QueueNotification(new ApnsNotification
{
DeviceToken = deviceToken,
Payload = JObject.FromObject(new {
mdm = pushMagic
})
});
}