[WIP] Add all extensions

This commit is contained in:
Ali
2020-10-18 19:13:31 +04:00
parent ed7d110c94
commit 702d572f07
48 changed files with 480 additions and 54 deletions

View File

@@ -0,0 +1,298 @@
#import <Foundation/Foundation.h>
/*
* Layer 1
*/
@class Api1_Photo;
@class Api1_Photo_photoEmpty;
@class Api1_Photo_photo;
@class Api1_PhotoSize;
@class Api1_PhotoSize_photoSizeEmpty;
@class Api1_PhotoSize_photoSize;
@class Api1_PhotoSize_photoCachedSize;
@class Api1_PhotoSize_photoStrippedSize;
@class Api1_FileLocation;
@class Api1_FileLocation_fileLocationToBeDeprecated;
@class Api1_DocumentAttribute;
@class Api1_DocumentAttribute_documentAttributeImageSize;
@class Api1_DocumentAttribute_documentAttributeAnimated;
@class Api1_DocumentAttribute_documentAttributeSticker;
@class Api1_DocumentAttribute_documentAttributeVideo;
@class Api1_DocumentAttribute_documentAttributeAudio;
@class Api1_DocumentAttribute_documentAttributeFilename;
@class Api1_DocumentAttribute_documentAttributeHasStickers;
@class Api1_InputStickerSet;
@class Api1_InputStickerSet_inputStickerSetEmpty;
@class Api1_InputStickerSet_inputStickerSetID;
@class Api1_InputStickerSet_inputStickerSetShortName;
@class Api1_InputFileLocation;
@class Api1_InputFileLocation_inputPhotoFileLocation;
@class Api1_InputFileLocation_inputDocumentFileLocation;
@class Api1_MaskCoords;
@class Api1_MaskCoords_maskCoords;
@class Api1_Document;
@class Api1_Document_document;
@interface Api1__Environment : NSObject
+ (NSData *)serializeObject:(id)object;
+ (id)parseObject:(NSData *)data;
@end
@interface Api1_FunctionContext : NSObject
@property (nonatomic, strong, readonly) NSData *payload;
@property (nonatomic, copy, readonly) id (^responseParser)(NSData *);
@property (nonatomic, strong, readonly) id metadata;
- (instancetype)initWithPayload:(NSData *)payload responseParser:(id (^)(NSData *))responseParser metadata:(id)metadata;
@end
/*
* Types 1
*/
@interface Api1_Photo : NSObject
@property (nonatomic, strong, readonly) NSNumber * pid;
+ (Api1_Photo_photoEmpty *)photoEmptyWithPid:(NSNumber *)pid;
+ (Api1_Photo_photo *)photoWithFlags:(NSNumber *)flags pid:(NSNumber *)pid accessHash:(NSNumber *)accessHash fileReference:(NSData *)fileReference date:(NSNumber *)date sizes:(NSArray *)sizes dcId:(NSNumber *)dcId;
@end
@interface Api1_Photo_photoEmpty : Api1_Photo
@end
@interface Api1_Photo_photo : Api1_Photo
@property (nonatomic, strong, readonly) NSNumber * flags;
@property (nonatomic, strong, readonly) NSNumber * accessHash;
@property (nonatomic, strong, readonly) NSData * fileReference;
@property (nonatomic, strong, readonly) NSNumber * date;
@property (nonatomic, strong, readonly) NSArray * sizes;
@property (nonatomic, strong, readonly) NSNumber * dcId;
@end
@interface Api1_PhotoSize : NSObject
@property (nonatomic, strong, readonly) NSString * type;
+ (Api1_PhotoSize_photoSizeEmpty *)photoSizeEmptyWithType:(NSString *)type;
+ (Api1_PhotoSize_photoSize *)photoSizeWithType:(NSString *)type location:(Api1_FileLocation *)location w:(NSNumber *)w h:(NSNumber *)h size:(NSNumber *)size;
+ (Api1_PhotoSize_photoCachedSize *)photoCachedSizeWithType:(NSString *)type location:(Api1_FileLocation *)location w:(NSNumber *)w h:(NSNumber *)h bytes:(NSData *)bytes;
+ (Api1_PhotoSize_photoStrippedSize *)photoStrippedSizeWithType:(NSString *)type bytes:(NSData *)bytes;
@end
@interface Api1_PhotoSize_photoSizeEmpty : Api1_PhotoSize
@end
@interface Api1_PhotoSize_photoSize : Api1_PhotoSize
@property (nonatomic, strong, readonly) Api1_FileLocation * location;
@property (nonatomic, strong, readonly) NSNumber * w;
@property (nonatomic, strong, readonly) NSNumber * h;
@property (nonatomic, strong, readonly) NSNumber * size;
@end
@interface Api1_PhotoSize_photoCachedSize : Api1_PhotoSize
@property (nonatomic, strong, readonly) Api1_FileLocation * location;
@property (nonatomic, strong, readonly) NSNumber * w;
@property (nonatomic, strong, readonly) NSNumber * h;
@property (nonatomic, strong, readonly) NSData * bytes;
@end
@interface Api1_PhotoSize_photoStrippedSize : Api1_PhotoSize
@property (nonatomic, strong, readonly) NSData * bytes;
@end
@interface Api1_FileLocation : NSObject
@property (nonatomic, strong, readonly) NSNumber * volumeId;
@property (nonatomic, strong, readonly) NSNumber * localId;
+ (Api1_FileLocation_fileLocationToBeDeprecated *)fileLocationToBeDeprecatedWithVolumeId:(NSNumber *)volumeId localId:(NSNumber *)localId;
@end
@interface Api1_FileLocation_fileLocationToBeDeprecated : Api1_FileLocation
@end
@interface Api1_DocumentAttribute : NSObject
+ (Api1_DocumentAttribute_documentAttributeImageSize *)documentAttributeImageSizeWithW:(NSNumber *)w h:(NSNumber *)h;
+ (Api1_DocumentAttribute_documentAttributeAnimated *)documentAttributeAnimated;
+ (Api1_DocumentAttribute_documentAttributeSticker *)documentAttributeStickerWithFlags:(NSNumber *)flags alt:(NSString *)alt stickerset:(Api1_InputStickerSet *)stickerset maskCoords:(Api1_MaskCoords *)maskCoords;
+ (Api1_DocumentAttribute_documentAttributeVideo *)documentAttributeVideoWithFlags:(NSNumber *)flags duration:(NSNumber *)duration w:(NSNumber *)w h:(NSNumber *)h;
+ (Api1_DocumentAttribute_documentAttributeAudio *)documentAttributeAudioWithFlags:(NSNumber *)flags duration:(NSNumber *)duration title:(NSString *)title performer:(NSString *)performer waveform:(NSData *)waveform;
+ (Api1_DocumentAttribute_documentAttributeFilename *)documentAttributeFilenameWithFileName:(NSString *)fileName;
+ (Api1_DocumentAttribute_documentAttributeHasStickers *)documentAttributeHasStickers;
@end
@interface Api1_DocumentAttribute_documentAttributeImageSize : Api1_DocumentAttribute
@property (nonatomic, strong, readonly) NSNumber * w;
@property (nonatomic, strong, readonly) NSNumber * h;
@end
@interface Api1_DocumentAttribute_documentAttributeAnimated : Api1_DocumentAttribute
@end
@interface Api1_DocumentAttribute_documentAttributeSticker : Api1_DocumentAttribute
@property (nonatomic, strong, readonly) NSNumber * flags;
@property (nonatomic, strong, readonly) NSString * alt;
@property (nonatomic, strong, readonly) Api1_InputStickerSet * stickerset;
@property (nonatomic, strong, readonly) Api1_MaskCoords * maskCoords;
@end
@interface Api1_DocumentAttribute_documentAttributeVideo : Api1_DocumentAttribute
@property (nonatomic, strong, readonly) NSNumber * flags;
@property (nonatomic, strong, readonly) NSNumber * duration;
@property (nonatomic, strong, readonly) NSNumber * w;
@property (nonatomic, strong, readonly) NSNumber * h;
@end
@interface Api1_DocumentAttribute_documentAttributeAudio : Api1_DocumentAttribute
@property (nonatomic, strong, readonly) NSNumber * flags;
@property (nonatomic, strong, readonly) NSNumber * duration;
@property (nonatomic, strong, readonly) NSString * title;
@property (nonatomic, strong, readonly) NSString * performer;
@property (nonatomic, strong, readonly) NSData * waveform;
@end
@interface Api1_DocumentAttribute_documentAttributeFilename : Api1_DocumentAttribute
@property (nonatomic, strong, readonly) NSString * fileName;
@end
@interface Api1_DocumentAttribute_documentAttributeHasStickers : Api1_DocumentAttribute
@end
@interface Api1_InputStickerSet : NSObject
+ (Api1_InputStickerSet_inputStickerSetEmpty *)inputStickerSetEmpty;
+ (Api1_InputStickerSet_inputStickerSetID *)inputStickerSetIDWithPid:(NSNumber *)pid accessHash:(NSNumber *)accessHash;
+ (Api1_InputStickerSet_inputStickerSetShortName *)inputStickerSetShortNameWithShortName:(NSString *)shortName;
@end
@interface Api1_InputStickerSet_inputStickerSetEmpty : Api1_InputStickerSet
@end
@interface Api1_InputStickerSet_inputStickerSetID : Api1_InputStickerSet
@property (nonatomic, strong, readonly) NSNumber * pid;
@property (nonatomic, strong, readonly) NSNumber * accessHash;
@end
@interface Api1_InputStickerSet_inputStickerSetShortName : Api1_InputStickerSet
@property (nonatomic, strong, readonly) NSString * shortName;
@end
@interface Api1_InputFileLocation : NSObject
@property (nonatomic, strong, readonly) NSNumber * pid;
@property (nonatomic, strong, readonly) NSNumber * accessHash;
@property (nonatomic, strong, readonly) NSData * fileReference;
@property (nonatomic, strong, readonly) NSString * thumbSize;
+ (Api1_InputFileLocation_inputPhotoFileLocation *)inputPhotoFileLocationWithPid:(NSNumber *)pid accessHash:(NSNumber *)accessHash fileReference:(NSData *)fileReference thumbSize:(NSString *)thumbSize;
+ (Api1_InputFileLocation_inputDocumentFileLocation *)inputDocumentFileLocationWithPid:(NSNumber *)pid accessHash:(NSNumber *)accessHash fileReference:(NSData *)fileReference thumbSize:(NSString *)thumbSize;
@end
@interface Api1_InputFileLocation_inputPhotoFileLocation : Api1_InputFileLocation
@end
@interface Api1_InputFileLocation_inputDocumentFileLocation : Api1_InputFileLocation
@end
@interface Api1_MaskCoords : NSObject
@property (nonatomic, strong, readonly) NSNumber * n;
@property (nonatomic, strong, readonly) NSNumber * x;
@property (nonatomic, strong, readonly) NSNumber * y;
@property (nonatomic, strong, readonly) NSNumber * zoom;
+ (Api1_MaskCoords_maskCoords *)maskCoordsWithN:(NSNumber *)n x:(NSNumber *)x y:(NSNumber *)y zoom:(NSNumber *)zoom;
@end
@interface Api1_MaskCoords_maskCoords : Api1_MaskCoords
@end
@interface Api1_Document : NSObject
@property (nonatomic, strong, readonly) NSNumber * flags;
@property (nonatomic, strong, readonly) NSNumber * pid;
@property (nonatomic, strong, readonly) NSNumber * accessHash;
@property (nonatomic, strong, readonly) NSData * fileReference;
@property (nonatomic, strong, readonly) NSNumber * date;
@property (nonatomic, strong, readonly) NSString * mimeType;
@property (nonatomic, strong, readonly) NSNumber * size;
@property (nonatomic, strong, readonly) NSArray * thumbs;
@property (nonatomic, strong, readonly) NSNumber * dcId;
@property (nonatomic, strong, readonly) NSArray * attributes;
+ (Api1_Document_document *)documentWithFlags:(NSNumber *)flags pid:(NSNumber *)pid accessHash:(NSNumber *)accessHash fileReference:(NSData *)fileReference date:(NSNumber *)date mimeType:(NSString *)mimeType size:(NSNumber *)size thumbs:(NSArray *)thumbs dcId:(NSNumber *)dcId attributes:(NSArray *)attributes;
@end
@interface Api1_Document_document : Api1_Document
@end
/*
* Functions 1
*/
@interface Api1: NSObject
@end

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,7 @@
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
id _Nullable parseAttachment(NSData * _Nonnull data);
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,31 @@
#import "Attachments.h"
#import <MTProtoKit/MTProtoKit.h>
#import "Api.h"
id _Nullable parseAttachment(NSData * _Nonnull data) {
if (data.length < 4) {
return nil;
}
MTInputStream *inputStream = [[MTInputStream alloc] initWithData:data];
int32_t signature = [inputStream readInt32];
NSData *dataToParse = nil;
if (signature == 0x3072cfa1) {
NSData *bytes = [inputStream readBytes];
if (bytes != nil) {
dataToParse = [MTGzip decompress:bytes];
}
} else {
dataToParse = data;
}
if (dataToParse == nil) {
return nil;
}
return [Api1__Environment parseObject:dataToParse];
}

