CustomMessage Format for ProPTT2
ProPTT2 client is using CustomMessage for special features.(especially sending picture)
ProPTT2 developer can use this format for ProPTT2 client and their clients.
Please refer to https://dev.proptt2.com/sdk-client-ref3-pttchannel.html#sendPublicCustomMessage
1. Format structure
CustomMessage = Custom Message Basic header + { Chunking header } + Custom Message header + Custom Data
2. Custom Message Basic header
| Field | Type | Description |
|---|---|---|
| Command | Byte | Request command 0x01: SENDPHOTO2CHANNEL 0x02~0x9F: Reserved for ProPTT2 0xA0~0xFF: User command. Other client can use this command. |
| ContentType | Byte | 0:Single message, 1:Chunked message |
| MsgID | Short(2Bytes) | You can decide it. |
3. Chunking header
| Field | Type | Description |
|---|---|---|
| CurrChunk | Short(2Bytes) | Current index, 1~ChunkCount |
| ChunkCount | Short(2Bytes) | Total chunk count. |
4. Custom Message header
4.1 SENDPHOTO2CHANNEL(0x01)
| Field | Type | Description |
|---|---|---|
| Type | Byte | Image format 0x01: BMP 0x02: JPEG 0x03: PNG |
| Reserved | Byte | Must be 0x00 |
| Reserved | Byte | Must be 0x00 |
| Reserved | Byte | Must be 0x00 |
The custom data must be a image file.
Custom Message Basic header + Chunking header + Custom Message header + Image file data.
< iOS sample for image receiving >
-(void) onPublicCustomMessageRecevied : (PTTChannel*) channelInstance senderID : (NSString*) senderID senderName : (NSString*) senderName dataBuffer : (unsigned char*) dataBuffer dataLength : (int) dataLength
{
if([[ProvisionTokenRepository getInstance] provChat] == nil || ([[ProvisionTokenRepository getInstance] provChat] != nil && ![[[ProvisionTokenRepository getInstance] provChat] isEqualToString:@"0"]))
{
if(_isConnected)
{
if(![senderID isEqualToString:_myID])
{
if(dataLength >= 16)
{
char command = dataBuffer[0];
switch (command) {
case 0x01:
{
char contentType = dataBuffer[1];
if((contentType & 0x01) > 0) // chunked
{
short msgID = ( dataBuffer[2] << 8 | dataBuffer[3]);
short currChunk = ( dataBuffer[4] << 8 | dataBuffer[5]);
short chunkCount = ( dataBuffer[6] << 8 | dataBuffer[7]);
char type = dataBuffer[8];
NSString *suffix = @"";
if(type == 1)
{
suffix = @".bmp";
return; // not support current
}
else if(type == 2){
suffix = @".jpg";
}
else if(type == 3){
suffix = @".png";
}
NSString *path = [NSString stringWithFormat:@"%@%@",[_photoFormatter stringFromDate:[NSDate date]],suffix];
NSData *data = [[NSData alloc] initWithBytes:dataBuffer+12 length:dataLength-12];
NSString *key = [NSString stringWithFormat:@"%@_%d",senderID,msgID];
NSData* photo = [self combinePhotoChunkkey photoData:data currChunk:currChunk chunkSize:chunkCount];
if(photo != nil)
{
dispatch_async(dispatch_get_main_queue(), ^{
if([self isNeedToShowChatNoti])
{
[PTTNotification showSentImage:senderName channel:self];
}
UIImage *image = [UIImage imageWithData:photo];
[_repository savePhotoImageSync:path image:image];
NSArray *records = [self insertChatNewMessage:_client.provisionServerURI account:_myID message:nil channelID:_channel.channelID userID:senderID userName:senderName cellType:ChatCellTypeReceiverPhoto path:path fileSize:[photo length] imageState:ImageStateCompleted width:image.size.width height:image.size.height location:@""];
for(NSManagedObject *record in records)
{
id channelDelegate = [self getVisibleDelegate];
if([channelDelegate respondsToSelector:@selector(onPublicCustomMessageRecevied:senderID:senderName:dataBuffer:dataLength:)])
{
[channelDelegate onPublicCustomMessageRecevied:self senderID:senderID senderName:senderName dataBuffer:dataBuffer dataLength:dataLength];
}
if([channelDelegate respondsToSelector:@selector(onPublicCustomMessageRecevied:managedObject:)])
{
[channelDelegate onPublicCustomMessageRecevied:self managedObject:record];
}
if([_channelDelegate respondsToSelector:@selector(onPublicCustomMessageRecevied:senderID:senderName:dataBuffer:dataLength:)])
{
[_channelDelegate onPublicCustomMessageRecevied:self senderID:senderID senderName:senderName dataBuffer:dataBuffer dataLength:dataLength];
}
if([_channelDelegate respondsToSelector:@selector(onPublicCustomMessageRecevied:managedObject:)])
{
[_channelDelegate onPublicCustomMessageRecevied:self managedObject:record];
}
}
});
}
}
}
break;
default:
break;
}
}
}
}
}
}
-(NSData*) combinePhotoChunk(NSString*) msgID photoData : (NSData*) data currChunk : (short) currChunk chunkSize : (short) chunkSize
{
NSMutableDictionary *photoData = [_photoMap objectForKey:msgID];
if(photoData == nil)
{
photoData = [[NSMutableDictionary alloc] init];
[_photoMap setObject:photoData forKey:msgID];
}
[photoData setObject:data forKey:@(currChunk)];
if([photoData count] == chunkSize)
{
NSMutableData *result = [[NSMutableData alloc] init];
for(NSInteger i=0; i < chunkSize; ++i)
{
NSData *segment = [photoData objectForKey:@(i+1)];
[result appendData:segment];
}
[_photoMap removeObjectForKey:msgID];
return result;
}
return nil;
}
