Skip to content

Commit aebbcf5

Browse files
Add limits, input validation, and unit tests for custom signals
1 parent 8d0f566 commit aebbcf5

File tree

3 files changed

+139
-11
lines changed

3 files changed

+139
-11
lines changed

FirebaseRemoteConfig/Sources/FIRRemoteConfig.m

Lines changed: 49 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -245,53 +245,91 @@ - (void)setCustomSignals:(nullable NSDictionary<NSString *, NSObject *> *)custom
245245
void (^setCustomSignalsBlock)(void) = ^{
246246
if (!customSignals) {
247247
if (completionHandler) {
248-
dispatch_async(dispatch_get_main_queue(), ^{
248+
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
249249
completionHandler(nil);
250250
});
251251
}
252252
return;
253253
}
254254

255+
// Validate value type, and key and value length
255256
for (NSString *key in customSignals) {
256257
NSObject *value = customSignals[key];
257-
if (value && ![value isKindOfClass:[NSString class]] &&
258+
if (![value isKindOfClass:[NSNull class]] && ![value isKindOfClass:[NSString class]] &&
258259
![value isKindOfClass:[NSNumber class]]) {
259260
if (completionHandler) {
260-
dispatch_async(dispatch_get_main_queue(), ^{
261+
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
261262
NSError *error =
262263
[NSError errorWithDomain:FIRRemoteConfigCustomSignalsErrorDomain
263264
code:FIRRemoteConfigCustomSignalsErrorInvalidValueType
264265
userInfo:@{
265266
NSLocalizedDescriptionKey :
266-
@"Invalid value type. Must be NSString or NSNumber."
267+
@"Invalid value type. Must be NSString, NSNumber or NSNull"
267268
}];
268269
completionHandler(error);
269270
});
270271
}
271272
return;
272273
}
274+
275+
if (key.length > 250 ||
276+
([value isKindOfClass:[NSString class]] && [(NSString *)value length] > 500)) {
277+
if (completionHandler) {
278+
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
279+
NSError *error = [NSError errorWithDomain:FIRRemoteConfigCustomSignalsErrorDomain
280+
code:FIRRemoteConfigCustomSignalsErrorLimitExceeded
281+
userInfo:@{
282+
NSLocalizedDescriptionKey :
283+
@"Custom signal keys and string values must be "
284+
@"250 and 500 characters or less respectively."
285+
}];
286+
completionHandler(error);
287+
});
288+
}
289+
return;
290+
}
273291
}
274292

293+
// Merge new signals with existing ones, overwriting existing keys.
294+
// Also, remove entries where the new value is null.
275295
NSMutableDictionary<NSString *, NSObject *> *newCustomSignals =
276296
[[NSMutableDictionary alloc] initWithDictionary:self->_settings.customSignals];
277297

278298
for (NSString *key in customSignals) {
279299
NSObject *value = customSignals[key];
280-
if (value) {
300+
if (![value isKindOfClass:[NSNull class]]) {
281301
[newCustomSignals setObject:value forKey:key];
282302
} else {
283303
[newCustomSignals removeObjectForKey:key];
284304
}
285305
}
286306

287-
self->_settings.customSignals = newCustomSignals;
288-
if (completionHandler) {
289-
dispatch_async(dispatch_get_main_queue(), ^{
290-
completionHandler(nil);
291-
});
307+
// Check the size limit.
308+
if (newCustomSignals.count > 100) {
309+
if (completionHandler) {
310+
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
311+
NSError *error = [NSError
312+
errorWithDomain:FIRRemoteConfigCustomSignalsErrorDomain
313+
code:FIRRemoteConfigCustomSignalsErrorLimitExceeded
314+
userInfo:@{
315+
NSLocalizedDescriptionKey : @"Custom signals count exceeds the limit of 100."
316+
}];
317+
completionHandler(error);
318+
});
319+
}
320+
return;
292321
}
293-
};
294322

323+
// Update only if there are changes.
324+
if (![newCustomSignals isEqualToDictionary:self->_settings.customSignals]) {
325+
self->_settings.customSignals = newCustomSignals;
326+
if (completionHandler) {
327+
dispatch_async(dispatch_get_main_queue(), ^{
328+
completionHandler(nil);
329+
});
330+
}
331+
}
332+
};
295333
dispatch_async(_queue, setCustomSignalsBlock);
296334
}
297335

FirebaseRemoteConfig/Sources/Public/FirebaseRemoteConfig/FIRRemoteConfig.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,8 @@ typedef NS_ERROR_ENUM(FIRRemoteConfigCustomSignalsErrorDomain, FIRRemoteConfigCu
105105
FIRRemoteConfigCustomSignalsErrorUnknown = 8001,
106106
/// Invalid value type in the custom signals dictionary.
107107
FIRRemoteConfigCustomSignalsErrorInvalidValueType = 8002,
108+
/// Limit exceeded for key length, value length, or number of signals.
109+
FIRRemoteConfigCustomSignalsErrorLimitExceeded = 8003,
108110
} NS_SWIFT_NAME(RemoteConfigCustomSignalsError);
109111