View File

@@ -0,0 +1,11 @@
#import <Foundation/Foundation.h>
#import "StoredAccountInfos.h"
#import "Api.h"
#import <BuildConfig/BuildConfig.h>
NS_ASSUME_NONNULL_BEGIN
dispatch_block_t fetchImage(BuildConfig *buildConfig, AccountProxyConnection * _Nullable proxyConnection, StoredAccountInfo *account, Api1_InputFileLocation *inputFileLocation, int32_t datacenterId, void (^_completion)(NSData * _Nullable));
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,221 @@
#import "FetchImage.h"
#import <MTProtoKit/MTProtoKit.h>
#import <EncryptionProvider/EncryptionProvider.h>
#import "Serialization.h"
@interface EmptyEncryptionProvider: NSObject <EncryptionProvider>
@end
@implementation EmptyEncryptionProvider
- (id<MTBignumContext>)createBignumContext {
return nil;
}
- (NSData * _Nullable)rsaEncryptWithPublicKey:(NSString *)publicKey data:(NSData *)data {
return nil;
}
- (NSData * _Nullable)rsaEncryptPKCS1OAEPWithPublicKey:(NSString *)publicKey data:(NSData *)data {
return nil;
}
- (id<MTRsaPublicKey>)parseRSAPublicKey:(NSString *)publicKey {
return nil;
}
@end
@interface InMemoryKeychain : NSObject <MTKeychain> {
NSMutableDictionary *_dict;
}
@end
@implementation InMemoryKeychain
- (instancetype)init {
self = [super init];
if (self != nil) {
_dict = [[NSMutableDictionary alloc] init];
}
return self;
}
- (void)setObject:(id)object forKey:(NSString *)aKey group:(NSString *)group {
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:object];
_dict[[NSString stringWithFormat:@"%@:%@", group, aKey]] = data;
}
- (id)objectForKey:(NSString *)aKey group:(NSString *)group {
NSData *data = _dict[[NSString stringWithFormat:@"%@:%@", group, aKey]];
if (data != nil) {
return [NSKeyedUnarchiver unarchiveObjectWithData:data];
} else {
return nil;
}
}
- (void)removeObjectForKey:(NSString *)aKey group:(NSString *)group {
[_dict removeObjectForKey:[NSString stringWithFormat:@"%@:%@", group, aKey]];
}
- (void)dropGroup:(NSString *)group {
}
@end
static void MTLoggingFunction(NSString *string, va_list args) {
NSLogv(string, args);
}
@interface ParsedFile : NSObject
@property (nonatomic, strong, readonly) NSData * _Nullable data;
@end
@implementation ParsedFile
- (instancetype)initWithData:(NSData * _Nullable)data {
self = [super init];
if (self != nil) {
_data = data;
}
return self;
}
@end
dispatch_block_t fetchImage(BuildConfig *buildConfig, AccountProxyConnection * _Nullable proxyConnection, StoredAccountInfo *account, Api1_InputFileLocation *inputFileLocation, int32_t datacenterId, void (^completion)(NSData * _Nullable)) {
MTLogSetEnabled(true);
MTLogSetLoggingFunction(&MTLoggingFunction);
Serialization *serialization = [[Serialization alloc] init];
MTApiEnvironment *apiEnvironment = [[MTApiEnvironment alloc] init];
apiEnvironment.apiId = buildConfig.apiId;
apiEnvironment.langPack = @"ios";
apiEnvironment.layer = @([serialization currentLayer]);
apiEnvironment.disableUpdates = true;
apiEnvironment = [apiEnvironment withUpdatedLangPackCode:@"en"];
if (proxyConnection != nil) {
apiEnvironment = [apiEnvironment withUpdatedSocksProxySettings:[[MTSocksProxySettings alloc] initWithIp:proxyConnection.host port:(uint16_t)proxyConnection.port username:proxyConnection.username password:proxyConnection.password secret:proxyConnection.secret]];
}
MTContext *context = [[MTContext alloc] initWithSerialization:serialization encryptionProvider:[[EmptyEncryptionProvider alloc] init] apiEnvironment:apiEnvironment isTestingEnvironment:account.isTestingEnvironment useTempAuthKeys:true];
context.tempKeyExpiration = 10 * 60 * 60;
NSDictionary *seedAddressList = @{};
if (account.isTestingEnvironment) {
seedAddressList = @{
@(1): @[@"149.154.175.10"],
@(2): @[@"149.154.167.40"]
};
} else {
seedAddressList = @{
@(1): @[@"149.154.175.50", @"2001:b28:f23d:f001::a"],
@(2): @[@"149.154.167.50", @"2001:67c:4e8:f002::a"],
@(3): @[@"149.154.175.100", @"2001:b28:f23d:f003::a"],
@(4): @[@"149.154.167.91", @"2001:67c:4e8:f004::a"],
@(5): @[@"149.154.171.5", @"2001:b28:f23f:f005::a"]
};
}
for (NSNumber *datacenterId in seedAddressList) {
NSMutableArray *addressList = [[NSMutableArray alloc] init];
for (NSString *host in seedAddressList[datacenterId]) {
[addressList addObject:[[MTDatacenterAddress alloc] initWithIp:host port:443 preferForMedia:false restrictToTcp:false cdn:false preferForProxy:false secret:nil]];
}
[context setSeedAddressSetForDatacenterWithId:[datacenterId intValue] seedAddressSet:[[MTDatacenterAddressSet alloc] initWithAddressList:addressList]];
}
InMemoryKeychain *keychain = [[InMemoryKeychain alloc] init];
context.keychain = keychain;
[context performBatchUpdates:^{
for (NSNumber *datacenterId in account.datacenters) {
AccountDatacenterInfo *info = account.datacenters[datacenterId];
if (info.addressList.count != 0) {
NSMutableArray *list = [[NSMutableArray alloc] init];
for (AccountDatacenterAddress *address in info.addressList) {
[list addObject:[[MTDatacenterAddress alloc] initWithIp:address.host port:address.port preferForMedia:address.isMedia restrictToTcp:false cdn:false preferForProxy:address.isProxy secret:address.secret]];
}
[context updateAddressSetForDatacenterWithId:[datacenterId intValue] addressSet:[[MTDatacenterAddressSet alloc] initWithAddressList:list] forceUpdateSchemes:true];
}
}
}];
for (NSNumber *datacenterId in account.datacenters) {
AccountDatacenterInfo *info = account.datacenters[datacenterId];
[context updateAuthInfoForDatacenterWithId:[datacenterId intValue] authInfo:[[MTDatacenterAuthInfo alloc] initWithAuthKey:info.masterKey.data authKeyId:info.masterKey.keyId saltSet:@[] authKeyAttributes:@{}] selector:MTDatacenterAuthInfoSelectorPersistent];
}
MTProto *mtProto = [[MTProto alloc] initWithContext:context datacenterId:datacenterId usageCalculationInfo:nil requiredAuthToken:nil authTokenMasterDatacenterId:0];
mtProto.useTempAuthKeys = context.useTempAuthKeys;
mtProto.checkForProxyConnectionIssues = false;
MTRequestMessageService *requestService = [[MTRequestMessageService alloc] initWithContext:context];
[mtProto addMessageService:requestService];
MTRequest *request = [[MTRequest alloc] init];
MTOutputStream *outputStream = [[MTOutputStream alloc] init];
[outputStream writeInt32:-475607115]; //upload.getFile
[outputStream writeData:[Api1__Environment serializeObject:inputFileLocation]];
[outputStream writeInt32:0];
[outputStream writeInt32:32 * 1024];
[request setPayload:[outputStream currentBytes] metadata:@"getFile" shortMetadata:@"getFile" responseParser:^id(NSData *response) {
MTInputStream *inputStream = [[MTInputStream alloc] initWithData:response];
int32_t signature = [inputStream readInt32];
if (signature != 157948117) {
return [[ParsedFile alloc] initWithData:nil];
}
[inputStream readInt32]; //type
[inputStream readInt32]; //mtime
return [[ParsedFile alloc] initWithData:[inputStream readBytes]];
}];
request.dependsOnPasswordEntry = false;
request.shouldContinueExecutionWithErrorContext = ^bool (__unused MTRequestErrorContext *errorContext) {
return true;
};
request.completed = ^(id boxedResponse, __unused NSTimeInterval completionTimestamp, MTRpcError *error) {
if (error != nil) {
if (completion) {
completion(nil);
}
} else {
if ([boxedResponse isKindOfClass:[ParsedFile class]]) {
if (completion) {
completion(((ParsedFile *)boxedResponse).data);
}
} else {
if (completion) {
completion(nil);
}
}
}
};
[requestService addRequest:request];
[mtProto resume];
id internalId = request.internalId;
return ^{
[requestService removeRequestByInternalId:internalId];
[context performBatchUpdates:^{
}];
[mtProto stop];
};
}

