238 lines
7.5 KiB
Objective-C
238 lines
7.5 KiB
Objective-C
//
|
|
// Copyright 2017 Signal Messenger, LLC
|
|
// SPDX-License-Identifier: AGPL-3.0-only
|
|
//
|
|
|
|
#import "TSGroupModel.h"
|
|
#import <SignalServiceKit/SignalServiceKit-Swift.h>
|
|
|
|
NS_ASSUME_NONNULL_BEGIN
|
|
|
|
const uint64_t kMaxEncryptedAvatarSize = 3 * 1024 * 1024;
|
|
const uint64_t kMaxAvatarSize = (kMaxEncryptedAvatarSize
|
|
/* The length of the padding. See GroupSecretParams:encrypt_blob_with_padding in LibSignal. */
|
|
- sizeof(uint32_t)
|
|
/* Overhead from GroupAttributeBlob (protobuf) via GroupV2Params.encryptGroupAvatar(_:). */
|
|
/* One byte for "2:LEN" & 4 bytes to represent more than 2 MiB of data (2^21). */
|
|
- 1
|
|
- 4
|
|
/* The padding bytes themselves. See ClientZkGroupCipher.encryptBlob(randomness:plaintext:). */
|
|
- 0
|
|
/* Encryption tag & nonce. See GroupSecretParams:encrypt_blob in LibSignal. */
|
|
- 16
|
|
- 12
|
|
/* Reserved byte. See GroupSecretParams:encrypt_blob in LibSignal. */
|
|
- 1);
|
|
const NSUInteger kGroupIdLengthV1 = 16;
|
|
const NSUInteger kGroupIdLengthV2 = 32;
|
|
|
|
NSUInteger const TSGroupModelSchemaVersion = 2;
|
|
|
|
@interface TSGroupModel ()
|
|
|
|
@property (nonatomic, readonly) NSUInteger groupModelSchemaVersion;
|
|
|
|
@end
|
|
|
|
#pragma mark -
|
|
|
|
@implementation TSGroupModel
|
|
|
|
@synthesize groupName = _groupName;
|
|
|
|
#if TARGET_OS_IOS
|
|
|
|
- (instancetype)initWithGroupId:(NSData *)groupId
|
|
name:(nullable NSString *)name
|
|
avatarData:(nullable NSData *)avatarData
|
|
members:(NSArray<SignalServiceAddress *> *)members
|
|
addedByAddress:(nullable SignalServiceAddress *)addedByAddress
|
|
{
|
|
self = [super init];
|
|
if (!self) {
|
|
return self;
|
|
}
|
|
|
|
_groupId = groupId;
|
|
_groupName = name;
|
|
_groupMembers = members;
|
|
_addedByAddress = addedByAddress;
|
|
_groupModelSchemaVersion = TSGroupModelSchemaVersion;
|
|
|
|
if (avatarData) {
|
|
NSError *error;
|
|
[self persistAvatarData:avatarData error:&error];
|
|
if (error) {
|
|
OWSFailDebug(@"Failed to persist group avatar data %@", error);
|
|
}
|
|
}
|
|
|
|
OWSAssertDebug([GroupManager isValidGroupId:groupId groupsVersion:self.groupsVersion]);
|
|
|
|
return self;
|
|
}
|
|
|
|
+ (BOOL)supportsSecureCoding
|
|
{
|
|
return YES;
|
|
}
|
|
|
|
- (void)encodeWithCoder:(NSCoder *)coder
|
|
{
|
|
SignalServiceAddress *addedByAddress = self.addedByAddress;
|
|
if (addedByAddress != nil) {
|
|
[coder encodeObject:addedByAddress forKey:@"addedByAddress"];
|
|
}
|
|
NSString *avatarHash = self.avatarHash;
|
|
if (avatarHash != nil) {
|
|
[coder encodeObject:avatarHash forKey:@"avatarHash"];
|
|
}
|
|
NSData *groupId = self.groupId;
|
|
if (groupId != nil) {
|
|
[coder encodeObject:groupId forKey:@"groupId"];
|
|
}
|
|
if (![self isKindOfClass:[TSGroupModelV2 class]]) {
|
|
NSArray *groupMembers = self.groupMembers;
|
|
if (groupMembers != nil) {
|
|
[coder encodeObject:groupMembers forKey:@"groupMembers"];
|
|
}
|
|
}
|
|
[coder encodeObject:[self valueForKey:@"groupModelSchemaVersion"] forKey:@"groupModelSchemaVersion"];
|
|
NSString *groupName = self.groupName;
|
|
if (groupName != nil) {
|
|
[coder encodeObject:groupName forKey:@"groupName"];
|
|
}
|
|
NSData *legacyAvatarData = self.legacyAvatarData;
|
|
if (legacyAvatarData != nil) {
|
|
[coder encodeObject:legacyAvatarData forKey:@"legacyAvatarData"];
|
|
}
|
|
}
|
|
|
|
- (nullable instancetype)initWithCoder:(NSCoder *)coder
|
|
{
|
|
self = [super init];
|
|
if (!self) {
|
|
return self;
|
|
}
|
|
self->_addedByAddress = [coder decodeObjectOfClass:[SignalServiceAddress class] forKey:@"addedByAddress"];
|
|
self->_avatarHash = [coder decodeObjectOfClass:[NSString class] forKey:@"avatarHash"];
|
|
self->_groupId = [coder decodeObjectOfClass:[NSData class] forKey:@"groupId"];
|
|
self->_groupMembers =
|
|
[coder decodeObjectOfClasses:[NSSet setWithArray:@[ [NSArray class], [SignalServiceAddress class] ]]
|
|
forKey:@"groupMembers"];
|
|
self->_groupModelSchemaVersion =
|
|
[(NSNumber *)[coder decodeObjectOfClass:[NSNumber class]
|
|
forKey:@"groupModelSchemaVersion"] unsignedIntegerValue];
|
|
self->_groupName = [coder decodeObjectOfClass:[NSString class] forKey:@"groupName"];
|
|
self->_legacyAvatarData = [coder decodeObjectOfClass:[NSData class] forKey:@"legacyAvatarData"];
|
|
|
|
OWSAssertDebug([GroupManager isValidGroupId:self.groupId groupsVersion:self.groupsVersion]);
|
|
|
|
if (_groupModelSchemaVersion < 1) {
|
|
NSArray<NSString *> *_Nullable memberE164s =
|
|
[coder decodeObjectOfClasses:[NSSet setWithArray:@[ [NSArray class], [NSString class] ]]
|
|
forKey:@"groupMemberIds"];
|
|
if (memberE164s) {
|
|
NSMutableArray<SignalServiceAddress *> *memberAddresses = [NSMutableArray new];
|
|
for (NSString *phoneNumber in memberE164s) {
|
|
[memberAddresses addObject:[SignalServiceAddress legacyAddressWithServiceIdString:nil
|
|
phoneNumber:phoneNumber]];
|
|
}
|
|
_groupMembers = [memberAddresses copy];
|
|
} else {
|
|
_groupMembers = @[];
|
|
}
|
|
}
|
|
|
|
if (_groupModelSchemaVersion < 2) {
|
|
_legacyAvatarData = [coder decodeObjectOfClass:[NSData class] forKey:@"groupAvatarData"];
|
|
}
|
|
|
|
_groupModelSchemaVersion = TSGroupModelSchemaVersion;
|
|
|
|
return self;
|
|
}
|
|
|
|
- (NSUInteger)hash
|
|
{
|
|
NSUInteger result = 0;
|
|
result ^= self.addedByAddress.hash;
|
|
result ^= self.avatarHash.hash;
|
|
result ^= self.groupId.hash;
|
|
result ^= self.groupMembers.hash;
|
|
result ^= self.groupModelSchemaVersion;
|
|
result ^= self.groupName.hash;
|
|
result ^= self.legacyAvatarData.hash;
|
|
return result;
|
|
}
|
|
|
|
- (BOOL)isEqual:(id)other
|
|
{
|
|
if (![other isMemberOfClass:self.class]) {
|
|
return NO;
|
|
}
|
|
TSGroupModel *typedOther = (TSGroupModel *)other;
|
|
if (![NSObject isObject:self.addedByAddress equalToObject:typedOther.addedByAddress]) {
|
|
return NO;
|
|
}
|
|
if (![NSObject isObject:self.avatarHash equalToObject:typedOther.avatarHash]) {
|
|
return NO;
|
|
}
|
|
if (![NSObject isObject:self.groupId equalToObject:typedOther.groupId]) {
|
|
return NO;
|
|
}
|
|
if (![NSObject isObject:self.groupMembers equalToObject:typedOther.groupMembers]) {
|
|
return NO;
|
|
}
|
|
if (self.groupModelSchemaVersion != typedOther.groupModelSchemaVersion) {
|
|
return NO;
|
|
}
|
|
if (![NSObject isObject:self.groupName equalToObject:typedOther.groupName]) {
|
|
return NO;
|
|
}
|
|
if (![NSObject isObject:self.legacyAvatarData equalToObject:typedOther.legacyAvatarData]) {
|
|
return NO;
|
|
}
|
|
return YES;
|
|
}
|
|
|
|
- (id)copyWithZone:(nullable NSZone *)zone
|
|
{
|
|
TSGroupModel *result = [[[self class] allocWithZone:zone] init];
|
|
result->_addedByAddress = self.addedByAddress;
|
|
result->_avatarHash = self.avatarHash;
|
|
result->_groupId = self.groupId;
|
|
result->_groupMembers = self.groupMembers;
|
|
result->_groupModelSchemaVersion = self.groupModelSchemaVersion;
|
|
result->_groupName = self.groupName;
|
|
result->_legacyAvatarData = self.legacyAvatarData;
|
|
return result;
|
|
}
|
|
|
|
- (GroupsVersion)groupsVersion
|
|
{
|
|
return GroupsVersionV1;
|
|
}
|
|
|
|
- (GroupMembership *)groupMembership
|
|
{
|
|
return [[GroupMembership alloc] initWithV1Members:self.groupMembers];
|
|
}
|
|
|
|
#endif
|
|
|
|
- (nullable NSString *)groupName
|
|
{
|
|
return _groupName.filterStringForDisplay;
|
|
}
|
|
|
|
- (NSString *)groupNameOrDefault
|
|
{
|
|
NSString *_Nullable groupName = [self.groupName filterStringForDisplay];
|
|
return groupName.length > 0 ? groupName : TSGroupThread.defaultGroupName;
|
|
}
|
|
|
|
@end
|
|
|
|
NS_ASSUME_NONNULL_END
|