110112
/// Enumerated value that indicates the source of Remote Config data. Data can come from

FirebaseRemoteConfig/Tests/Unit/RCNRemoteConfigTest.m

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1860,6 +1860,94 @@ - (void)testSetCustomSingals {
18601860
[self waitForExpectationsWithTimeout:_expectationTimeout handler:nil];
18611861
}
18621862

1863+
- (void)testSetCustomSignalsMultipleTimes {
1864+
NSMutableArray<XCTestExpectation *> *expectations =
1865+
[[NSMutableArray alloc] initWithCapacity:RCNTestRCNumTotalInstances];
1866+
1867+
for (int i = 0; i < RCNTestRCNumTotalInstances; i++) {
1868+
expectations[i] = [self
1869+
expectationWithDescription:
1870+
[NSString stringWithFormat:@"Set custom signals multiple times - instance %d", i]];
1871+
1872+
// First set of signals
1873+
NSDictionary<NSString *, NSObject *> *testSignals1 = @{
1874+
@"signal1" : @"stringValue1",
1875+
@"signal2" : @"stringValue2",
1876+
};
1877+
1878+
// Second set of signals (overwrites, remove and adds new)
1879+
NSDictionary<NSString *, NSObject *> *testSignals2 = @{
1880+
@"signal1" : @"updatedValue1",
1881+
@"signal2" : [NSNull null],
1882+
@"signal3" : @5,
1883+
};
1884+
1885+
// Expected final set of signals
1886+
NSDictionary<NSString *, NSObject *> *expectedSignals = @{
1887+
@"signal1" : @"updatedValue1",
1888+
@"signal3" : @5,
1889+
};
1890+
1891+
[_configInstances[i] setCustomSignals:testSignals1
1892+
WithCompletion:^(NSError *_Nullable error) {
1893+
XCTAssertNil(error);
1894+
[_configInstances[i]
1895+
setCustomSignals:testSignals2
1896+
WithCompletion:^(NSError *_Nullable error) {
1897+
XCTAssertNil(error);
1898+
NSMutableDictionary<NSString *, NSObject *> *retrievedSignals =
1899+
self->_configInstances[i].settings.customSignals;
1900+
XCTAssertEqualObjects(retrievedSignals, expectedSignals);
1901+
[expectations[i] fulfill];
1902+
}];
1903+
}];
1904+
}
1905+
[self waitForExpectationsWithTimeout:_expectationTimeout handler:nil];
1906+
}
1907+
1908+
- (void)testSetCustomSignals_invalidInput_throwsException {
1909+
NSMutableArray<XCTestExpectation *> *expectations =
1910+
[[NSMutableArray alloc] initWithCapacity:RCNTestRCNumTotalInstances];
1911+
1912+
for (int i = 0; i < RCNTestRCNumTotalInstances; i++) {
1913+
expectations[i] =
1914+
[self expectationWithDescription:
1915+
[NSString stringWithFormat:@"Set custom signals expects error - instance %d", i]];
1916+
1917+
// Invalid value type.
1918+
NSDictionary<NSString *, NSObject *> *invalidSignals1 = @{@"name" : [NSDate date]};
1919+
1920+
// Key length exceeds limit.
1921+
NSDictionary<NSString *, NSObject *> *invalidSignals2 =
1922+
@{[@"a" stringByPaddingToLength:251 withString:@"a" startingAtIndex:0] : @"value"};
1923+
1924+
// Value length exceeds limit.
1925+
NSDictionary<NSString *, NSObject *> *invalidSignals3 =
1926+
@{@"key" : [@"a" stringByPaddingToLength:501 withString:@"a" startingAtIndex:0]};
1927+
1928+
[_configInstances[i]
1929+
setCustomSignals:invalidSignals1
1930+
WithCompletion:^(NSError *_Nullable error) {
1931+
XCTAssertNotNil(error);
1932+
XCTAssertEqual(error.code, FIRRemoteConfigCustomSignalsErrorInvalidValueType);
1933+
}];
1934+
[_configInstances[i]
1935+
setCustomSignals:invalidSignals2
1936+
WithCompletion:^(NSError *_Nullable error) {
1937+
XCTAssertNotNil(error);
1938+
XCTAssertEqual(error.code, FIRRemoteConfigCustomSignalsErrorLimitExceeded);
1939+
}];
1940+
[_configInstances[i]
1941+
setCustomSignals:invalidSignals3
1942+
WithCompletion:^(NSError *_Nullable error) {
1943+
XCTAssertNotNil(error);
1944+
XCTAssertEqual(error.code, FIRRemoteConfigCustomSignalsErrorLimitExceeded);
1945+
[expectations[i] fulfill];
1946+
}];
1947+
}
1948+
[self waitForExpectationsWithTimeout:_expectationTimeout handler:nil];
1949+
}
1950+
18631951
#pragma mark - Test Helpers
18641952

18651953
- (FIROptions *)firstAppOptions {

0 commit comments

Comments
 (0)