View File

@@ -0,0 +1,480 @@
#import <NotificationServiceObjC/NotificationServiceObjC.h>
#import <mach/mach.h>
#import <UIKit/UIKit.h>
#import <BuildConfig/BuildConfig.h>
#ifdef __IPHONE_13_0
#import <BackgroundTasks/BackgroundTasks.h>
#endif
#import "StoredAccountInfos.h"
#import "Attachments.h"
#import "Api.h"
#import "FetchImage.h"
static NSData * _Nullable parseBase64(NSString *string) {
string = [string stringByReplacingOccurrencesOfString:@"-" withString:@"+"];
string = [string stringByReplacingOccurrencesOfString:@"_" withString:@"/"];
while (string.length % 4 != 0) {
string = [string stringByAppendingString:@"="];
}
return [[NSData alloc] initWithBase64EncodedString:string options:0];
}
typedef enum {
PeerNamespaceCloudUser = 0,
PeerNamespaceCloudGroup = 1,
PeerNamespaceCloudChannel = 2,
PeerNamespaceSecretChat = 3
} PeerNamespace;
static int64_t makePeerId(int32_t namespace, int32_t value) {
return (((int64_t)(namespace)) << 32) | ((int64_t)((uint64_t)((uint32_t)value)));
}
#if DEBUG
static void reportMemory() {
struct task_basic_info info;
mach_msg_type_number_t size = TASK_BASIC_INFO_COUNT;
kern_return_t kerr = task_info(mach_task_self(), TASK_BASIC_INFO, (task_info_t)&info, &size);
if (kerr == KERN_SUCCESS) {
NSLog(@"Memory in use (in bytes): %lu", info.resident_size);
NSLog(@"Memory in use (in MiB): %f", ((CGFloat)info.resident_size / 1048576));
} else {
NSLog(@"Error with task_info(): %s", mach_error_string(kerr));
}
}
#endif
@interface NotificationServiceImpl () {
void (^_serialDispatch)(dispatch_block_t);
void (^_countIncomingMessage)(NSString *, int64_t, DeviceSpecificEncryptionParameters *, int64_t, int32_t);
NSString * _Nullable _rootPath;
DeviceSpecificEncryptionParameters * _Nullable _deviceSpecificEncryptionParameters;
bool _isLockedValue;
NSString *_lockedMessageTextValue;
NSString * _Nullable _baseAppBundleId;
void (^_contentHandler)(UNNotificationContent *);
UNMutableNotificationContent * _Nullable _bestAttemptContent;
void (^_cancelFetch)(void);
NSNumber * _Nullable _updatedUnreadCount;
bool _contentReady;
}
@end
@implementation NotificationServiceImpl
- (instancetype)initWithSerialDispatch:(void (^)(dispatch_block_t))serialDispatch countIncomingMessage:(void (^)(NSString *, int64_t, DeviceSpecificEncryptionParameters *, int64_t, int32_t))countIncomingMessage isLocked:(nonnull bool (^)(NSString * _Nonnull))isLocked lockedMessageText:(NSString *(^)(NSString *))lockedMessageText {
self = [super init];
if (self != nil) {
#if DEBUG
reportMemory();
#endif
_serialDispatch = [serialDispatch copy];
_countIncomingMessage = [countIncomingMessage copy];
NSString *appBundleIdentifier = [NSBundle mainBundle].bundleIdentifier;
NSRange lastDotRange = [appBundleIdentifier rangeOfString:@"." options:NSBackwardsSearch];
if (lastDotRange.location != NSNotFound) {
_baseAppBundleId = [appBundleIdentifier substringToIndex:lastDotRange.location];
NSString *appGroupName = [@"group." stringByAppendingString:_baseAppBundleId];
NSURL *appGroupUrl = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:appGroupName];
if (appGroupUrl != nil) {
NSString *rootPath = [[appGroupUrl path] stringByAppendingPathComponent:@"telegram-data"];
_rootPath = rootPath;
if (rootPath != nil) {
_deviceSpecificEncryptionParameters = [BuildConfig deviceSpecificEncryptionParameters:rootPath baseAppBundleId:_baseAppBundleId];
_isLockedValue = isLocked(rootPath);
if (_isLockedValue) {
_lockedMessageTextValue = lockedMessageText(rootPath);
}
}
} else {
NSAssert(false, @"appGroupUrl == nil");
}
} else {
NSAssert(false, @"Invalid bundle id");
}
}
return self;
}
- (void)completeWithBestAttemptContent {
_contentReady = true;
_updatedUnreadCount = @(-1);
if (_contentReady && _updatedUnreadCount) {
[self _internalComplete];
}
}
- (void)updateUnreadCount:(int32_t)unreadCount {
_updatedUnreadCount = @(unreadCount);
if (_contentReady && _updatedUnreadCount) {
[self _internalComplete];
}
}
- (void)_internalComplete {
#if DEBUG
reportMemory();
#endif
NSString *baseAppBundleId = _baseAppBundleId;
void (^contentHandler)(UNNotificationContent *) = [_contentHandler copy];
UNMutableNotificationContent *bestAttemptContent = _bestAttemptContent;
NSNumber *updatedUnreadCount = updatedUnreadCount;
dispatch_async(dispatch_get_main_queue(), ^{
#ifdef __IPHONE_13_0
if (baseAppBundleId != nil && false) {
BGAppRefreshTaskRequest *request = [[BGAppRefreshTaskRequest alloc] initWithIdentifier:[baseAppBundleId stringByAppendingString:@".refresh"]];
request.earliestBeginDate = nil;
NSError *error = nil;
[[BGTaskScheduler sharedScheduler] submitTaskRequest:request error:&error];
if (error != nil) {
NSLog(@"Error: %@", error);
}
}
#endif
if (updatedUnreadCount != nil) {
int32_t unreadCount = (int32_t)[updatedUnreadCount intValue];
if (unreadCount > 0) {
bestAttemptContent.badge = @(unreadCount);
}
}
contentHandler(bestAttemptContent);
});
}
- (void)didReceiveNotificationRequest:(UNNotificationRequest *)request withContentHandler:(void (^)(UNNotificationContent * _Nonnull))contentHandler {
if (_rootPath == nil) {
_bestAttemptContent = request.content;
[self completeWithBestAttemptContent];
return;
}
_contentHandler = [contentHandler copy];
_bestAttemptContent = (UNMutableNotificationContent *)[request.content mutableCopy];
NSString * _Nullable encryptedPayload = request.content.userInfo[@"p"];
NSData * _Nullable encryptedData = nil;
if (encryptedPayload != nil && [encryptedPayload isKindOfClass:[NSString class]]) {
encryptedData = parseBase64(encryptedPayload);
}
StoredAccountInfos * _Nullable accountInfos = [StoredAccountInfos loadFromPath:[_rootPath stringByAppendingPathComponent:@"accounts-shared-data"]];
int selectedAccountIndex = -1;
NSDictionary *decryptedPayload = decryptedNotificationPayload(accountInfos.accounts, encryptedData, &selectedAccountIndex);
if (decryptedPayload != nil && selectedAccountIndex != -1) {
StoredAccountInfo *account = accountInfos.accounts[selectedAccountIndex];
NSMutableDictionary *userInfo = nil;
if (_bestAttemptContent.userInfo != nil) {
userInfo = [[NSMutableDictionary alloc] initWithDictionary:_bestAttemptContent.userInfo];
} else {
userInfo = [[NSMutableDictionary alloc] init];
}
userInfo[@"accountId"] = @(account.accountId);
int64_t peerId = 0;
int32_t messageId = 0;
bool silent = false;
NSString *messageIdString = decryptedPayload[@"msg_id"];
if ([messageIdString isKindOfClass:[NSString class]]) {
userInfo[@"msg_id"] = messageIdString;
messageId = [messageIdString intValue];
}
NSString *fromIdString = decryptedPayload[@"from_id"];
if ([fromIdString isKindOfClass:[NSString class]]) {
userInfo[@"from_id"] = fromIdString;
peerId = makePeerId(PeerNamespaceCloudUser, [fromIdString intValue]);
}
NSString *chatIdString = decryptedPayload[@"chat_id"];
if ([chatIdString isKindOfClass:[NSString class]]) {
userInfo[@"chat_id"] = chatIdString;
peerId = makePeerId(PeerNamespaceCloudGroup, [chatIdString intValue]);
}
NSString *channelIdString = decryptedPayload[@"channel_id"];
if ([channelIdString isKindOfClass:[NSString class]]) {
userInfo[@"channel_id"] = channelIdString;
peerId = makePeerId(PeerNamespaceCloudChannel, [channelIdString intValue]);
}
/*if (_countIncomingMessage && _deviceSpecificEncryptionParameters) {
_countIncomingMessage(_rootPath, account.accountId, _deviceSpecificEncryptionParameters, peerId, messageId);
}*/
NSString *silentString = decryptedPayload[@"silent"];
if ([silentString isKindOfClass:[NSString class]]) {
silent = [silentString intValue] != 0;
}
NSData *attachmentData = nil;
id parsedAttachment = nil;
if (!_isLockedValue) {
NSString *attachmentDataString = decryptedPayload[@"attachb64"];
if ([attachmentDataString isKindOfClass:[NSString class]]) {
attachmentData = parseBase64(attachmentDataString);
if (attachmentData != nil) {
parsedAttachment = parseAttachment(attachmentData);
}
}
}
NSString *imagesPath = [NSTemporaryDirectory() stringByAppendingPathComponent:@"aps-data"];
[[NSFileManager defaultManager] createDirectoryAtPath:imagesPath withIntermediateDirectories:true attributes:nil error:nil];
NSString *accountBasePath = [_rootPath stringByAppendingPathComponent:[NSString stringWithFormat:@"account-%llud", account.accountId]];
NSString *mediaBoxPath = [accountBasePath stringByAppendingPathComponent:@"/postbox/media"];
NSString *tempImagePath = nil;
NSString *mediaBoxThumbnailImagePath = nil;
int32_t fileDatacenterId = 0;
Api1_InputFileLocation *inputFileLocation = nil;
NSString *fetchResourceId = nil;
bool isPng = false;
bool isExpandableMedia = false;
if (parsedAttachment != nil) {
if ([parsedAttachment isKindOfClass:[Api1_Photo_photo class]]) {
Api1_Photo_photo *photo = parsedAttachment;
isExpandableMedia = true;
for (id size in photo.sizes) {
if ([size isKindOfClass:[Api1_PhotoSize_photoSize class]]) {
Api1_PhotoSize_photoSize *sizeValue = size;
if ([sizeValue.type isEqualToString:@"m"]) {
inputFileLocation = [Api1_InputFileLocation inputPhotoFileLocationWithPid:photo.pid accessHash:photo.accessHash fileReference:photo.fileReference thumbSize:sizeValue.type];
fileDatacenterId = [photo.dcId intValue];
fetchResourceId = [NSString stringWithFormat:@"telegram-cloud-photo-size-%@-%@-%@", photo.dcId, photo.pid, sizeValue.type];
break;
}
}
}
} else if ([parsedAttachment isKindOfClass:[Api1_Document_document class]]) {
Api1_Document_document *document = parsedAttachment;
bool isSticker = false;
for (id attribute in document.attributes) {
if ([attribute isKindOfClass:[Api1_DocumentAttribute_documentAttributeSticker class]]) {
isSticker = true;
}
}
bool isAnimatedSticker = [document.mimeType isEqualToString:@"application/x-tgsticker"];
if (isSticker || isAnimatedSticker) {
isExpandableMedia = true;
}
for (id size in document.thumbs) {
if ([size isKindOfClass:[Api1_PhotoSize_photoSize class]]) {
Api1_PhotoSize_photoSize *photoSize = size;
if ((isSticker && [photoSize.type isEqualToString:@"s"]) || [photoSize.type isEqualToString:@"m"]) {
if (isSticker) {
isPng = true;
}
inputFileLocation = [Api1_InputFileLocation inputDocumentFileLocationWithPid:document.pid accessHash:document.accessHash fileReference:document.fileReference thumbSize:photoSize.type];
fileDatacenterId = [document.dcId intValue];
fetchResourceId = [NSString stringWithFormat:@"telegram-cloud-document-size-%@-%@-%@", document.dcId, document.pid, photoSize.type];
break;
}
}
}
}
}
if (fetchResourceId != nil) {
tempImagePath = [imagesPath stringByAppendingPathComponent:[NSString stringWithFormat:@"%@.%@", fetchResourceId, isPng ? @"png" : @"jpg"]];
mediaBoxThumbnailImagePath = [mediaBoxPath stringByAppendingPathComponent:fetchResourceId];
}
NSDictionary *aps = decryptedPayload[@"aps"];
if ([aps isKindOfClass:[NSDictionary class]]) {
id alert = aps[@"alert"];
if ([alert isKindOfClass:[NSDictionary class]]) {
NSDictionary *alertDict = alert;
NSString *title = alertDict[@"title"];
NSString *subtitle = alertDict[@"subtitle"];
NSString *body = alertDict[@"body"];
if (![title isKindOfClass:[NSString class]]) {
title = @"";
}
if (![subtitle isKindOfClass:[NSString class]]) {
subtitle = @"";
}
if (![body isKindOfClass:[NSString class]]) {
body = nil;
}
if (title.length != 0 && silent) {
title = [title stringByAppendingString:@" 🔕"];
}
_bestAttemptContent.title = title;
if (_isLockedValue) {
_bestAttemptContent.title = @"";
_bestAttemptContent.subtitle = @"";
if (_lockedMessageTextValue != nil) {
_bestAttemptContent.body = _lockedMessageTextValue;
} else {
_bestAttemptContent.body = @"^You have a new message";
}
} else {
_bestAttemptContent.subtitle = subtitle;
_bestAttemptContent.body = body;
}
} else if ([alert isKindOfClass:[NSString class]]) {
_bestAttemptContent.title = @"";
_bestAttemptContent.subtitle = @"";
if (_isLockedValue) {
if (_lockedMessageTextValue != nil) {
_bestAttemptContent.body = _lockedMessageTextValue;
} else {
_bestAttemptContent.body = @"^You have a new message";
}
} else {
_bestAttemptContent.body = alert;
}
}
if (_isLockedValue) {
_bestAttemptContent.threadIdentifier = @"locked";
} else {
NSString *threadIdString = aps[@"thread-id"];
if ([threadIdString isKindOfClass:[NSString class]]) {
_bestAttemptContent.threadIdentifier = threadIdString;
}
}
NSString *soundString = aps[@"sound"];
if ([soundString isKindOfClass:[NSString class]]) {
_bestAttemptContent.sound = [UNNotificationSound soundNamed:soundString];
}
if (_isLockedValue) {
_bestAttemptContent.categoryIdentifier = @"locked";
} else {
NSString *categoryString = aps[@"category"];
if ([categoryString isKindOfClass:[NSString class]]) {
_bestAttemptContent.categoryIdentifier = categoryString;
if (peerId != 0 && messageId != 0 && parsedAttachment != nil && attachmentData != nil) {
userInfo[@"peerId"] = @(peerId);
userInfo[@"messageId.namespace"] = @(0);
userInfo[@"messageId.id"] = @(messageId);
userInfo[@"media"] = [attachmentData base64EncodedStringWithOptions:0];
if (isExpandableMedia) {
if ([categoryString isEqualToString:@"r"]) {
_bestAttemptContent.categoryIdentifier = @"withReplyMedia";
} else if ([categoryString isEqualToString:@"m"]) {
_bestAttemptContent.categoryIdentifier = @"withMuteMedia";
}
}
}
}
if (accountInfos.accounts.count > 1) {
if (_bestAttemptContent.title.length != 0 && account.peerName.length != 0) {
_bestAttemptContent.title = [NSString stringWithFormat:@"%@ → %@", _bestAttemptContent.title, account.peerName];
}
}
}
}
_bestAttemptContent.userInfo = userInfo;
if (_cancelFetch) {
_cancelFetch();
_cancelFetch = nil;
}
if (mediaBoxThumbnailImagePath != nil && tempImagePath != nil && inputFileLocation != nil) {
NSData *data = [NSData dataWithContentsOfFile:mediaBoxThumbnailImagePath];
if (data != nil) {
NSData *tempData = data;
if (isPng) {
/*if let image = WebP.convert(fromWebP: data), let imageData = image.pngData() {
tempData = imageData
}*/
}
if ([tempData writeToFile:tempImagePath atomically:true]) {
UNNotificationAttachment *attachment = [UNNotificationAttachment attachmentWithIdentifier:@"image" URL:[NSURL fileURLWithPath:tempImagePath] options:nil error:nil];
if (attachment != nil) {
_bestAttemptContent.attachments = @[attachment];
}
}
[self completeWithBestAttemptContent];
} else {
BuildConfig *buildConfig = [[BuildConfig alloc] initWithBaseAppBundleId:_baseAppBundleId];
void (^serialDispatch)(dispatch_block_t) = _serialDispatch;
__weak typeof(self) weakSelf = self;
_cancelFetch = fetchImage(buildConfig, accountInfos.proxy, account, inputFileLocation, fileDatacenterId, ^(NSData * _Nullable data) {
serialDispatch(^{
__strong typeof(weakSelf) strongSelf = weakSelf;
if (strongSelf == nil) {
return;
}
if (strongSelf->_cancelFetch) {
strongSelf->_cancelFetch();
strongSelf->_cancelFetch = nil;
if (data != nil) {
[data writeToFile:mediaBoxThumbnailImagePath atomically:true];
NSData *tempData = data;
if (isPng) {
/*if let image = WebP.convert(fromWebP: data), let imageData = image.pngData() {
tempData = imageData
}*/
}
if ([tempData writeToFile:tempImagePath atomically:true]) {
UNNotificationAttachment *attachment = [UNNotificationAttachment attachmentWithIdentifier:@"image" URL:[NSURL fileURLWithPath:tempImagePath] options:nil error:nil];
if (attachment != nil) {
strongSelf->_bestAttemptContent.attachments = @[attachment];
}
}
}
[strongSelf completeWithBestAttemptContent];
}
});
});
}
} else {
[self completeWithBestAttemptContent];
}
} else {
[self completeWithBestAttemptContent];
}
}
- (void)serviceExtensionTimeWillExpire {
if (_cancelFetch) {
_cancelFetch();
_cancelFetch = nil;
}
if (_contentHandler) {
if(_bestAttemptContent) {
if (_updatedUnreadCount == nil) {
_updatedUnreadCount = @(-1);
}
[self completeWithBestAttemptContent];
}
_contentHandler = nil;
}
}
@end

