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) { idchannelDelegate = [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; }