View File

@@ -0,0 +1,11 @@
#import <Foundation/Foundation.h>
#import <MTProtoKit/MTProtoKit.h>
NS_ASSUME_NONNULL_BEGIN
@interface Serialization : NSObject <MTSerialization>
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,35 @@
#import "Serialization.h"
@implementation Serialization
- (NSUInteger)currentLayer {
return 120;
}
- (id _Nullable)parseMessage:(NSData * _Nullable)data {
return nil;
}
- (MTExportAuthorizationResponseParser _Nonnull)exportAuthorization:(int32_t)datacenterId data:(__autoreleasing NSData **)data {
return ^MTExportedAuthorizationData *(NSData *resultData) {
return nil;
};
}
- (NSData * _Nonnull)importAuthorization:(int32_t)authId bytes:(NSData * _Nonnull)bytes {
return [NSData data];
}
- (MTRequestDatacenterAddressListParser)requestDatacenterAddressWithData:(__autoreleasing NSData **)data {
return ^MTDatacenterAddressListData *(NSData *resultData) {
return nil;
};
}
- (MTRequestNoopParser)requestNoop:(__autoreleasing NSData **)data {
return ^id(NSData *resultData) {
return nil;
};
}
@end

View File

@@ -0,0 +1,67 @@
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface AccountNotificationKey: NSObject
@property (nonatomic, strong, readonly) NSData *keyId;
@property (nonatomic, strong, readonly) NSData *data;
@end
@interface AccountDatacenterKey: NSObject
@property (nonatomic, readonly) int64_t keyId;
@property (nonatomic, strong, readonly) NSData *data;
@end
@interface AccountDatacenterAddress: NSObject
@property (nonatomic, strong, readonly) NSString *host;
@property (nonatomic, readonly) int32_t port;
@property (nonatomic, readonly) bool isMedia;
@property (nonatomic, strong, readonly) NSData * _Nullable secret;
@end
@interface AccountDatacenterInfo: NSObject
@property (nonatomic, strong, readonly) AccountDatacenterKey *masterKey;
@property (nonatomic, strong, readonly) NSArray<AccountDatacenterAddress *> *addressList;
@end
@interface AccountProxyConnection: NSObject
@property (nonatomic, strong, readonly) NSString *host;
@property (nonatomic, readonly) int32_t port;
@property (nonatomic, strong) NSString * _Nullable username;
@property (nonatomic, strong) NSString * _Nullable password;
@property (nonatomic, strong) NSData * _Nullable secret;
@end
@interface StoredAccountInfo : NSObject
@property (nonatomic, readonly) int64_t accountId;
@property (nonatomic, readonly) int32_t primaryId;
@property (nonatomic, readonly) bool isTestingEnvironment;
@property (nonatomic, strong, readonly) NSString *peerName;
@property (nonatomic, strong, readonly) NSDictionary<NSNumber *, AccountDatacenterInfo *> *datacenters;
@property (nonatomic, strong, readonly) AccountNotificationKey *notificationKey;
@end
@interface StoredAccountInfos : NSObject
@property (nonatomic, strong, readonly) AccountProxyConnection * _Nullable proxy;
@property (nonatomic, strong, readonly) NSArray<StoredAccountInfo *> *accounts;
+ (StoredAccountInfos * _Nullable)loadFromPath:(NSString *)path;
@end
NSDictionary * _Nullable decryptedNotificationPayload(NSArray<StoredAccountInfo *> *accounts, NSData *data, int *selectedAccountIndex);
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,413 @@
#import "StoredAccountInfos.h"
#import <MTProtoKit/MTProtoKit.h>
#import <CommonCrypto/CommonDigest.h>
@implementation AccountNotificationKey
- (instancetype)initWithKeyId:(NSData *)keyId data:(NSData *)data {
self = [super init];
if (self != nil) {
_keyId = keyId;
_data = data;
}
return self;
}
+ (instancetype _Nullable)parse:(NSDictionary *)dict {
NSString *keyIdString = dict[@"id"];
NSData *keyId = nil;
if ([keyIdString isKindOfClass:[NSString class]]) {
keyId = [[NSData alloc] initWithBase64EncodedString:keyIdString options:0];
}
if (keyId == nil) {
return nil;
}
NSString *dataString = dict[@"data"];
NSData *data = nil;
if ([dataString isKindOfClass:[NSString class]]) {
data = [[NSData alloc] initWithBase64EncodedString:dataString options:0];
}
if (data == nil) {
return nil;
}
return [[AccountNotificationKey alloc] initWithKeyId:keyId data:data];
}
@end
@implementation AccountDatacenterKey
- (instancetype)initWithKeyId:(int64_t)keyId data:(NSData *)data {
self = [super init];
if (self != nil) {
_keyId = keyId;
_data = data;
}
return self;
}
+ (instancetype _Nullable)parse:(NSDictionary *)dict {
NSNumber *keyIdNumber = dict[@"id"];
if (![keyIdNumber isKindOfClass:[NSNumber class]]) {
return nil;
}
int64_t keyId = [keyIdNumber longLongValue];
NSString *dataString = dict[@"data"];
NSData *data = nil;
if ([dataString isKindOfClass:[NSString class]]) {
data = [[NSData alloc] initWithBase64EncodedString:dataString options:0];
}
if (data == nil) {
return nil;
}
return [[AccountDatacenterKey alloc] initWithKeyId:keyId data:data];
}
@end
@implementation AccountDatacenterAddress
- (instancetype)initWithHost:(NSString *)host port:(int32_t)port isMedia:(bool)isMedia secret:(NSData * _Nullable)secret {
self = [super init];
if (self != nil) {
_host = host;
_port = port;
_isMedia = isMedia;
_secret = secret;
}
return self;
}
+ (instancetype _Nullable)parse:(NSDictionary *)dict {
NSString *hostString = dict[@"host"];
if (![hostString isKindOfClass:[NSString class]]) {
return nil;
}
NSString *host = hostString;
NSNumber *portNumber = dict[@"port"];
if (![portNumber isKindOfClass:[NSNumber class]]) {
return nil;
}
int32_t port = [portNumber intValue];
NSNumber *isMediaNumber = dict[@"isMedia"];
if (![isMediaNumber isKindOfClass:[NSNumber class]]) {
return nil;
}
bool isMedia = [isMediaNumber intValue] != 0;
NSString *secretString = dict[@"secret"];
NSData *secret = nil;
if ([secretString isKindOfClass:[NSString class]]) {
secret = [[NSData alloc] initWithBase64EncodedString:secretString options:0];
}
return [[AccountDatacenterAddress alloc] initWithHost:host port:port isMedia:isMedia secret:secret];
}
@end
@implementation AccountDatacenterInfo
- (instancetype)initWithMasterKey:(AccountDatacenterKey *)masterKey addressList:(NSArray<AccountDatacenterAddress *> *)addressList {
self = [super init];
if (self != nil) {
_masterKey = masterKey;
_addressList = addressList;
}
return self;
}
+ (instancetype _Nullable)parse:(NSDictionary *)dict {
NSDictionary *masterKeyDict = dict[@"masterKey"];
if (![masterKeyDict isKindOfClass:[NSDictionary class]]) {
return nil;
}
AccountDatacenterKey *masterKey = [AccountDatacenterKey parse:masterKeyDict];
if (masterKey == nil) {
return nil;
}
NSArray *addressListArray = dict[@"addressList"];
if (![addressListArray isKindOfClass:[NSArray class]]) {
return nil;
}
NSMutableArray<AccountDatacenterAddress *> *addressList = [[NSMutableArray alloc] init];
for (NSDictionary *addressListItem in addressListArray) {
if (![addressListItem isKindOfClass:[NSDictionary class]]) {
return nil;
}
AccountDatacenterAddress *address = [AccountDatacenterAddress parse:addressListItem];
if (address == nil) {
return nil;
}
[addressList addObject:address];
}
return [[AccountDatacenterInfo alloc] initWithMasterKey:masterKey addressList:addressList];
}
@end
@implementation AccountProxyConnection
- (instancetype)initWithHost:(NSString *)host port:(int32_t)port username:(NSString * _Nullable)username password:(NSString * _Nullable)password secret:(NSData * _Nullable)secret {
self = [super init];
if (self != nil) {
_host = host;
_port = port;
username = _username;
password = _password;
secret = _secret;
}
return self;
}
+ (instancetype _Nullable)parse:(NSDictionary *)dict {
NSString *hostString = dict[@"host"];
if (![hostString isKindOfClass:[NSString class]]) {
return nil;
}
NSString *host = hostString;
NSNumber *portNumber = dict[@"port"];
if (![portNumber isKindOfClass:[NSNumber class]]) {
return nil;
}
int32_t port = [portNumber intValue];
NSString *usernameString = dict[@"username"];
NSString *username = nil;
if ([usernameString isKindOfClass:[NSString class]]) {
username = usernameString;
}
NSString *passwordString = dict[@"password"];
NSString *password = nil;
if ([passwordString isKindOfClass:[NSString class]]) {
password = passwordString;
}
NSString *secretString = dict[@"secret"];
NSData *secret = nil;
if ([secretString isKindOfClass:[NSString class]]) {
secret = [[NSData alloc] initWithBase64EncodedString:secretString options:0];
}
return [[AccountProxyConnection alloc] initWithHost:host port:port username:username password:password secret:secret];
}
@end
@implementation StoredAccountInfo
- (instancetype)initWithAccountId:(int64_t)accountId primaryId:(int32_t)primaryId isTestingEnvironment:(bool)isTestingEnvironment peerName:(NSString *)peerName datacenters:(NSDictionary<NSNumber *, AccountDatacenterInfo *> *)datacenters notificationKey:(AccountNotificationKey *)notificationKey {
self = [super init];
if (self != nil) {
_accountId = accountId;
_primaryId = primaryId;
_isTestingEnvironment = isTestingEnvironment;
_peerName = peerName;
_datacenters = datacenters;
_notificationKey = notificationKey;
}
return self;
}
+ (instancetype _Nullable)parse:(NSDictionary *)dict {
NSNumber *idNumber = dict[@"id"];
if (![idNumber isKindOfClass:[NSNumber class]]) {
return nil;
}
int64_t accountId = [idNumber longLongValue];
NSNumber *primaryIdNumber = dict[@"primaryId"];
if (![primaryIdNumber isKindOfClass:[NSNumber class]]) {
return nil;
}
int32_t primaryId = [primaryIdNumber intValue];
NSNumber *isTestingEnvironmentNumber = dict[@"isTestingEnvironment"];
if (![isTestingEnvironmentNumber isKindOfClass:[NSNumber class]]) {
return nil;
}
bool isTestingEnvironment = [isTestingEnvironmentNumber intValue] != 0;
NSString *peerNameString = dict[@"peerName"];
if (![peerNameString isKindOfClass:[NSString class]]) {
return nil;
}
NSString *peerName = peerNameString;
NSArray *datacentersArray = dict[@"datacenters"];
if (![datacentersArray isKindOfClass:[NSArray class]]) {
return nil;
}
NSMutableDictionary<NSNumber *, AccountDatacenterInfo *> *datacenters = [[NSMutableDictionary alloc] init];
for (NSInteger i = 0; i < datacentersArray.count; i += 2) {
NSNumber *datacenterKey = datacentersArray[i];
NSDictionary *datacenterData = datacentersArray[i + 1];
if (![datacenterKey isKindOfClass:[NSNumber class]]) {
return nil;
}
if (![datacenterData isKindOfClass:[NSDictionary class]]) {
return nil;
}
AccountDatacenterInfo *parsedDatacenter = [AccountDatacenterInfo parse:datacenterData];
if (parsedDatacenter != nil) {
datacenters[datacenterKey] = parsedDatacenter;
}
}
NSDictionary *notificationKeyDict = dict[@"notificationKey"];
if (![notificationKeyDict isKindOfClass:[NSDictionary class]]) {
return nil;
}
AccountNotificationKey *notificationKey = [AccountNotificationKey parse:notificationKeyDict];
if (notificationKey == nil) {
return nil;
}
return [[StoredAccountInfo alloc] initWithAccountId:accountId primaryId:primaryId isTestingEnvironment:isTestingEnvironment peerName:peerName datacenters:datacenters notificationKey:notificationKey];
}
@end
@implementation StoredAccountInfos
- (instancetype)initWithProxy:(AccountProxyConnection * _Nullable)proxy accounts:(NSArray<StoredAccountInfo *> *)accounts {
self = [super init];
if (self != nil) {
_proxy = proxy;
_accounts = accounts;
}
return self;
}
+ (StoredAccountInfos * _Nullable)loadFromPath:(NSString *)path {
NSData *data = [NSData dataWithContentsOfFile:path];
if (data == nil) {
return nil;
}
NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
if (![dict isKindOfClass:[NSDictionary class]]) {
return nil;
}
AccountProxyConnection * _Nullable proxy = nil;
NSDictionary *proxyDict = dict[@"proxy"];
if ([proxyDict isKindOfClass:[NSDictionary class]]) {
proxy = [AccountProxyConnection parse:proxyDict];
}
NSMutableArray<StoredAccountInfo *> *accounts = [[NSMutableArray alloc] init];
NSArray *accountsObject = dict[@"accounts"];
if ([accountsObject isKindOfClass:[NSArray class]]) {
for (NSDictionary *object in accountsObject) {
if ([object isKindOfClass:[NSDictionary class]]) {
StoredAccountInfo *account = [StoredAccountInfo parse:object];
if (account != nil) {
[accounts addObject:account];
}
}
}
}
return [[StoredAccountInfos alloc] initWithProxy:proxy accounts:accounts];;
}
@end
static NSData *sha256Digest(NSData *data) {
uint8_t digest[CC_SHA256_DIGEST_LENGTH];
CC_SHA256(data.bytes, (CC_LONG)data.length, digest);
return [[NSData alloc] initWithBytes:digest length:CC_SHA256_DIGEST_LENGTH];
}
static NSData *concatData(NSData *data1, NSData *data2) {
NSMutableData *data = [[NSMutableData alloc] initWithCapacity:data1.length + data2.length];
[data appendData:data1];
[data appendData:data2];
return data;
}
static NSData *concatData3(NSData *data1, NSData *data2, NSData *data3) {
NSMutableData *data = [[NSMutableData alloc] initWithCapacity:data1.length + data2.length + data3.length];
[data appendData:data1];
[data appendData:data2];
[data appendData:data3];
return data;
}
NSDictionary * _Nullable decryptedNotificationPayload(NSArray<StoredAccountInfo *> *accounts, NSData *data, int *selectedAccountIndex) {
if (data.length < 8 + 16) {
return nil;
}
int accountIndex = -1;
for (StoredAccountInfo *account in accounts) {
accountIndex += 1;
AccountNotificationKey *notificationKey = account.notificationKey;
if (![[data subdataWithRange:NSMakeRange(0, 8)] isEqualToData:notificationKey.keyId]) {
continue;
}
int x = 8;
NSData *msgKey = [data subdataWithRange:NSMakeRange(8, 16)];
NSData *rawData = [data subdataWithRange:NSMakeRange(8 + 16, data.length - (8 + 16))];
NSData *sha256_a = sha256Digest(concatData(msgKey, [notificationKey.data subdataWithRange:NSMakeRange(x, 36)]));
NSData *sha256_b = sha256Digest(concatData([notificationKey.data subdataWithRange:NSMakeRange(40 + x, 36)], msgKey));
NSData *aesKey = concatData3([sha256_a subdataWithRange:NSMakeRange(0, 8)], [sha256_b subdataWithRange:NSMakeRange(8, 16)], [sha256_a subdataWithRange:NSMakeRange(24, 8)]);
NSData *aesIv = concatData3([sha256_b subdataWithRange:NSMakeRange(0, 8)], [sha256_a subdataWithRange:NSMakeRange(8, 16)], [sha256_b subdataWithRange:NSMakeRange(24, 8)]);
NSData *decryptedData = MTAesDecrypt(rawData, aesKey, aesIv);
if (decryptedData.length <= 4) {
return nil;
}
int32_t dataLength = 0;
[decryptedData getBytes:&dataLength range:NSMakeRange(0, 4)];
if (dataLength < 0 || dataLength > decryptedData.length - 4) {
return nil;
}
NSData *checkMsgKeyLarge = sha256Digest(concatData([notificationKey.data subdataWithRange:NSMakeRange(88 + x, 32)], decryptedData));
NSData *checkMsgKey = [checkMsgKeyLarge subdataWithRange:NSMakeRange(8, 16)];
if (![checkMsgKey isEqualToData:msgKey]) {
return nil;
}
NSData *contentData = [decryptedData subdataWithRange:NSMakeRange(4, dataLength)];
NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:contentData options:0 error:nil];
if (![dict isKindOfClass:[NSDictionary class]]) {
return nil;
}
if (selectedAccountIndex != nil) {
*selectedAccountIndex = accountIndex;
}
return dict;
}
return nil;
}