From c26aff31c706f4fa296dd83ae1f6c6b21ce49bab Mon Sep 17 00:00:00 2001 From: Pete Markowsky Date: Mon, 20 Sep 2021 17:49:29 -0400 Subject: [PATCH 01/14] Initial commit of santametricservice. The santametricservice is an XPC helper service to write metrics. It consists of Formatters and Writers. This initial commit only has support for the rawJSON format and writing to a file. This is a new daemon to be included. Docs and packaging will be updated in future PRs. --- Source/santametricservice/BUILD | 54 ++++ Source/santametricservice/Formats/BUILD | 45 ++++ .../Formats/SNTMetricFormat.h | 19 ++ .../Formats/SNTMetricRawJsonFormat.h | 21 ++ .../Formats/SNTMetricRawJsonFormat.m | 98 ++++++++ .../Formats/SNTMetricRawJsonFormatTest.m | 155 ++++++++++++ .../Formats/testdata/json/test.json | 111 +++++++++ Source/santametricservice/Info.plist | 26 ++ Source/santametricservice/SNTMetricService.h | 20 ++ Source/santametricservice/SNTMetricService.m | 121 +++++++++ .../santametricservice/SNTMetricServiceTest.m | 232 ++++++++++++++++++ Source/santametricservice/Writers/BUILD | 35 +++ .../Writers/SNTMetricFileWriter.h | 17 ++ .../Writers/SNTMetricFileWriter.m | 77 ++++++ .../Writers/SNTMetricFileWriterTest.m | 101 ++++++++ .../Writers/SNTMetricWriter.h | 23 ++ Source/santametricservice/main.m | 35 +++ docs/deployment/configuration.md | 2 +- 18 files changed, 1191 insertions(+), 1 deletion(-) create mode 100644 Source/santametricservice/BUILD create mode 100644 Source/santametricservice/Formats/BUILD create mode 100644 Source/santametricservice/Formats/SNTMetricFormat.h create mode 100644 Source/santametricservice/Formats/SNTMetricRawJsonFormat.h create mode 100644 Source/santametricservice/Formats/SNTMetricRawJsonFormat.m create mode 100644 Source/santametricservice/Formats/SNTMetricRawJsonFormatTest.m create mode 100644 Source/santametricservice/Formats/testdata/json/test.json create mode 100644 Source/santametricservice/Info.plist create mode 100644 Source/santametricservice/SNTMetricService.h create mode 100644 Source/santametricservice/SNTMetricService.m create mode 100644 Source/santametricservice/SNTMetricServiceTest.m create mode 100644 Source/santametricservice/Writers/BUILD create mode 100644 Source/santametricservice/Writers/SNTMetricFileWriter.h create mode 100644 Source/santametricservice/Writers/SNTMetricFileWriter.m create mode 100644 Source/santametricservice/Writers/SNTMetricFileWriterTest.m create mode 100644 Source/santametricservice/Writers/SNTMetricWriter.h create mode 100644 Source/santametricservice/main.m diff --git a/Source/santametricservice/BUILD b/Source/santametricservice/BUILD new file mode 100644 index 000000000..991d1ff37 --- /dev/null +++ b/Source/santametricservice/BUILD @@ -0,0 +1,54 @@ +load("@build_bazel_rules_apple//apple:macos.bzl", "macos_command_line_application") +load("//:helper.bzl", "santa_unit_test") + +package(default_visibility = ["//:santa_package_group"]) + +licenses(["notice"]) # Apache 2.0 + +objc_library( + name = "SNTMetricServiceLib", + srcs = [ + "SNTMetricService.h", + "SNTMetricService.m", + "main.m", + ], + deps = [ + "//Source/common:SNTXPCMetricServiceInterface", + "//Source/common:SNTLogging", + "//Source/common:SNTConfigurator", + "//Source/common:SNTMetricSet", + "//Source/santametricservice/Formats:SNTMetricRawJsonFormat", + "//Source/santametricservice/Writers:SNTMetricFileWriter", + "@MOLCodesignChecker", + "@MOLXPCConnection", + ], +) + +santa_unit_test( + name="SNTMetricServiceTest", + srcs = ["SNTMetricServiceTest.m"], + deps = [ + ":SNTMetricServiceLib", + "@OCMock", + ], +) + +test_suite( + name="unit_tests", + tests = [ + ":SNTMetricServiceTest", + "//Source/santametricservice/Formats:SNTMetricRawJsonFormatTest", + "//Source/santametricservice/Writers:SNTMetricFileWriterTest", + ], +) + + +macos_command_line_application( + name = "santametricservice", + bundle_id = "com.google.santa.metricservice", + infoplists = ["Info.plist"], + minimum_os_version = "10.15", + version = "//:version", + visibility = ["//:santa_package_group"], + deps = [":SNTMetricServiceLib"], +) diff --git a/Source/santametricservice/Formats/BUILD b/Source/santametricservice/Formats/BUILD new file mode 100644 index 000000000..b24f90ccf --- /dev/null +++ b/Source/santametricservice/Formats/BUILD @@ -0,0 +1,45 @@ +load("@build_bazel_rules_apple//apple:macos.bzl", "macos_command_line_application") +load("//:helper.bzl", "santa_unit_test") + +package(default_visibility = ["//:santa_package_group"]) + +licenses(["notice"]) # Apache 2.0 + +objc_library( + name = "SNTMetricFormat", + hdrs = ["SNTMetricFormat.h"], +) + +objc_library( + name = "SNTMetricRawJsonFormat", + srcs = [ + "SNTMetricRawJsonFormat.h", + "SNTMetricRawJsonFormat.m", + "SNTMetricFormat.h", + + ], + deps = [ + ":SNTMetricFormat", + "//Source/common:SNTLogging", + ], +) + +santa_unit_test( + name = "SNTMetricRawJsonFormatTest", + srcs = [ + "SNTMetricRawJsonFormatTest.m", + ], + structured_resources = glob(["testdata/**"]), + deps = [ + ":SNTMetricRawJsonFormat", + "//Source/common:SNTMetricSet", + ], +) + + +test_suite( + name = "format_tests", + tests = [ + ":SNTMetricRawJsonFormatTest", + ], +) diff --git a/Source/santametricservice/Formats/SNTMetricFormat.h b/Source/santametricservice/Formats/SNTMetricFormat.h new file mode 100644 index 000000000..9ce6a06b7 --- /dev/null +++ b/Source/santametricservice/Formats/SNTMetricFormat.h @@ -0,0 +1,19 @@ +/// Copyright 2021 Google Inc. All rights reserved. +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. + +#import + +@protocol SNTMetricFormat +- (NSArray *)convert:(NSDictionary *)metrics error:(NSError **)err; +@end diff --git a/Source/santametricservice/Formats/SNTMetricRawJsonFormat.h b/Source/santametricservice/Formats/SNTMetricRawJsonFormat.h new file mode 100644 index 000000000..913662ca5 --- /dev/null +++ b/Source/santametricservice/Formats/SNTMetricRawJsonFormat.h @@ -0,0 +1,21 @@ +/// Copyright 2021 Google Inc. All rights reserved. +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. + +#import + +#import "Source/santametricservice/Formats/SNTMetricFormat.h" + +@interface SNTMetricRawJsonFormat : NSObject +- (NSArray *) convert:(NSDictionary *)metrics error:(NSError **)err; +@end diff --git a/Source/santametricservice/Formats/SNTMetricRawJsonFormat.m b/Source/santametricservice/Formats/SNTMetricRawJsonFormat.m new file mode 100644 index 000000000..528bdbee6 --- /dev/null +++ b/Source/santametricservice/Formats/SNTMetricRawJsonFormat.m @@ -0,0 +1,98 @@ +/// Copyright 2021 Google Inc. All rights reserved. +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +#import "Source/common/SNTLogging.h" + +#import "Source/santametricservice/Formats/SNTMetricRawJsonFormat.h" + +@implementation SNTMetricRawJsonFormat { + NSDateFormatter *dateFormatter; +} + +- (instancetype)init +{ + self = [super init]; + if (self) { + dateFormatter = [[NSDateFormatter alloc] init]; + [dateFormatter setDateFormat:@"yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"]; + } + return self; +} + +- (NSArray *)normalizeArray:(NSArray *)arr +{ + NSMutableArray *normalized = [NSMutableArray arrayWithArray: arr]; + + for (int i = 0; i < [arr count];i++) { + if ([arr[i] isKindOfClass: [NSArray class]]) { + normalized[i] = [self normalizeArray: (NSArray *)arr[i]]; + } else if ([arr[i] isKindOfClass: [NSDictionary class]]) { + normalized[i] = [self normalize: (NSDictionary *)arr[i]]; + } + } + + return normalized; +} + +/** + * Normalizes the metrics dictionary for exporting to JSON + **/ +- (NSDictionary *)normalize: (NSDictionary *)metrics +{ + // Convert NSDate's to RFC3339 in strings as NSDate's cannot be serialized + // to JSON. + NSMutableDictionary *normalizedMetrics = [NSMutableDictionary dictionaryWithDictionary:metrics]; + + for (NSString *key in metrics) { + const id object = [metrics objectForKey: key]; + if ([object isKindOfClass: [NSDate class]]) { + normalizedMetrics[key] = [self->dateFormatter stringFromDate: (NSDate *)object]; + } else if ([object isKindOfClass: [NSDictionary class]]) { + normalizedMetrics[key] = [self normalize: metrics[key]]; + } else if ([object isKindOfClass: [NSArray class]]) { + normalizedMetrics[key] = [self normalizeArray: (NSArray *)object]; + } + } + + return (NSDictionary *)normalizedMetrics; +} + +/* + * Convert normalies and converts the metrics dictionary to a single JSON + * object. + * + * @param metrics an NSDictionary exported by the SNTMetricSet + * @param error a pointer to an NSError to allow errors to bubble up. + * + * Returns an NSArray containing one entry of all metrics serialized to JSON or + * nil on error. + */ +- (NSArray *) convert:(NSDictionary *)metrics error:(NSError **)err +{ + NSDictionary *normalizedMetrics = [self normalize: metrics]; + + if (![NSJSONSerialization isValidJSONObject: normalizedMetrics]) { + LOGE(@"unable to convert metrics to JSON: invalid metrics"); + return nil; + } + + NSData *json = [NSJSONSerialization dataWithJSONObject: normalizedMetrics + options: NSJSONWritingPrettyPrinted + error: err]; + if (json == nil && *err != nil) { + return nil; + } + + return @[ json ]; +} +@end \ No newline at end of file diff --git a/Source/santametricservice/Formats/SNTMetricRawJsonFormatTest.m b/Source/santametricservice/Formats/SNTMetricRawJsonFormatTest.m new file mode 100644 index 000000000..f1fba7482 --- /dev/null +++ b/Source/santametricservice/Formats/SNTMetricRawJsonFormatTest.m @@ -0,0 +1,155 @@ +#import + +#import "Source/common/SNTMetricSet.h" +#import "Source/santametricservice/Formats/SNTMetricRawJsonFormat.h" + +NSDictionary *validMetricsDict = nil; + +@interface SNTMetricRawJsonFormatTest : XCTestCase +@end + +@implementation SNTMetricRawJsonFormatTest + +- (void)initializeValidMetricsDict +{ + NSDateFormatter *formatter = NSDateFormatter.new; + [formatter setDateFormat:@"yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"]; + NSDate *fixedDate = [formatter dateFromString:@"2021-09-16T21:07:34.826Z"]; + + validMetricsDict = @{ + @"root_labels" : @{@"hostname" : @"testHost", @"username" : @"testUser"}, + @"metrics" : @{ + @"/build/label" : @{ + @"type" : [NSNumber numberWithInt:(int)SNTMetricTypeConstantString], + @"fields" : @{ + @"" : @[ @{ + @"value" : @"", + @"created" : fixedDate, + @"last_updated" : fixedDate, + @"data" : @"20210809.0.1" + } ] + } + }, + @"/santa/events" : @{ + @"type" : [NSNumber numberWithInt:(int)SNTMetricTypeCounter], + @"fields" : @{ + @"rule_type" : @[ + @{ + @"value" : @"binary", + @"created" : fixedDate, + @"last_updated" : fixedDate, + @"data" : @1, + }, + @{ + @"value" : @"certificate", + @"created" : fixedDate, + @"last_updated" : fixedDate, + @"data" : @2, + }, + ], + }, + }, + @"/santa/rules" : @{ + @"type" : [NSNumber numberWithInt:(int)SNTMetricTypeGaugeInt64], + @"fields" : @{ + @"rule_type" : @[ + @{ + @"value" : @"binary", + @"created" : fixedDate, + @"last_updated" : fixedDate, + @"data" : @1 + }, + @{ + @"value" : @"certificate", + @"created" : fixedDate, + @"last_updated" : fixedDate, + @"data" : @3 + } + ] + }, + }, + @"/santa/using_endpoint_security_framework" : @{ + @"type" : [NSNumber numberWithInt:(int)SNTMetricTypeConstantBool], + @"fields" : @{ + @"" : @[ @{ + @"value" : @"", + @"created" : fixedDate, + @"last_updated" : fixedDate, + @"data" : [NSNumber numberWithBool:YES] + } ] + } + }, + @"/proc/birth_timestamp" : @{ + @"type" : [NSNumber numberWithInt:(int)SNTMetricTypeConstantInt64], + @"fields" : @{ + @"" : @[ @{ + @"value" : @"", + @"created" : fixedDate, + @"last_updated" : fixedDate, + @"data" : [NSNumber numberWithLong:1250999830800] + } ] + }, + }, + @"/proc/memory/virtual_size" : @{ + @"type" : [NSNumber numberWithInt:(int)SNTMetricTypeGaugeInt64], + @"fields" : @{ + @"" : @[ @{ + @"value" : @"", + @"created" : fixedDate, + @"last_updated" : fixedDate, + @"data" : [NSNumber numberWithInt:987654321] + } ] + } + }, + @"/proc/memory/resident_size" : @{ + @"type" : [NSNumber numberWithInt:(int)SNTMetricTypeGaugeInt64], + @"fields" : @{ + @"" : @[ @{ + @"value" : @"", + @"created" : fixedDate, + @"last_updated" : fixedDate, + @"data" : [NSNumber numberWithInt:123456789] + } ] + }, + }, + } + }; +} + +- (void)setUp +{ + [self initializeValidMetricsDict]; +} + +- (void)testMetricsConversionToJSON +{ + SNTMetricRawJsonFormat *formatter = [[SNTMetricRawJsonFormat alloc] init]; + NSError *err = nil; + NSArray *output = [formatter convert:validMetricsDict error: &err]; + + XCTAssertEqual(1, [output count]); + XCTAssertNotNil(output[0]); + XCTAssertNil(err); + + NSDictionary *jsonDict = [NSJSONSerialization JSONObjectWithData:output[0] + options:NSJSONReadingAllowFragments + error:&err]; + XCTAssertNotNil(jsonDict); + + NSString *path = [[NSBundle bundleForClass:[self class]] resourcePath]; + path = [path stringByAppendingPathComponent:@"testdata/json/test.json"]; + + NSFileManager *filemgr = [NSFileManager defaultManager]; + NSData *goldenFileData = [filemgr contentsAtPath: path]; + + XCTAssertNotNil(goldenFileData, @"unable to open / read golden file"); + + NSDictionary *expectedJsonDict = [NSJSONSerialization JSONObjectWithData:goldenFileData + options:NSJSONReadingAllowFragments + error: &err]; + + XCTAssertNotNil(expectedJsonDict); + XCTAssertEqualObjects(expectedJsonDict, jsonDict, @"generated JSON does not match golden file."); +} + +@end \ No newline at end of file diff --git a/Source/santametricservice/Formats/testdata/json/test.json b/Source/santametricservice/Formats/testdata/json/test.json new file mode 100644 index 000000000..36ddbbe45 --- /dev/null +++ b/Source/santametricservice/Formats/testdata/json/test.json @@ -0,0 +1,111 @@ +{ + "metrics" : { + "/santa/rules" : { + "type" : 7, + "fields" : { + "rule_type" : [ + { + "value" : "binary", + "created" : "2021-09-16T21:07:34.826Z", + "last_updated" : "2021-09-16T21:07:34.826Z", + "data" : 1 + }, + { + "value" : "certificate", + "created" : "2021-09-16T21:07:34.826Z", + "last_updated" : "2021-09-16T21:07:34.826Z", + "data" : 3 + } + ] + } + }, + "/santa/events" : { + "type" : 9, + "fields" : { + "rule_type" : [ + { + "value" : "binary", + "created" : "2021-09-16T21:07:34.826Z", + "last_updated" : "2021-09-16T21:07:34.826Z", + "data" : 1 + }, + { + "value" : "certificate", + "created" : "2021-09-16T21:07:34.826Z", + "last_updated" : "2021-09-16T21:07:34.826Z", + "data" : 2 + } + ] + } + }, + "/proc/memory/resident_size" : { + "type" : 7, + "fields" : { + "" : [ + { + "value" : "", + "created" : "2021-09-16T21:07:34.826Z", + "last_updated" : "2021-09-16T21:07:34.826Z", + "data" : 123456789 + } + ] + } + }, + "/build/label" : { + "type" : 2, + "fields" : { + "" : [ + { + "value" : "", + "created" : "2021-09-16T21:07:34.826Z", + "last_updated" : "2021-09-16T21:07:34.826Z", + "data" : "20210809.0.1" + } + ] + } + }, + "/proc/birth_timestamp" : { + "type" : 3, + "fields" : { + "" : [ + { + "value" : "", + "created" : "2021-09-16T21:07:34.826Z", + "last_updated" : "2021-09-16T21:07:34.826Z", + "data" : 1250999830800 + } + ] + } + }, + "/proc/memory/virtual_size" : { + "type" : 7, + "fields" : { + "" : [ + { + "value" : "", + "created" : "2021-09-16T21:07:34.826Z", + "last_updated" : "2021-09-16T21:07:34.826Z", + "data" : 987654321 + } + ] + } + }, + "/santa/using_endpoint_security_framework" : { + "type" : 1, + "fields" : { + "" : [ + { + "value" : "", + "created" : "2021-09-16T21:07:34.826Z", + "last_updated" : "2021-09-16T21:07:34.826Z", + "data" : true + } + ] + } + } + }, + "root_labels" : { + "hostname" : "testHost", + "username" : "testUser" + } +} diff --git a/Source/santametricservice/Info.plist b/Source/santametricservice/Info.plist new file mode 100644 index 000000000..52f9026d2 --- /dev/null +++ b/Source/santametricservice/Info.plist @@ -0,0 +1,26 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleDisplayName + santametricservice + CFBundleExecutable + santametricservice + CFBundleIdentifier + com.google.santa.metricservice + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + santametricservice + CFBundleShortVersionString + ${SANTA_VERSION} + CFBundleSignature + ???? + CFBundleVersion + ${SANTA_VERSION} + NSHumanReadableCopyright + Google LLC. + + diff --git a/Source/santametricservice/SNTMetricService.h b/Source/santametricservice/SNTMetricService.h new file mode 100644 index 000000000..a8830842c --- /dev/null +++ b/Source/santametricservice/SNTMetricService.h @@ -0,0 +1,20 @@ +/// Copyright 2021 Google Inc. All rights reserved. +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. + +#import + +#import "Source/common/SNTXPCMetricServiceInterface.h" + +@interface SNTMetricService : NSObject +@end diff --git a/Source/santametricservice/SNTMetricService.m b/Source/santametricservice/SNTMetricService.m new file mode 100644 index 000000000..b8c38b0b6 --- /dev/null +++ b/Source/santametricservice/SNTMetricService.m @@ -0,0 +1,121 @@ +/// Copyright 2021 Google Inc. All rights reserved. +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. + +#include +#include + +#import "Source/common/SNTConfigurator.h" +#import "Source/common/SNTLogging.h" + +#import "Source/santametricservice/Formats/SNTMetricRawJsonFormat.h" +#import "Source/santametricservice/Writers/SNTMetricFileWriter.h" +#import "SNTMetricService.h" + +@interface SNTMetricService () +@property MOLXPCConnection *notifierConnection; +@property MOLXPCConnection *listener; +@property(nonatomic) dispatch_queue_t queue; +@end + +@implementation SNTMetricService { + @private + SNTMetricRawJsonFormat* rawJsonFormatter; + NSDictionary *metricWriters; +} + +- (instancetype)init { + self = [super init]; + + rawJsonFormatter = [[SNTMetricRawJsonFormat alloc] init]; + metricWriters = @{@"file": [[SNTMetricFileWriter alloc] init]}; + + _queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0); + return self; +} + +/** + * Helper function to format NSError's for logging error messages. + */ +- (NSString *)messageFromError:(NSError *)error +{ + NSString *message = [error localizedDescription]; + NSString *details = [error localizedFailureReason] ? + [error localizedFailureReason] : @""; + + return [NSString stringWithFormat:@"%@ %@", message, details]; +} + + +/** + * Converts the exported Metrics dicitionary to the appropriate monitoring + * format. + * + * @param metrics NSDictionary containing the exported metrics + * @param format SNTMetricFormatType the exported metrics format + * @return An array of metrics formatted according to the specified format or + * nil on error; + */ +- (NSArray *) convertMetrics:(NSDictionary *)metrics + toFormat:(SNTMetricFormatType)format + error:(NSError **)err +{ + switch (format) { + case SNTMetricFormatTypeRawJSON: + return [self->rawJsonFormatter convert: metrics error:err]; + default: + return nil; + } +} + +/** + * Exports the metrics for a configured monitoring system, if santa is + * configured to do so. + * + * @param metrics The NSDictionary from a MetricSet export call. + */ +- (void)exportForMonitoring:(NSDictionary *)metrics { + SNTConfigurator *config = [SNTConfigurator configurator]; + + if (![config exportMetrics]) { + return; + } + + if (metrics == nil) { + LOGE(@"nil metrics dictionary sent for export"); + return; + } + + NSError *err; + NSArray *formattedMetrics = [self convertMetrics:metrics + toFormat:config.metricFormat + error: &err]; + + if (err != nil) { + LOGE(@"unable to format metrics as %@", [self messageFromError: err]); + return; + } + + const id writer = metricWriters[config.metricURL.scheme]; + + if (writer) { + BOOL ok = [writer write:formattedMetrics toURL: config.metricURL error:&err]; + + if (!ok) { + if (err != nil) { + LOGE(@"unable to write metrics: %@", [self messageFromError: err]); + } + } + } +} +@end diff --git a/Source/santametricservice/SNTMetricServiceTest.m b/Source/santametricservice/SNTMetricServiceTest.m new file mode 100644 index 000000000..b224e513c --- /dev/null +++ b/Source/santametricservice/SNTMetricServiceTest.m @@ -0,0 +1,232 @@ +#include +#import + +#import "Source/common/SNTCommonEnums.h" +#import "Source/common/SNTConfigurator.h" +#import "Source/common/SNTMetricSet.h" + +#import + +#import "Source/santametricservice/SNTMetricService.h" + +NSDictionary *validMetricsDict = nil; + +@interface SNTMetricServiceTest : XCTestCase +@property id mockConfigurator; +@property NSString *tempDir; +@property NSURL *jsonURL; +@end + +@implementation SNTMetricServiceTest + +- (void)initializeValidMetricsDict +{ + NSDateFormatter *formatter = NSDateFormatter.new; + [formatter setDateFormat:@"yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"]; + NSDate *fixedDate = [formatter dateFromString:@"2021-09-16T21:07:34.826Z"]; + + validMetricsDict = @{ + @"root_labels" : @{@"hostname" : @"testHost", @"username" : @"testUser"}, + @"metrics" : @{ + @"/build/label" : @{ + @"type" : [NSNumber numberWithInt:(int)SNTMetricTypeConstantString], + @"fields" : @{ + @"" : @[ @{ + @"value" : @"", + @"created" : fixedDate, + @"last_updated" : fixedDate, + @"data" : @"20210809.0.1" + } ] + } + }, + @"/santa/events" : @{ + @"type" : [NSNumber numberWithInt:(int)SNTMetricTypeCounter], + @"fields" : @{ + @"rule_type" : @[ + @{ + @"value" : @"binary", + @"created" : fixedDate, + @"last_updated" : fixedDate, + @"data" : @1, + }, + @{ + @"value" : @"certificate", + @"created" : fixedDate, + @"last_updated" : fixedDate, + @"data" : @2, + }, + ], + }, + }, + @"/santa/rules" : @{ + @"type" : [NSNumber numberWithInt:(int)SNTMetricTypeGaugeInt64], + @"fields" : @{ + @"rule_type" : @[ + @{ + @"value" : @"binary", + @"created" : fixedDate, + @"last_updated" : fixedDate, + @"data" : @1 + }, + @{ + @"value" : @"certificate", + @"created" : fixedDate, + @"last_updated" : fixedDate, + @"data" : @3 + } + ] + }, + }, + @"/santa/using_endpoint_security_framework" : @{ + @"type" : [NSNumber numberWithInt:(int)SNTMetricTypeConstantBool], + @"fields" : @{ + @"" : @[ @{ + @"value" : @"", + @"created" : fixedDate, + @"last_updated" : fixedDate, + @"data" : [NSNumber numberWithBool:YES] + } ] + } + }, + @"/proc/birth_timestamp" : @{ + @"type" : [NSNumber numberWithInt:(int)SNTMetricTypeConstantInt64], + @"fields" : @{ + @"" : @[ @{ + @"value" : @"", + @"created" : fixedDate, + @"last_updated" : fixedDate, + @"data" : [NSNumber numberWithLong:1250999830800] + } ] + }, + }, + @"/proc/memory/virtual_size" : @{ + @"type" : [NSNumber numberWithInt:(int)SNTMetricTypeGaugeInt64], + @"fields" : @{ + @"" : @[ @{ + @"value" : @"", + @"created" : fixedDate, + @"last_updated" : fixedDate, + @"data" : [NSNumber numberWithInt:987654321] + } ] + } + }, + @"/proc/memory/resident_size" : @{ + @"type" : [NSNumber numberWithInt:(int)SNTMetricTypeGaugeInt64], + @"fields" : @{ + @"" : @[ @{ + @"value" : @"", + @"created" : fixedDate, + @"last_updated" : fixedDate, + @"data" : [NSNumber numberWithInt:123456789] + } ] + }, + }, + } + }; +} + +- (void)setUp +{ + [self initializeValidMetricsDict]; + //create the configurator + self.mockConfigurator = OCMClassMock([SNTConfigurator class]); + OCMStub([self.mockConfigurator configurator]).andReturn(self.mockConfigurator); + + // create a temp dir + char template[] = "/tmp/sntmetricsservicetestdata.XXXXXXX"; + char *tempPath = mkdtemp(template); + + if (tempPath == NULL) { + NSLog(@"Unable to make temp directory"); + exit(1); + } + + self.tempDir = [[NSFileManager defaultManager] + stringWithFileSystemRepresentation: tempPath + length: strlen(tempPath)]; + self.jsonURL = [NSURL URLWithString: [NSString pathWithComponents: @[@"file://", self.tempDir, @"test.json"]]]; + +} + +- (void)tearDown +{ + [self.mockConfigurator stopMocking]; + + //delete the temp dir + [[NSFileManager defaultManager] removeItemAtPath: self.tempDir + error:NULL]; +} + +- (NSDate *) createNSDateFromDateString:(NSString *)dateString +{ + NSDateFormatter *formatter = [[NSDateFormatter alloc] init]; + + [formatter setTimeZone:[NSTimeZone timeZoneWithName:@"UTC"]]; + [formatter setDateFormat:@"yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"]; + + return [formatter dateFromString: dateString]; +} + +- (NSDictionary *)convertJSONDateStringsToNSDateWithJson: (NSDictionary *)jsonData +{ + NSMutableDictionary *jsonDict = [jsonData mutableCopy]; + + for (NSString *metricName in jsonDict[@"metrics"]) { + NSMutableDictionary *metric = jsonDict[@"metrics"][metricName]; + + for (NSString *field in metric[@"fields"]) { + NSMutableArray *values = metric[@"fields"][field]; + + for (int i = 0; i < [values count]; i++) { + values[i][@"created"] = [self createNSDateFromDateString: values[i][@"created"]]; + values[i][@"last_updated"] = [self createNSDateFromDateString: values[i][@"last_updated"]]; + } + } + } + + return jsonDict; +} + +- (void)testDefaultConfigOptionsDoNotExport +{ + SNTMetricService *ms = [[SNTMetricService alloc] init]; + //OCMStub([self.mockConfigurator exportMetrics]).andReturn(NO); + + [ms exportForMonitoring: validMetricsDict]; + + // Check the temp dir + NSArray* items = [[NSFileManager defaultManager] contentsOfDirectoryAtPath: self.tempDir + error:NULL]; + XCTAssertEqual(0, [items count]); +} + +- (void)testWritingRawJSONFile +{ + OCMStub([self.mockConfigurator exportMetrics]).andReturn(YES); + OCMStub([self.mockConfigurator metricFormat]).andReturn(SNTMetricFormatTypeRawJSON); + OCMStub([self.mockConfigurator metricURL]).andReturn(self.jsonURL); + + + SNTMetricService *ms = [[SNTMetricService alloc] init]; + [ms exportForMonitoring: validMetricsDict]; + + // Ensure that this has written 1 file that is well formed. + NSArray* items = [[NSFileManager defaultManager] contentsOfDirectoryAtPath: self.tempDir + error:NULL]; + XCTAssertEqual(1, [items count], @"failed to create JSON metrics file"); + + NSData *jsonData = [NSData dataWithContentsOfFile:self.jsonURL.path + options:NSDataReadingUncached + error:nil]; + + NSDictionary *parsedJSONData = [NSJSONSerialization JSONObjectWithData:jsonData + options:NSJSONReadingMutableContainers + error:nil]; + + // Convert JSON's date strings back into dates. + [self convertJSONDateStringsToNSDateWithJson: parsedJSONData]; + + + XCTAssertEqualObjects(validMetricsDict, parsedJSONData, @"invalid json created"); +} +@end diff --git a/Source/santametricservice/Writers/BUILD b/Source/santametricservice/Writers/BUILD new file mode 100644 index 000000000..b5445fd0e --- /dev/null +++ b/Source/santametricservice/Writers/BUILD @@ -0,0 +1,35 @@ +load("@build_bazel_rules_apple//apple:macos.bzl", "macos_command_line_application") +load("//:helper.bzl", "santa_unit_test") + +package(default_visibility = ["//:santa_package_group"]) + +licenses(["notice"]) # Apache 2.0 + +objc_library( + name = "SNTMetricWriter", + hdrs = ["SNTMetricWriter.h"], +) + +objc_library( + name = "SNTMetricFileWriter", + srcs = [ + "SNTMetricFileWriter.h", + "SNTMetricFileWriter.m", + "SNTMetricWriter.h", + + ], + deps = [ + ":SNTMetricWriter", + "//Source/common:SNTLogging", + ], +) + +santa_unit_test( + name = "SNTMetricFileWriterTest", + srcs = [ + "SNTMetricFileWriterTest.m", + ], + deps = [ + ":SNTMetricFileWriter", + ], +) diff --git a/Source/santametricservice/Writers/SNTMetricFileWriter.h b/Source/santametricservice/Writers/SNTMetricFileWriter.h new file mode 100644 index 000000000..49e158d45 --- /dev/null +++ b/Source/santametricservice/Writers/SNTMetricFileWriter.h @@ -0,0 +1,17 @@ +/// Copyright 2021 Google Inc. All rights reserved. +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +#import "Source/santametricservice/Writers/SNTMetricWriter.h" + +@interface SNTMetricFileWriter : NSObject +@end \ No newline at end of file diff --git a/Source/santametricservice/Writers/SNTMetricFileWriter.m b/Source/santametricservice/Writers/SNTMetricFileWriter.m new file mode 100644 index 000000000..44a47fc50 --- /dev/null +++ b/Source/santametricservice/Writers/SNTMetricFileWriter.m @@ -0,0 +1,77 @@ +/// Copyright 2021 Google Inc. All rights reserved. +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. + +#import "Source/common/SNTLogging.h" +#import "Source/santametricservice/Writers/SNTMetricFileWriter.h" + +@implementation SNTMetricFileWriter + +/* + * Open a file for appending. + */ +- (NSFileHandle *)fileHandleForAppendingAtPath:(NSString *)path createMode:(mode_t)mode { + int fd; + if (!path) { + return nil; + } + + fd = open([path fileSystemRepresentation], O_WRONLY | O_APPEND | O_TRUNC | O_CREAT, mode); + if (fd < 0) { + return nil; + } + return [[NSFileHandle alloc] initWithFileDescriptor:fd closeOnDealloc:YES]; +} + +/** + * Write serialzied metrics to the file one JSON object per line. + **/ +- (BOOL)write:(NSArray *)metrics toURL:(NSURL *)url error: (NSError **)error { + // open the file and write it. + @autoreleasepool { + if (![url isFileURL]) { + LOGE(@"url supplied to SNTMetricFileOutput is not a file url, given %@", url.absoluteString); + return NO; + } + + NSFileHandle *file = [self fileHandleForAppendingAtPath:url.path createMode:0600]; + const char newline[1] = {'\n'}; + + if (file == nil) { + LOGE(@"Unable to open file %@ to write metrics", url.path); + return NO; + } + + NSMutableData *lineData; + + for (int i = 0; i < [metrics count]; i++) { + lineData = [NSMutableData dataWithData: metrics[i]]; + + [lineData appendBytes: newline length: 1]; + + if (@available(macos 10.15, *)) { + [file writeData:lineData error:error]; + + if (*error != nil) { + return NO; + } + } else { + [file writeData:lineData]; + } + } + } + + return YES; +} + +@end diff --git a/Source/santametricservice/Writers/SNTMetricFileWriterTest.m b/Source/santametricservice/Writers/SNTMetricFileWriterTest.m new file mode 100644 index 000000000..ea78bea1d --- /dev/null +++ b/Source/santametricservice/Writers/SNTMetricFileWriterTest.m @@ -0,0 +1,101 @@ +#import + +#import "Source/santametricservice/Writers/SNTMetricFileWriter.h" + +@interface SNTMetricFileWriterTest : XCTestCase +@property NSString *tempDir; +@end + +@implementation SNTMetricFileWriterTest + +- (void)setUp +{ + // create a temp dir + char template[] = "/tmp/sntmetricfileoutputtest.XXXXXXX"; + char *tempPath = mkdtemp(template); + + if (tempPath == NULL) { + NSLog(@"Unable to make temp directory"); + exit(1); + } + + self.tempDir = [[NSFileManager defaultManager] + stringWithFileSystemRepresentation: tempPath + length: strlen(tempPath)]; +} + +- (void)tearDown +{ + //delete the temp dir + [[NSFileManager defaultManager] removeItemAtPath: self.tempDir + error:NULL]; +} + +- (void)testWritingToNonFileURLFails +{ + NSString *testURL = @"http://www.google.com"; + + SNTMetricFileWriter *fileWriter = [[SNTMetricFileWriter alloc] init]; + + NSError *err; + + NSData *firstLine = [@"AAAAAAAA" dataUsingEncoding:NSUTF8StringEncoding]; + + NSArray *input = @[firstLine]; + + BOOL result = [fileWriter write: input toURL:[NSURL URLWithString: testURL] error:&err]; + XCTAssertFalse(result); +} + +- (void)testWritingDataToFileWorks +{ + NSString *testFile = [NSString pathWithComponents: @[@"file://", self.tempDir, @"test.data"]]; + NSURL *url = [NSURL URLWithString: testFile]; + + + SNTMetricFileWriter *fileWriter = [[SNTMetricFileWriter alloc] init]; + + NSError *err; + + NSData *firstLine = [@"AAAAAAAA" dataUsingEncoding:NSUTF8StringEncoding]; + NSData *secondLine = [@"BBBBBBBB" dataUsingEncoding:NSUTF8StringEncoding]; + + NSArray *input = @[firstLine]; + + BOOL success = [fileWriter write: input toURL: url error:&err]; + + if (!success) { + NSLog(@"error: %@\n", err); + } + + XCTAssertEqual(YES, success); + XCTAssertNil(err); + const char newline[1] = {'\n'}; + + // Read file ensure it only contains the first line followed by a Newline + NSData *testFileContents = [NSData dataWithContentsOfFile: url.path]; + NSMutableData *expected = [NSMutableData dataWithData:firstLine]; + + [expected appendBytes: newline length: 1]; + + XCTAssertEqualObjects(expected, testFileContents); + + [expected appendData: secondLine]; + [expected appendBytes: newline length: 1]; + + // Test that calling a second time overwrites the file and that multiple rows + // are separated by a newline + input = @[firstLine, secondLine]; + + success = [fileWriter write:input toURL:url error:&err]; + XCTAssertEqual(YES, success); + XCTAssertNil(err); + + if (!success) { + NSLog(@"error: %@\n", err); + } + + testFileContents = [NSData dataWithContentsOfFile: url.path]; + XCTAssertEqualObjects(expected, testFileContents); +} +@end \ No newline at end of file diff --git a/Source/santametricservice/Writers/SNTMetricWriter.h b/Source/santametricservice/Writers/SNTMetricWriter.h new file mode 100644 index 000000000..2d47263b6 --- /dev/null +++ b/Source/santametricservice/Writers/SNTMetricWriter.h @@ -0,0 +1,23 @@ +/// Copyright 2021 Google Inc. All rights reserved. +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. + +#import + +/** + * An SNTMetricWriter outputs a serialized SNTMetricSet to the external + * monitoring system. + * */ +@protocol SNTMetricWriter +- (BOOL) write:(NSArray *)data toURL:(NSURL *)url error:(NSError **) error; +@end \ No newline at end of file diff --git a/Source/santametricservice/main.m b/Source/santametricservice/main.m new file mode 100644 index 000000000..6f0ad6c33 --- /dev/null +++ b/Source/santametricservice/main.m @@ -0,0 +1,35 @@ +/// Copyright 2021 Google Inc. All rights reserved. +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. + +#import + +#import + +#import "Source/common/SNTLogging.h" +#import "Source/common/SNTXPCMetricServiceInterface.h" +#import "Source/santametricservice/SNTMetricService.h" + +int main(int argc, const char *argv[]) { + @autoreleasepool { + NSDictionary *infoDict = [[NSBundle mainBundle] infoDictionary]; + LOGI(@"Started, version %@", infoDict[@"CFBundleVersion"]); + MOLXPCConnection *c = + [[MOLXPCConnection alloc] initServerWithName:[SNTXPCMetricServiceInterface serviceID]]; + c.privilegedInterface = c.unprivilegedInterface = + [SNTXPCMetricServiceInterface metricServiceInterface]; + c.exportedObject = [[SNTMetricService alloc] init]; + [c resume]; + [[NSRunLoop mainRunLoop] run]; + } +} diff --git a/docs/deployment/configuration.md b/docs/deployment/configuration.md index 9b3754309..52bf8f517 100644 --- a/docs/deployment/configuration.md +++ b/docs/deployment/configuration.md @@ -47,7 +47,7 @@ Additionally, there are options that can be controlled by both. | EventLogType | String | Defines how event logs are stored. Options are 1) syslog: Sent to ASL or ULS (if built with the 10.12 SDK or later). 2) filelog: Sent to a file on disk. Use EventLogPath to specify a path. Defaults to filelog | | EventLogPath | String | If EventLogType is set to filelog, EventLogPath will provide the path to save logs. Defaults to /var/db/santa/santa.log. If you change this value ensure you also update com.google.santa.newsyslog.conf with the new path. | | EnableMachineIDDecoration | Bool | If YES, this appends the MachineID to the end of each log line. Defaults to NO. | -| MetricFormat | String | Format to export metrics as, supported formats are "rawjson" for a single JSON blob and "json" for one metric per line. Defaults to "". | +| MetricFormat | Integer | Format to export metrics as 0 = None, 1 = Raw JSON blob, 2 = JSON one metric per line. Defaults to 0. | | MetricURL | String | URL describing where monitoring metrics should be exported. | *overridable by the sync server: run `santactl status` to check the current From 9149315598cd2af11cc7807fb931333f9afc824c Mon Sep 17 00:00:00 2001 From: Pete Markowsky Date: Wed, 22 Sep 2021 10:11:05 -0400 Subject: [PATCH 02/14] Fixed up formatting and capitalized JSON. --- Source/santametricservice/BUILD | 23 ++-- Source/santametricservice/Formats/BUILD | 14 ++- ...wJsonFormat.h => SNTMetricRawJSONFormat.h} | 4 +- .../Formats/SNTMetricRawJSONFormat.m | 94 ++++++++++++++++ ...matTest.m => SNTMetricRawJSONFormatTest.m} | 74 +++++++------ .../Formats/SNTMetricRawJsonFormat.m | 98 ----------------- Source/santametricservice/SNTMetricService.m | 100 ++++++++---------- .../santametricservice/SNTMetricServiceTest.m | 71 ++++++------- Source/santametricservice/Writers/BUILD | 3 +- .../Writers/SNTMetricFileWriter.m | 8 +- .../Writers/SNTMetricFileWriterTest.m | 94 ++++++++-------- .../Writers/SNTMetricWriter.h | 4 +- 12 files changed, 278 insertions(+), 309 deletions(-) rename Source/santametricservice/Formats/{SNTMetricRawJsonFormat.h => SNTMetricRawJSONFormat.h} (83%) create mode 100644 Source/santametricservice/Formats/SNTMetricRawJSONFormat.m rename Source/santametricservice/Formats/{SNTMetricRawJsonFormatTest.m => SNTMetricRawJSONFormatTest.m} (68%) delete mode 100644 Source/santametricservice/Formats/SNTMetricRawJsonFormat.m diff --git a/Source/santametricservice/BUILD b/Source/santametricservice/BUILD index 991d1ff37..df6795b8b 100644 --- a/Source/santametricservice/BUILD +++ b/Source/santametricservice/BUILD @@ -10,39 +10,38 @@ objc_library( srcs = [ "SNTMetricService.h", "SNTMetricService.m", - "main.m", + "main.m", ], deps = [ - "//Source/common:SNTXPCMetricServiceInterface", - "//Source/common:SNTLogging", "//Source/common:SNTConfigurator", + "//Source/common:SNTLogging", "//Source/common:SNTMetricSet", + "//Source/common:SNTXPCMetricServiceInterface", "//Source/santametricservice/Formats:SNTMetricRawJsonFormat", "//Source/santametricservice/Writers:SNTMetricFileWriter", - "@MOLCodesignChecker", + "@MOLCodesignChecker", "@MOLXPCConnection", ], ) santa_unit_test( - name="SNTMetricServiceTest", + name = "SNTMetricServiceTest", srcs = ["SNTMetricServiceTest.m"], deps = [ - ":SNTMetricServiceLib", - "@OCMock", + ":SNTMetricServiceLib", + "@OCMock", ], ) test_suite( - name="unit_tests", + name = "unit_tests", tests = [ - ":SNTMetricServiceTest", - "//Source/santametricservice/Formats:SNTMetricRawJsonFormatTest", - "//Source/santametricservice/Writers:SNTMetricFileWriterTest", + ":SNTMetricServiceTest", + "//Source/santametricservice/Formats:SNTMetricRawJsonFormatTest", + "//Source/santametricservice/Writers:SNTMetricFileWriterTest", ], ) - macos_command_line_application( name = "santametricservice", bundle_id = "com.google.santa.metricservice", diff --git a/Source/santametricservice/Formats/BUILD b/Source/santametricservice/Formats/BUILD index b24f90ccf..007881d4c 100644 --- a/Source/santametricservice/Formats/BUILD +++ b/Source/santametricservice/Formats/BUILD @@ -13,10 +13,9 @@ objc_library( objc_library( name = "SNTMetricRawJsonFormat", srcs = [ + "SNTMetricFormat.h", "SNTMetricRawJsonFormat.h", "SNTMetricRawJsonFormat.m", - "SNTMetricFormat.h", - ], deps = [ ":SNTMetricFormat", @@ -32,14 +31,13 @@ santa_unit_test( structured_resources = glob(["testdata/**"]), deps = [ ":SNTMetricRawJsonFormat", - "//Source/common:SNTMetricSet", + "//Source/common:SNTMetricSet", ], ) - test_suite( - name = "format_tests", - tests = [ - ":SNTMetricRawJsonFormatTest", - ], + name = "format_tests", + tests = [ + ":SNTMetricRawJsonFormatTest", + ], ) diff --git a/Source/santametricservice/Formats/SNTMetricRawJsonFormat.h b/Source/santametricservice/Formats/SNTMetricRawJSONFormat.h similarity index 83% rename from Source/santametricservice/Formats/SNTMetricRawJsonFormat.h rename to Source/santametricservice/Formats/SNTMetricRawJSONFormat.h index 913662ca5..612480cb0 100644 --- a/Source/santametricservice/Formats/SNTMetricRawJsonFormat.h +++ b/Source/santametricservice/Formats/SNTMetricRawJSONFormat.h @@ -16,6 +16,6 @@ #import "Source/santametricservice/Formats/SNTMetricFormat.h" -@interface SNTMetricRawJsonFormat : NSObject -- (NSArray *) convert:(NSDictionary *)metrics error:(NSError **)err; +@interface SNTMetricRawJSONFormat : NSObject +- (NSArray *)convert:(NSDictionary *)metrics error:(NSError **)err; @end diff --git a/Source/santametricservice/Formats/SNTMetricRawJSONFormat.m b/Source/santametricservice/Formats/SNTMetricRawJSONFormat.m new file mode 100644 index 000000000..53d4d4a9e --- /dev/null +++ b/Source/santametricservice/Formats/SNTMetricRawJSONFormat.m @@ -0,0 +1,94 @@ +/// Copyright 2021 Google Inc. All rights reserved. +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +#import "Source/common/SNTLogging.h" + +#import "Source/santametricservice/Formats/SNTMetricRawJSONFormat.h" + +@implementation SNTMetricRawJSONFormat { + NSDateFormatter *dateFormatter; +} + +- (instancetype)init { + self = [super init]; + if (self) { + dateFormatter = [[NSDateFormatter alloc] init]; + [dateFormatter setDateFormat:@"yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"]; + } + return self; +} + +- (NSArray *)normalizeArray:(NSArray *)arr { + NSMutableArray *normalized = [NSMutableArray arrayWithArray:arr]; + + for (int i = 0; i < [arr count]; i++) { + if ([arr[i] isKindOfClass:[NSArray class]]) { + normalized[i] = [self normalizeArray:(NSArray *)arr[i]]; + } else if ([arr[i] isKindOfClass:[NSDictionary class]]) { + normalized[i] = [self normalize:(NSDictionary *)arr[i]]; + } + } + + return normalized; +} + +/** + * Normalizes the metrics dictionary for exporting to JSON + **/ +- (NSDictionary *)normalize:(NSDictionary *)metrics { + // Convert NSDate's to RFC3339 in strings as NSDate's cannot be serialized + // to JSON. + NSMutableDictionary *normalizedMetrics = [NSMutableDictionary dictionaryWithDictionary:metrics]; + + for (NSString *key in metrics) { + const id object = [metrics objectForKey:key]; + if ([object isKindOfClass:[NSDate class]]) { + normalizedMetrics[key] = [self->dateFormatter stringFromDate:(NSDate *)object]; + } else if ([object isKindOfClass:[NSDictionary class]]) { + normalizedMetrics[key] = [self normalize:metrics[key]]; + } else if ([object isKindOfClass:[NSArray class]]) { + normalizedMetrics[key] = [self normalizeArray:(NSArray *)object]; + } + } + + return (NSDictionary *)normalizedMetrics; +} + +/* + * Convert normalies and converts the metrics dictionary to a single JSON + * object. + * + * @param metrics an NSDictionary exported by the SNTMetricSet + * @param error a pointer to an NSError to allow errors to bubble up. + * + * Returns an NSArray containing one entry of all metrics serialized to JSON or + * nil on error. + */ +- (NSArray *)convert:(NSDictionary *)metrics error:(NSError **)err { + NSDictionary *normalizedMetrics = [self normalize:metrics]; + + if (![NSJSONSerialization isValidJSONObject:normalizedMetrics]) { + LOGE(@"unable to convert metrics to JSON: invalid metrics"); + return nil; + } + + NSData *json = [NSJSONSerialization dataWithJSONObject:normalizedMetrics + options:NSJSONWritingPrettyPrinted + error:err]; + if (json == nil && *err != nil) { + return nil; + } + + return @[ json ]; +} +@end diff --git a/Source/santametricservice/Formats/SNTMetricRawJsonFormatTest.m b/Source/santametricservice/Formats/SNTMetricRawJSONFormatTest.m similarity index 68% rename from Source/santametricservice/Formats/SNTMetricRawJsonFormatTest.m rename to Source/santametricservice/Formats/SNTMetricRawJSONFormatTest.m index f1fba7482..54bd2257f 100644 --- a/Source/santametricservice/Formats/SNTMetricRawJsonFormatTest.m +++ b/Source/santametricservice/Formats/SNTMetricRawJSONFormatTest.m @@ -1,17 +1,16 @@ #import #import "Source/common/SNTMetricSet.h" -#import "Source/santametricservice/Formats/SNTMetricRawJsonFormat.h" +#import "Source/santametricservice/Formats/SNTMetricRawJSONFormat.h" NSDictionary *validMetricsDict = nil; -@interface SNTMetricRawJsonFormatTest : XCTestCase +@interface SNTMetricRawJSONFormatTest : XCTestCase @end -@implementation SNTMetricRawJsonFormatTest +@implementation SNTMetricRawJSONFormatTest -- (void)initializeValidMetricsDict -{ +- (void)initializeValidMetricsDict { NSDateFormatter *formatter = NSDateFormatter.new; [formatter setDateFormat:@"yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"]; NSDate *fixedDate = [formatter dateFromString:@"2021-09-16T21:07:34.826Z"]; @@ -116,40 +115,39 @@ - (void)initializeValidMetricsDict }; } -- (void)setUp -{ - [self initializeValidMetricsDict]; +- (void)setUp { + [self initializeValidMetricsDict]; } -- (void)testMetricsConversionToJSON -{ - SNTMetricRawJsonFormat *formatter = [[SNTMetricRawJsonFormat alloc] init]; - NSError *err = nil; - NSArray *output = [formatter convert:validMetricsDict error: &err]; - - XCTAssertEqual(1, [output count]); - XCTAssertNotNil(output[0]); - XCTAssertNil(err); - - NSDictionary *jsonDict = [NSJSONSerialization JSONObjectWithData:output[0] - options:NSJSONReadingAllowFragments - error:&err]; - XCTAssertNotNil(jsonDict); - - NSString *path = [[NSBundle bundleForClass:[self class]] resourcePath]; - path = [path stringByAppendingPathComponent:@"testdata/json/test.json"]; - - NSFileManager *filemgr = [NSFileManager defaultManager]; - NSData *goldenFileData = [filemgr contentsAtPath: path]; - - XCTAssertNotNil(goldenFileData, @"unable to open / read golden file"); - - NSDictionary *expectedJsonDict = [NSJSONSerialization JSONObjectWithData:goldenFileData - options:NSJSONReadingAllowFragments - error: &err]; - - XCTAssertNotNil(expectedJsonDict); - XCTAssertEqualObjects(expectedJsonDict, jsonDict, @"generated JSON does not match golden file."); +- (void)testMetricsConversionToJSON { + SNTMetricRawJSONFormat *formatter = [[SNTMetricRawJSONFormat alloc] init]; + NSError *err = nil; + NSArray *output = [formatter convert:validMetricsDict error:&err]; + + XCTAssertEqual(1, [output count]); + XCTAssertNotNil(output[0]); + XCTAssertNil(err); + + NSDictionary *jsonDict = [NSJSONSerialization JSONObjectWithData:output[0] + options:NSJSONReadingAllowFragments + error:&err]; + XCTAssertNotNil(jsonDict); + + NSString *path = [[NSBundle bundleForClass:[self class]] resourcePath]; + path = [path stringByAppendingPathComponent:@"testdata/json/test.json"]; + + NSFileManager *filemgr = [NSFileManager defaultManager]; + NSData *goldenFileData = [filemgr contentsAtPath:path]; + + XCTAssertNotNil(goldenFileData, @"unable to open / read golden file"); + + NSDictionary *expectedJSONDict = + [NSJSONSerialization JSONObjectWithData:goldenFileData + options:NSJSONReadingAllowFragments + error:&err]; + + XCTAssertNotNil(expectedJSONDict); + XCTAssertEqualObjects(expectedJSONDict, jsonDict, @"generated JSON does not match golden file."); } -@end \ No newline at end of file +@end diff --git a/Source/santametricservice/Formats/SNTMetricRawJsonFormat.m b/Source/santametricservice/Formats/SNTMetricRawJsonFormat.m deleted file mode 100644 index 528bdbee6..000000000 --- a/Source/santametricservice/Formats/SNTMetricRawJsonFormat.m +++ /dev/null @@ -1,98 +0,0 @@ -/// Copyright 2021 Google Inc. All rights reserved. -/// -/// Licensed under the Apache License, Version 2.0 (the "License"); -/// you may not use this file except in compliance with the License. -/// You may obtain a copy of the License at -/// -/// http://www.apache.org/licenses/LICENSE-2.0 -/// -/// Unless required by applicable law or agreed to in writing, software -/// distributed under the License is distributed on an "AS IS" BASIS, -/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -/// See the License for the specific language governing permissions and -/// limitations under the License. -#import "Source/common/SNTLogging.h" - -#import "Source/santametricservice/Formats/SNTMetricRawJsonFormat.h" - -@implementation SNTMetricRawJsonFormat { - NSDateFormatter *dateFormatter; -} - -- (instancetype)init -{ - self = [super init]; - if (self) { - dateFormatter = [[NSDateFormatter alloc] init]; - [dateFormatter setDateFormat:@"yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"]; - } - return self; -} - -- (NSArray *)normalizeArray:(NSArray *)arr -{ - NSMutableArray *normalized = [NSMutableArray arrayWithArray: arr]; - - for (int i = 0; i < [arr count];i++) { - if ([arr[i] isKindOfClass: [NSArray class]]) { - normalized[i] = [self normalizeArray: (NSArray *)arr[i]]; - } else if ([arr[i] isKindOfClass: [NSDictionary class]]) { - normalized[i] = [self normalize: (NSDictionary *)arr[i]]; - } - } - - return normalized; -} - -/** - * Normalizes the metrics dictionary for exporting to JSON - **/ -- (NSDictionary *)normalize: (NSDictionary *)metrics -{ - // Convert NSDate's to RFC3339 in strings as NSDate's cannot be serialized - // to JSON. - NSMutableDictionary *normalizedMetrics = [NSMutableDictionary dictionaryWithDictionary:metrics]; - - for (NSString *key in metrics) { - const id object = [metrics objectForKey: key]; - if ([object isKindOfClass: [NSDate class]]) { - normalizedMetrics[key] = [self->dateFormatter stringFromDate: (NSDate *)object]; - } else if ([object isKindOfClass: [NSDictionary class]]) { - normalizedMetrics[key] = [self normalize: metrics[key]]; - } else if ([object isKindOfClass: [NSArray class]]) { - normalizedMetrics[key] = [self normalizeArray: (NSArray *)object]; - } - } - - return (NSDictionary *)normalizedMetrics; -} - -/* - * Convert normalies and converts the metrics dictionary to a single JSON - * object. - * - * @param metrics an NSDictionary exported by the SNTMetricSet - * @param error a pointer to an NSError to allow errors to bubble up. - * - * Returns an NSArray containing one entry of all metrics serialized to JSON or - * nil on error. - */ -- (NSArray *) convert:(NSDictionary *)metrics error:(NSError **)err -{ - NSDictionary *normalizedMetrics = [self normalize: metrics]; - - if (![NSJSONSerialization isValidJSONObject: normalizedMetrics]) { - LOGE(@"unable to convert metrics to JSON: invalid metrics"); - return nil; - } - - NSData *json = [NSJSONSerialization dataWithJSONObject: normalizedMetrics - options: NSJSONWritingPrettyPrinted - error: err]; - if (json == nil && *err != nil) { - return nil; - } - - return @[ json ]; -} -@end \ No newline at end of file diff --git a/Source/santametricservice/SNTMetricService.m b/Source/santametricservice/SNTMetricService.m index b8c38b0b6..0d5815c67 100644 --- a/Source/santametricservice/SNTMetricService.m +++ b/Source/santametricservice/SNTMetricService.m @@ -12,15 +12,15 @@ /// See the License for the specific language governing permissions and /// limitations under the License. -#include #include +#include #import "Source/common/SNTConfigurator.h" #import "Source/common/SNTLogging.h" -#import "Source/santametricservice/Formats/SNTMetricRawJsonFormat.h" -#import "Source/santametricservice/Writers/SNTMetricFileWriter.h" #import "SNTMetricService.h" +#import "Source/santametricservice/Formats/SNTMetricRawJSONFormat.h" +#import "Source/santametricservice/Writers/SNTMetricFileWriter.h" @interface SNTMetricService () @property MOLXPCConnection *notifierConnection; @@ -29,16 +29,16 @@ @interface SNTMetricService () @end @implementation SNTMetricService { - @private - SNTMetricRawJsonFormat* rawJsonFormatter; - NSDictionary *metricWriters; + @private + SNTMetricRawJSONFormat *rawJSONFormatter; + NSDictionary *metricWriters; } - (instancetype)init { self = [super init]; - rawJsonFormatter = [[SNTMetricRawJsonFormat alloc] init]; - metricWriters = @{@"file": [[SNTMetricFileWriter alloc] init]}; + rawJSONFormatter = [[SNTMetricRawJSONFormat alloc] init]; + metricWriters = @{@"file" : [[SNTMetricFileWriter alloc] init]}; _queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0); return self; @@ -47,15 +47,12 @@ - (instancetype)init { /** * Helper function to format NSError's for logging error messages. */ -- (NSString *)messageFromError:(NSError *)error -{ - NSString *message = [error localizedDescription]; - NSString *details = [error localizedFailureReason] ? - [error localizedFailureReason] : @""; - - return [NSString stringWithFormat:@"%@ %@", message, details]; -} +- (NSString *)messageFromError:(NSError *)error { + NSString *message = [error localizedDescription]; + NSString *details = [error localizedFailureReason] ? [error localizedFailureReason] : @""; + return [NSString stringWithFormat:@"%@ %@", message, details]; +} /** * Converts the exported Metrics dicitionary to the appropriate monitoring @@ -66,56 +63,53 @@ - (NSString *)messageFromError:(NSError *)error * @return An array of metrics formatted according to the specified format or * nil on error; */ -- (NSArray *) convertMetrics:(NSDictionary *)metrics - toFormat:(SNTMetricFormatType)format - error:(NSError **)err -{ - switch (format) { - case SNTMetricFormatTypeRawJSON: - return [self->rawJsonFormatter convert: metrics error:err]; - default: - return nil; - } +- (NSArray *)convertMetrics:(NSDictionary *)metrics + toFormat:(SNTMetricFormatType)format + error:(NSError **)err { + switch (format) { + case SNTMetricFormatTypeRawJSON: return [self->rawJSONFormatter convert:metrics error:err]; + default: return nil; + } } -/** +/** * Exports the metrics for a configured monitoring system, if santa is * configured to do so. - * + * * @param metrics The NSDictionary from a MetricSet export call. - */ + */ - (void)exportForMonitoring:(NSDictionary *)metrics { - SNTConfigurator *config = [SNTConfigurator configurator]; + SNTConfigurator *config = [SNTConfigurator configurator]; - if (![config exportMetrics]) { - return; - } + if (![config exportMetrics]) { + return; + } - if (metrics == nil) { - LOGE(@"nil metrics dictionary sent for export"); - return; - } + if (metrics == nil) { + LOGE(@"nil metrics dictionary sent for export"); + return; + } - NSError *err; - NSArray *formattedMetrics = [self convertMetrics:metrics - toFormat:config.metricFormat - error: &err]; + NSError *err; + NSArray *formattedMetrics = [self convertMetrics:metrics + toFormat:config.metricFormat + error:&err]; - if (err != nil) { - LOGE(@"unable to format metrics as %@", [self messageFromError: err]); - return; - } + if (err != nil) { + LOGE(@"unable to format metrics as %@", [self messageFromError:err]); + return; + } - const id writer = metricWriters[config.metricURL.scheme]; + const id writer = metricWriters[config.metricURL.scheme]; - if (writer) { - BOOL ok = [writer write:formattedMetrics toURL: config.metricURL error:&err]; + if (writer) { + BOOL ok = [writer write:formattedMetrics toURL:config.metricURL error:&err]; - if (!ok) { - if (err != nil) { - LOGE(@"unable to write metrics: %@", [self messageFromError: err]); - } - } + if (!ok) { + if (err != nil) { + LOGE(@"unable to write metrics: %@", [self messageFromError:err]); + } } + } } @end diff --git a/Source/santametricservice/SNTMetricServiceTest.m b/Source/santametricservice/SNTMetricServiceTest.m index b224e513c..7d01c64c5 100644 --- a/Source/santametricservice/SNTMetricServiceTest.m +++ b/Source/santametricservice/SNTMetricServiceTest.m @@ -1,5 +1,5 @@ -#include #import +#include #import "Source/common/SNTCommonEnums.h" #import "Source/common/SNTConfigurator.h" @@ -19,8 +19,7 @@ @interface SNTMetricServiceTest : XCTestCase @implementation SNTMetricServiceTest -- (void)initializeValidMetricsDict -{ +- (void)initializeValidMetricsDict { NSDateFormatter *formatter = NSDateFormatter.new; [formatter setDateFormat:@"yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"]; NSDate *fixedDate = [formatter dateFromString:@"2021-09-16T21:07:34.826Z"]; @@ -125,10 +124,9 @@ - (void)initializeValidMetricsDict }; } -- (void)setUp -{ +- (void)setUp { [self initializeValidMetricsDict]; - //create the configurator + // create the configurator self.mockConfigurator = OCMClassMock([SNTConfigurator class]); OCMStub([self.mockConfigurator configurator]).andReturn(self.mockConfigurator); @@ -141,34 +139,30 @@ - (void)setUp exit(1); } - self.tempDir = [[NSFileManager defaultManager] - stringWithFileSystemRepresentation: tempPath - length: strlen(tempPath)]; - self.jsonURL = [NSURL URLWithString: [NSString pathWithComponents: @[@"file://", self.tempDir, @"test.json"]]]; - + self.tempDir = + [[NSFileManager defaultManager] stringWithFileSystemRepresentation:tempPath + length:strlen(tempPath)]; + self.jsonURL = + [NSURL URLWithString:[NSString pathWithComponents:@[ @"file://", self.tempDir, @"test.json" ]]]; } -- (void)tearDown -{ +- (void)tearDown { [self.mockConfigurator stopMocking]; - //delete the temp dir - [[NSFileManager defaultManager] removeItemAtPath: self.tempDir - error:NULL]; + // delete the temp dir + [[NSFileManager defaultManager] removeItemAtPath:self.tempDir error:NULL]; } -- (NSDate *) createNSDateFromDateString:(NSString *)dateString -{ +- (NSDate *)createNSDateFromDateString:(NSString *)dateString { NSDateFormatter *formatter = [[NSDateFormatter alloc] init]; - [formatter setTimeZone:[NSTimeZone timeZoneWithName:@"UTC"]]; + [formatter setTimeZone:[NSTimeZone timeZoneWithName:@"UTC"]]; [formatter setDateFormat:@"yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"]; - return [formatter dateFromString: dateString]; + return [formatter dateFromString:dateString]; } -- (NSDictionary *)convertJSONDateStringsToNSDateWithJson: (NSDictionary *)jsonData -{ +- (NSDictionary *)convertJSONDateStringsToNSDateWithJson:(NSDictionary *)jsonData { NSMutableDictionary *jsonDict = [jsonData mutableCopy]; for (NSString *metricName in jsonDict[@"metrics"]) { @@ -178,8 +172,8 @@ - (NSDictionary *)convertJSONDateStringsToNSDateWithJson: (NSDictionary *)jsonDa NSMutableArray *values = metric[@"fields"][field]; for (int i = 0; i < [values count]; i++) { - values[i][@"created"] = [self createNSDateFromDateString: values[i][@"created"]]; - values[i][@"last_updated"] = [self createNSDateFromDateString: values[i][@"last_updated"]]; + values[i][@"created"] = [self createNSDateFromDateString:values[i][@"created"]]; + values[i][@"last_updated"] = [self createNSDateFromDateString:values[i][@"last_updated"]]; } } } @@ -187,45 +181,42 @@ - (NSDictionary *)convertJSONDateStringsToNSDateWithJson: (NSDictionary *)jsonDa return jsonDict; } -- (void)testDefaultConfigOptionsDoNotExport -{ +- (void)testDefaultConfigOptionsDoNotExport { SNTMetricService *ms = [[SNTMetricService alloc] init]; - //OCMStub([self.mockConfigurator exportMetrics]).andReturn(NO); + // OCMStub([self.mockConfigurator exportMetrics]).andReturn(NO); - [ms exportForMonitoring: validMetricsDict]; + [ms exportForMonitoring:validMetricsDict]; // Check the temp dir - NSArray* items = [[NSFileManager defaultManager] contentsOfDirectoryAtPath: self.tempDir + NSArray *items = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:self.tempDir error:NULL]; XCTAssertEqual(0, [items count]); } -- (void)testWritingRawJSONFile -{ +- (void)testWritingRawJSONFile { OCMStub([self.mockConfigurator exportMetrics]).andReturn(YES); OCMStub([self.mockConfigurator metricFormat]).andReturn(SNTMetricFormatTypeRawJSON); OCMStub([self.mockConfigurator metricURL]).andReturn(self.jsonURL); - SNTMetricService *ms = [[SNTMetricService alloc] init]; - [ms exportForMonitoring: validMetricsDict]; + [ms exportForMonitoring:validMetricsDict]; // Ensure that this has written 1 file that is well formed. - NSArray* items = [[NSFileManager defaultManager] contentsOfDirectoryAtPath: self.tempDir + NSArray *items = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:self.tempDir error:NULL]; XCTAssertEqual(1, [items count], @"failed to create JSON metrics file"); NSData *jsonData = [NSData dataWithContentsOfFile:self.jsonURL.path - options:NSDataReadingUncached + options:NSDataReadingUncached error:nil]; - NSDictionary *parsedJSONData = [NSJSONSerialization JSONObjectWithData:jsonData - options:NSJSONReadingMutableContainers - error:nil]; + NSDictionary *parsedJSONData = + [NSJSONSerialization JSONObjectWithData:jsonData + options:NSJSONReadingMutableContainers + error:nil]; // Convert JSON's date strings back into dates. - [self convertJSONDateStringsToNSDateWithJson: parsedJSONData]; - + [self convertJSONDateStringsToNSDateWithJson:parsedJSONData]; XCTAssertEqualObjects(validMetricsDict, parsedJSONData, @"invalid json created"); } diff --git a/Source/santametricservice/Writers/BUILD b/Source/santametricservice/Writers/BUILD index b5445fd0e..869cca408 100644 --- a/Source/santametricservice/Writers/BUILD +++ b/Source/santametricservice/Writers/BUILD @@ -15,8 +15,7 @@ objc_library( srcs = [ "SNTMetricFileWriter.h", "SNTMetricFileWriter.m", - "SNTMetricWriter.h", - + "SNTMetricWriter.h", ], deps = [ ":SNTMetricWriter", diff --git a/Source/santametricservice/Writers/SNTMetricFileWriter.m b/Source/santametricservice/Writers/SNTMetricFileWriter.m index 44a47fc50..b6a58ada1 100644 --- a/Source/santametricservice/Writers/SNTMetricFileWriter.m +++ b/Source/santametricservice/Writers/SNTMetricFileWriter.m @@ -12,8 +12,8 @@ /// See the License for the specific language governing permissions and /// limitations under the License. -#import "Source/common/SNTLogging.h" #import "Source/santametricservice/Writers/SNTMetricFileWriter.h" +#import "Source/common/SNTLogging.h" @implementation SNTMetricFileWriter @@ -36,7 +36,7 @@ - (NSFileHandle *)fileHandleForAppendingAtPath:(NSString *)path createMode:(mode /** * Write serialzied metrics to the file one JSON object per line. **/ -- (BOOL)write:(NSArray *)metrics toURL:(NSURL *)url error: (NSError **)error { +- (BOOL)write:(NSArray *)metrics toURL:(NSURL *)url error:(NSError **)error { // open the file and write it. @autoreleasepool { if (![url isFileURL]) { @@ -55,9 +55,9 @@ - (BOOL)write:(NSArray *)metrics toURL:(NSURL *)url error: (NSError ** NSMutableData *lineData; for (int i = 0; i < [metrics count]; i++) { - lineData = [NSMutableData dataWithData: metrics[i]]; + lineData = [NSMutableData dataWithData:metrics[i]]; - [lineData appendBytes: newline length: 1]; + [lineData appendBytes:newline length:1]; if (@available(macos 10.15, *)) { [file writeData:lineData error:error]; diff --git a/Source/santametricservice/Writers/SNTMetricFileWriterTest.m b/Source/santametricservice/Writers/SNTMetricFileWriterTest.m index ea78bea1d..59bdcbe10 100644 --- a/Source/santametricservice/Writers/SNTMetricFileWriterTest.m +++ b/Source/santametricservice/Writers/SNTMetricFileWriterTest.m @@ -8,8 +8,7 @@ @interface SNTMetricFileWriterTest : XCTestCase @implementation SNTMetricFileWriterTest -- (void)setUp -{ +- (void)setUp { // create a temp dir char template[] = "/tmp/sntmetricfileoutputtest.XXXXXXX"; char *tempPath = mkdtemp(template); @@ -19,83 +18,78 @@ - (void)setUp exit(1); } - self.tempDir = [[NSFileManager defaultManager] - stringWithFileSystemRepresentation: tempPath - length: strlen(tempPath)]; + self.tempDir = + [[NSFileManager defaultManager] stringWithFileSystemRepresentation:tempPath + length:strlen(tempPath)]; } -- (void)tearDown -{ - //delete the temp dir - [[NSFileManager defaultManager] removeItemAtPath: self.tempDir - error:NULL]; +- (void)tearDown { + // delete the temp dir + [[NSFileManager defaultManager] removeItemAtPath:self.tempDir error:NULL]; } -- (void)testWritingToNonFileURLFails -{ +- (void)testWritingToNonFileURLFails { NSString *testURL = @"http://www.google.com"; SNTMetricFileWriter *fileWriter = [[SNTMetricFileWriter alloc] init]; NSError *err; - NSData *firstLine = [@"AAAAAAAA" dataUsingEncoding:NSUTF8StringEncoding]; + NSData *firstLine = [@"AAAAAAAA" dataUsingEncoding:NSUTF8StringEncoding]; - NSArray *input = @[firstLine]; + NSArray *input = @[ firstLine ]; - BOOL result = [fileWriter write: input toURL:[NSURL URLWithString: testURL] error:&err]; + BOOL result = [fileWriter write:input toURL:[NSURL URLWithString:testURL] error:&err]; XCTAssertFalse(result); } -- (void)testWritingDataToFileWorks -{ - NSString *testFile = [NSString pathWithComponents: @[@"file://", self.tempDir, @"test.data"]]; - NSURL *url = [NSURL URLWithString: testFile]; +- (void)testWritingDataToFileWorks { + NSString *testFile = [NSString pathWithComponents:@[ @"file://", self.tempDir, @"test.data" ]]; + NSURL *url = [NSURL URLWithString:testFile]; + SNTMetricFileWriter *fileWriter = [[SNTMetricFileWriter alloc] init]; - SNTMetricFileWriter *fileWriter = [[SNTMetricFileWriter alloc] init]; - - NSError *err; + NSError *err; - NSData *firstLine = [@"AAAAAAAA" dataUsingEncoding:NSUTF8StringEncoding]; - NSData *secondLine = [@"BBBBBBBB" dataUsingEncoding:NSUTF8StringEncoding]; + NSData *firstLine = [@"AAAAAAAA" dataUsingEncoding:NSUTF8StringEncoding]; + NSData *secondLine = [@"BBBBBBBB" dataUsingEncoding:NSUTF8StringEncoding]; - NSArray *input = @[firstLine]; + NSArray *input = @[ firstLine ]; - BOOL success = [fileWriter write: input toURL: url error:&err]; + BOOL success = [fileWriter write:input toURL:url error:&err]; - if (!success) { - NSLog(@"error: %@\n", err); - } + if (!success) { + NSLog(@"error: %@\n", err); + } - XCTAssertEqual(YES, success); - XCTAssertNil(err); - const char newline[1] = {'\n'}; + XCTAssertEqual(YES, success); + XCTAssertNil(err); + const char newline[1] = {'\n'}; - // Read file ensure it only contains the first line followed by a Newline - NSData *testFileContents = [NSData dataWithContentsOfFile: url.path]; - NSMutableData *expected = [NSMutableData dataWithData:firstLine]; + // Read file ensure it only contains the first line followed by a Newline + NSData *testFileContents = [NSData dataWithContentsOfFile:url.path]; + NSMutableData *expected = [NSMutableData dataWithData:firstLine]; - [expected appendBytes: newline length: 1]; + [expected appendBytes:newline length:1]; - XCTAssertEqualObjects(expected, testFileContents); + XCTAssertEqualObjects(expected, testFileContents); - [expected appendData: secondLine]; - [expected appendBytes: newline length: 1]; + [expected appendData:secondLine]; + [expected appendBytes:newline length:1]; - // Test that calling a second time overwrites the file and that multiple rows - // are separated by a newline - input = @[firstLine, secondLine]; + // Test that calling a second time overwrites the file and that multiple rows + // are separated by a newline + input = @[ firstLine, secondLine ]; - success = [fileWriter write:input toURL:url error:&err]; - XCTAssertEqual(YES, success); - XCTAssertNil(err); + success = [fileWriter write:input toURL:url error:&err]; + XCTAssertEqual(YES, success); + XCTAssertNil(err); - if (!success) { - NSLog(@"error: %@\n", err); - } + if (!success) { + NSLog(@"error: %@\n", err); + } - testFileContents = [NSData dataWithContentsOfFile: url.path]; - XCTAssertEqualObjects(expected, testFileContents); + testFileContents = [NSData dataWithContentsOfFile:url.path]; + XCTAssertEqualObjects(expected, testFileContents); } @end \ No newline at end of file diff --git a/Source/santametricservice/Writers/SNTMetricWriter.h b/Source/santametricservice/Writers/SNTMetricWriter.h index 2d47263b6..a4fd3589d 100644 --- a/Source/santametricservice/Writers/SNTMetricWriter.h +++ b/Source/santametricservice/Writers/SNTMetricWriter.h @@ -14,10 +14,10 @@ #import -/** +/** * An SNTMetricWriter outputs a serialized SNTMetricSet to the external * monitoring system. * */ @protocol SNTMetricWriter -- (BOOL) write:(NSArray *)data toURL:(NSURL *)url error:(NSError **) error; +- (BOOL)write:(NSArray *)data toURL:(NSURL *)url error:(NSError **)error; @end \ No newline at end of file From 89af1177c26a9813f5874a3f67965ff7e6f3ebcb Mon Sep 17 00:00:00 2001 From: Pete Markowsky Date: Wed, 22 Sep 2021 10:24:18 -0400 Subject: [PATCH 03/14] Revert configuration.md back to the correct version. --- docs/deployment/configuration.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/deployment/configuration.md b/docs/deployment/configuration.md index 52bf8f517..9b3754309 100644 --- a/docs/deployment/configuration.md +++ b/docs/deployment/configuration.md @@ -47,7 +47,7 @@ Additionally, there are options that can be controlled by both. | EventLogType | String | Defines how event logs are stored. Options are 1) syslog: Sent to ASL or ULS (if built with the 10.12 SDK or later). 2) filelog: Sent to a file on disk. Use EventLogPath to specify a path. Defaults to filelog | | EventLogPath | String | If EventLogType is set to filelog, EventLogPath will provide the path to save logs. Defaults to /var/db/santa/santa.log. If you change this value ensure you also update com.google.santa.newsyslog.conf with the new path. | | EnableMachineIDDecoration | Bool | If YES, this appends the MachineID to the end of each log line. Defaults to NO. | -| MetricFormat | Integer | Format to export metrics as 0 = None, 1 = Raw JSON blob, 2 = JSON one metric per line. Defaults to 0. | +| MetricFormat | String | Format to export metrics as, supported formats are "rawjson" for a single JSON blob and "json" for one metric per line. Defaults to "". | | MetricURL | String | URL describing where monitoring metrics should be exported. | *overridable by the sync server: run `santactl status` to check the current From a2acfc581bc5cdf30138db6cd8e7b5ce690022f7 Mon Sep 17 00:00:00 2001 From: Pete Markowsky Date: Wed, 22 Sep 2021 10:28:55 -0400 Subject: [PATCH 04/14] Change loop from C style to iterator style. --- .../santametricservice/Writers/SNTMetricFileWriter.m | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Source/santametricservice/Writers/SNTMetricFileWriter.m b/Source/santametricservice/Writers/SNTMetricFileWriter.m index b6a58ada1..078a683b9 100644 --- a/Source/santametricservice/Writers/SNTMetricFileWriter.m +++ b/Source/santametricservice/Writers/SNTMetricFileWriter.m @@ -52,21 +52,21 @@ - (BOOL)write:(NSArray *)metrics toURL:(NSURL *)url error:(NSError **) return NO; } - NSMutableData *lineData; + NSMutableData *entryData; - for (int i = 0; i < [metrics count]; i++) { - lineData = [NSMutableData dataWithData:metrics[i]]; + for (id formattedMetricData in metrics) { + entryData = [NSMutableData dataWithData:formattedMetricData]; - [lineData appendBytes:newline length:1]; + [entryData appendBytes:newline length:1]; if (@available(macos 10.15, *)) { - [file writeData:lineData error:error]; + [file writeData:entryData error:error]; if (*error != nil) { return NO; } } else { - [file writeData:lineData]; + [file writeData:entryData]; } } } From 1b2b09056f265b03a6dbf35e5f1fe6d8c4bd207a Mon Sep 17 00:00:00 2001 From: Pete Markowsky Date: Wed, 22 Sep 2021 11:18:18 -0400 Subject: [PATCH 05/14] renamed ivar to conform with style guide. --- .../santametricservice/Formats/SNTMetricRawJSONFormat.m | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Source/santametricservice/Formats/SNTMetricRawJSONFormat.m b/Source/santametricservice/Formats/SNTMetricRawJSONFormat.m index 53d4d4a9e..67d448633 100644 --- a/Source/santametricservice/Formats/SNTMetricRawJSONFormat.m +++ b/Source/santametricservice/Formats/SNTMetricRawJSONFormat.m @@ -16,14 +16,14 @@ #import "Source/santametricservice/Formats/SNTMetricRawJSONFormat.h" @implementation SNTMetricRawJSONFormat { - NSDateFormatter *dateFormatter; + NSDateFormatter *_dateFormatter; } - (instancetype)init { self = [super init]; if (self) { - dateFormatter = [[NSDateFormatter alloc] init]; - [dateFormatter setDateFormat:@"yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"]; + _dateFormatter = [[NSDateFormatter alloc] init]; + [_dateFormatter setDateFormat:@"yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"]; } return self; } @@ -53,7 +53,7 @@ - (NSDictionary *)normalize:(NSDictionary *)metrics { for (NSString *key in metrics) { const id object = [metrics objectForKey:key]; if ([object isKindOfClass:[NSDate class]]) { - normalizedMetrics[key] = [self->dateFormatter stringFromDate:(NSDate *)object]; + normalizedMetrics[key] = [self->_dateFormatter stringFromDate:(NSDate *)object]; } else if ([object isKindOfClass:[NSDictionary class]]) { normalizedMetrics[key] = [self normalize:metrics[key]]; } else if ([object isKindOfClass:[NSArray class]]) { From 52fe79cfc7b93c193ae16506b3e815ffea9eb4f9 Mon Sep 17 00:00:00 2001 From: Pete Markowsky Date: Wed, 22 Sep 2021 11:39:01 -0400 Subject: [PATCH 06/14] Made changes to incorporate feedback. --- .../Formats/SNTMetricRawJSONFormat.h | 1 - .../Formats/SNTMetricRawJSONFormat.m | 8 +++++++- Source/santametricservice/SNTMetricServiceTest.m | 1 - .../Writers/SNTMetricFileWriter.m | 2 +- .../Writers/SNTMetricFileWriterTest.m | 14 +++----------- 5 files changed, 11 insertions(+), 15 deletions(-) diff --git a/Source/santametricservice/Formats/SNTMetricRawJSONFormat.h b/Source/santametricservice/Formats/SNTMetricRawJSONFormat.h index 612480cb0..a846ed43b 100644 --- a/Source/santametricservice/Formats/SNTMetricRawJSONFormat.h +++ b/Source/santametricservice/Formats/SNTMetricRawJSONFormat.h @@ -17,5 +17,4 @@ #import "Source/santametricservice/Formats/SNTMetricFormat.h" @interface SNTMetricRawJSONFormat : NSObject -- (NSArray *)convert:(NSDictionary *)metrics error:(NSError **)err; @end diff --git a/Source/santametricservice/Formats/SNTMetricRawJSONFormat.m b/Source/santametricservice/Formats/SNTMetricRawJSONFormat.m index 67d448633..0ebca45b6 100644 --- a/Source/santametricservice/Formats/SNTMetricRawJSONFormat.m +++ b/Source/santametricservice/Formats/SNTMetricRawJSONFormat.m @@ -78,7 +78,13 @@ - (NSDictionary *)normalize:(NSDictionary *)metrics { NSDictionary *normalizedMetrics = [self normalize:metrics]; if (![NSJSONSerialization isValidJSONObject:normalizedMetrics]) { - LOGE(@"unable to convert metrics to JSON: invalid metrics"); + *err = [[NSError alloc] + initWithDomain:@"SNTMetricRawJSONFileWriter" + code:EINVAL + userInfo:@{ + NSLocalizedDescriptionKey : @"unable to convert metrics to JSON: invalid metrics" + }]; + return nil; } diff --git a/Source/santametricservice/SNTMetricServiceTest.m b/Source/santametricservice/SNTMetricServiceTest.m index 7d01c64c5..467cd92fe 100644 --- a/Source/santametricservice/SNTMetricServiceTest.m +++ b/Source/santametricservice/SNTMetricServiceTest.m @@ -183,7 +183,6 @@ - (NSDictionary *)convertJSONDateStringsToNSDateWithJson:(NSDictionary *)jsonDat - (void)testDefaultConfigOptionsDoNotExport { SNTMetricService *ms = [[SNTMetricService alloc] init]; - // OCMStub([self.mockConfigurator exportMetrics]).andReturn(NO); [ms exportForMonitoring:validMetricsDict]; diff --git a/Source/santametricservice/Writers/SNTMetricFileWriter.m b/Source/santametricservice/Writers/SNTMetricFileWriter.m index 078a683b9..6e15305c0 100644 --- a/Source/santametricservice/Writers/SNTMetricFileWriter.m +++ b/Source/santametricservice/Writers/SNTMetricFileWriter.m @@ -62,7 +62,7 @@ - (BOOL)write:(NSArray *)metrics toURL:(NSURL *)url error:(NSError **) if (@available(macos 10.15, *)) { [file writeData:entryData error:error]; - if (*error != nil) { + if (error != nil && *error != nil) { return NO; } } else { diff --git a/Source/santametricservice/Writers/SNTMetricFileWriterTest.m b/Source/santametricservice/Writers/SNTMetricFileWriterTest.m index 59bdcbe10..2f5c9569e 100644 --- a/Source/santametricservice/Writers/SNTMetricFileWriterTest.m +++ b/Source/santametricservice/Writers/SNTMetricFileWriterTest.m @@ -58,12 +58,9 @@ - (void)testWritingDataToFileWorks { BOOL success = [fileWriter write:input toURL:url error:&err]; - if (!success) { - NSLog(@"error: %@\n", err); - } - - XCTAssertEqual(YES, success); + XCTAssertTrue(success, @"error: %@", err); XCTAssertNil(err); + const char newline[1] = {'\n'}; // Read file ensure it only contains the first line followed by a Newline @@ -82,12 +79,7 @@ - (void)testWritingDataToFileWorks { input = @[ firstLine, secondLine ]; success = [fileWriter write:input toURL:url error:&err]; - XCTAssertEqual(YES, success); - XCTAssertNil(err); - - if (!success) { - NSLog(@"error: %@\n", err); - } + XCTAssertTrue(success, @"error: %@", err); testFileContents = [NSData dataWithContentsOfFile:url.path]; XCTAssertEqualObjects(expected, testFileContents); From ce5dd891ab68db77dcf3208e2d77c3482bcba7e8 Mon Sep 17 00:00:00 2001 From: Pete Markowsky Date: Wed, 22 Sep 2021 11:47:34 -0400 Subject: [PATCH 07/14] Added check for self. --- Source/santametricservice/SNTMetricService.m | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Source/santametricservice/SNTMetricService.m b/Source/santametricservice/SNTMetricService.m index 0d5815c67..7a6ac39eb 100644 --- a/Source/santametricservice/SNTMetricService.m +++ b/Source/santametricservice/SNTMetricService.m @@ -36,11 +36,13 @@ @implementation SNTMetricService { - (instancetype)init { self = [super init]; + if (self) { + rawJSONFormatter = [[SNTMetricRawJSONFormat alloc] init]; + metricWriters = @{@"file" : [[SNTMetricFileWriter alloc] init]}; - rawJSONFormatter = [[SNTMetricRawJSONFormat alloc] init]; - metricWriters = @{@"file" : [[SNTMetricFileWriter alloc] init]}; + _queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0); + } - _queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0); return self; } From 9280d92fe818fecec9d0172373a61fff12c0d4a2 Mon Sep 17 00:00:00 2001 From: Pete Markowsky Date: Wed, 22 Sep 2021 12:01:58 -0400 Subject: [PATCH 08/14] Fixes for more review feedback. --- Source/santametricservice/BUILD | 4 ++-- Source/santametricservice/Formats/BUILD | 14 +++++++------- Source/santametricservice/SNTMetricServiceTest.m | 2 +- .../Writers/SNTMetricFileWriterTest.m | 7 ++++++- 4 files changed, 16 insertions(+), 11 deletions(-) diff --git a/Source/santametricservice/BUILD b/Source/santametricservice/BUILD index df6795b8b..b15b445ea 100644 --- a/Source/santametricservice/BUILD +++ b/Source/santametricservice/BUILD @@ -17,7 +17,7 @@ objc_library( "//Source/common:SNTLogging", "//Source/common:SNTMetricSet", "//Source/common:SNTXPCMetricServiceInterface", - "//Source/santametricservice/Formats:SNTMetricRawJsonFormat", + "//Source/santametricservice/Formats:SNTMetricRawJSONFormat", "//Source/santametricservice/Writers:SNTMetricFileWriter", "@MOLCodesignChecker", "@MOLXPCConnection", @@ -37,7 +37,7 @@ test_suite( name = "unit_tests", tests = [ ":SNTMetricServiceTest", - "//Source/santametricservice/Formats:SNTMetricRawJsonFormatTest", + "//Source/santametricservice/Formats:SNTMetricRawJSONFormatTest", "//Source/santametricservice/Writers:SNTMetricFileWriterTest", ], ) diff --git a/Source/santametricservice/Formats/BUILD b/Source/santametricservice/Formats/BUILD index 007881d4c..fc3a3a9bd 100644 --- a/Source/santametricservice/Formats/BUILD +++ b/Source/santametricservice/Formats/BUILD @@ -11,11 +11,11 @@ objc_library( ) objc_library( - name = "SNTMetricRawJsonFormat", + name = "SNTMetricRawJSONFormat", srcs = [ "SNTMetricFormat.h", - "SNTMetricRawJsonFormat.h", - "SNTMetricRawJsonFormat.m", + "SNTMetricRawJSONFormat.h", + "SNTMetricRawJSONFormat.m", ], deps = [ ":SNTMetricFormat", @@ -24,13 +24,13 @@ objc_library( ) santa_unit_test( - name = "SNTMetricRawJsonFormatTest", + name = "SNTMetricRawJSONFormatTest", srcs = [ - "SNTMetricRawJsonFormatTest.m", + "SNTMetricRawJSONFormatTest.m", ], structured_resources = glob(["testdata/**"]), deps = [ - ":SNTMetricRawJsonFormat", + ":SNTMetricRawJSONFormat", "//Source/common:SNTMetricSet", ], ) @@ -38,6 +38,6 @@ santa_unit_test( test_suite( name = "format_tests", tests = [ - ":SNTMetricRawJsonFormatTest", + ":SNTMetricRawJSONFormatTest", ], ) diff --git a/Source/santametricservice/SNTMetricServiceTest.m b/Source/santametricservice/SNTMetricServiceTest.m index 467cd92fe..8944add32 100644 --- a/Source/santametricservice/SNTMetricServiceTest.m +++ b/Source/santametricservice/SNTMetricServiceTest.m @@ -217,6 +217,6 @@ - (void)testWritingRawJSONFile { // Convert JSON's date strings back into dates. [self convertJSONDateStringsToNSDateWithJson:parsedJSONData]; - XCTAssertEqualObjects(validMetricsDict, parsedJSONData, @"invalid json created"); + XCTAssertEqualObjects(validMetricsDict, parsedJSONData, @"invalid JSON created"); } @end diff --git a/Source/santametricservice/Writers/SNTMetricFileWriterTest.m b/Source/santametricservice/Writers/SNTMetricFileWriterTest.m index 2f5c9569e..229643c2c 100644 --- a/Source/santametricservice/Writers/SNTMetricFileWriterTest.m +++ b/Source/santametricservice/Writers/SNTMetricFileWriterTest.m @@ -25,7 +25,12 @@ - (void)setUp { - (void)tearDown { // delete the temp dir - [[NSFileManager defaultManager] removeItemAtPath:self.tempDir error:NULL]; + NSError *err; + [[NSFileManager defaultManager] removeItemAtPath:self.tempDir error:&err]; + + if (err != nil) { + NSLog(@"unable to remove %@, error: %@", self.tempDir, err); + } } - (void)testWritingToNonFileURLFails { From f0a81ff09fed1a38eebc2af66ceca323121637d7 Mon Sep 17 00:00:00 2001 From: Pete Markowsky Date: Wed, 22 Sep 2021 12:16:00 -0400 Subject: [PATCH 09/14] Changed normalization functions to use block iteration. --- .../Formats/SNTMetricRawJSONFormat.m | 31 ++++++++++--------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/Source/santametricservice/Formats/SNTMetricRawJSONFormat.m b/Source/santametricservice/Formats/SNTMetricRawJSONFormat.m index 0ebca45b6..0c31b4d17 100644 --- a/Source/santametricservice/Formats/SNTMetricRawJSONFormat.m +++ b/Source/santametricservice/Formats/SNTMetricRawJSONFormat.m @@ -31,13 +31,15 @@ - (instancetype)init { - (NSArray *)normalizeArray:(NSArray *)arr { NSMutableArray *normalized = [NSMutableArray arrayWithArray:arr]; - for (int i = 0; i < [arr count]; i++) { - if ([arr[i] isKindOfClass:[NSArray class]]) { - normalized[i] = [self normalizeArray:(NSArray *)arr[i]]; - } else if ([arr[i] isKindOfClass:[NSDictionary class]]) { - normalized[i] = [self normalize:(NSDictionary *)arr[i]]; + [normalized enumerateObjectsUsingBlock:^(id value, NSUInteger index, BOOL *stop) { + if ([value isKindOfClass:[NSDate class]]) { + normalized[index] = [self->_dateFormatter stringFromDate:(NSDate *)value]; + } else if ([value isKindOfClass:[NSArray class]]) { + normalized[index] = [self normalizeArray:(NSArray *)value]; + } else if ([value isKindOfClass:[NSDictionary class]]) { + normalized[index] = [self normalize:(NSDictionary *)value]; } - } + }]; return normalized; } @@ -50,16 +52,15 @@ - (NSDictionary *)normalize:(NSDictionary *)metrics { // to JSON. NSMutableDictionary *normalizedMetrics = [NSMutableDictionary dictionaryWithDictionary:metrics]; - for (NSString *key in metrics) { - const id object = [metrics objectForKey:key]; - if ([object isKindOfClass:[NSDate class]]) { - normalizedMetrics[key] = [self->_dateFormatter stringFromDate:(NSDate *)object]; - } else if ([object isKindOfClass:[NSDictionary class]]) { - normalizedMetrics[key] = [self normalize:metrics[key]]; - } else if ([object isKindOfClass:[NSArray class]]) { - normalizedMetrics[key] = [self normalizeArray:(NSArray *)object]; + [metrics enumerateKeysAndObjectsUsingBlock:^(id key, id value, BOOL *stop) { + if ([value isKindOfClass:[NSDate class]]) { + normalizedMetrics[key] = [self->_dateFormatter stringFromDate:(NSDate *)value]; + } else if ([value isKindOfClass:[NSDictionary class]]) { + normalizedMetrics[key] = [self normalize:(NSDictionary *)value]; + } else if ([value isKindOfClass:[NSArray class]]) { + normalizedMetrics[key] = [self normalizeArray:(NSArray *)value]; } - } + }]; return (NSDictionary *)normalizedMetrics; } From aa59cb1a6ff99f1ad255542aadab141ee3fb1dc6 Mon Sep 17 00:00:00 2001 From: Pete Markowsky Date: Wed, 22 Sep 2021 12:22:02 -0400 Subject: [PATCH 10/14] Added missing check for a nil error. --- .../Formats/SNTMetricRawJSONFormat.m | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/Source/santametricservice/Formats/SNTMetricRawJSONFormat.m b/Source/santametricservice/Formats/SNTMetricRawJSONFormat.m index 0c31b4d17..6b0db2d15 100644 --- a/Source/santametricservice/Formats/SNTMetricRawJSONFormat.m +++ b/Source/santametricservice/Formats/SNTMetricRawJSONFormat.m @@ -79,13 +79,14 @@ - (NSDictionary *)normalize:(NSDictionary *)metrics { NSDictionary *normalizedMetrics = [self normalize:metrics]; if (![NSJSONSerialization isValidJSONObject:normalizedMetrics]) { - *err = [[NSError alloc] - initWithDomain:@"SNTMetricRawJSONFileWriter" - code:EINVAL - userInfo:@{ - NSLocalizedDescriptionKey : @"unable to convert metrics to JSON: invalid metrics" - }]; - + if (err != nil) { + *err = [[NSError alloc] + initWithDomain:@"SNTMetricRawJSONFileWriter" + code:EINVAL + userInfo:@{ + NSLocalizedDescriptionKey : @"unable to convert metrics to JSON: invalid metrics" + }]; + } return nil; } From 5d0800c0a03c18e7381efb7ac40b4d79413f885f Mon Sep 17 00:00:00 2001 From: Pete Markowsky Date: Wed, 22 Sep 2021 12:27:21 -0400 Subject: [PATCH 11/14] More changes for feedback. --- Source/santametricservice/Formats/SNTMetricRawJSONFormatTest.m | 3 +-- Source/santametricservice/SNTMetricService.m | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/Source/santametricservice/Formats/SNTMetricRawJSONFormatTest.m b/Source/santametricservice/Formats/SNTMetricRawJSONFormatTest.m index 54bd2257f..6129bd8b7 100644 --- a/Source/santametricservice/Formats/SNTMetricRawJSONFormatTest.m +++ b/Source/santametricservice/Formats/SNTMetricRawJSONFormatTest.m @@ -136,8 +136,7 @@ - (void)testMetricsConversionToJSON { NSString *path = [[NSBundle bundleForClass:[self class]] resourcePath]; path = [path stringByAppendingPathComponent:@"testdata/json/test.json"]; - NSFileManager *filemgr = [NSFileManager defaultManager]; - NSData *goldenFileData = [filemgr contentsAtPath:path]; + NSData *goldenFileData = [NSData dataWithContentsOfFile:path]; XCTAssertNotNil(goldenFileData, @"unable to open / read golden file"); diff --git a/Source/santametricservice/SNTMetricService.m b/Source/santametricservice/SNTMetricService.m index 7a6ac39eb..59d353827 100644 --- a/Source/santametricservice/SNTMetricService.m +++ b/Source/santametricservice/SNTMetricService.m @@ -51,7 +51,7 @@ - (instancetype)init { */ - (NSString *)messageFromError:(NSError *)error { NSString *message = [error localizedDescription]; - NSString *details = [error localizedFailureReason] ? [error localizedFailureReason] : @""; + NSString *details = [error localizedFailureReason] ?: @""; return [NSString stringWithFormat:@"%@ %@", message, details]; } From 1c81d621eb868558d80487f4d1fa468b44212494 Mon Sep 17 00:00:00 2001 From: Pete Markowsky Date: Wed, 22 Sep 2021 13:15:47 -0400 Subject: [PATCH 12/14] Reformatted text to align with style guide. --- .../Formats/SNTMetricRawJSONFormat.m | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/Source/santametricservice/Formats/SNTMetricRawJSONFormat.m b/Source/santametricservice/Formats/SNTMetricRawJSONFormat.m index 6b0db2d15..ba8b603dd 100644 --- a/Source/santametricservice/Formats/SNTMetricRawJSONFormat.m +++ b/Source/santametricservice/Formats/SNTMetricRawJSONFormat.m @@ -80,12 +80,11 @@ - (NSDictionary *)normalize:(NSDictionary *)metrics { if (![NSJSONSerialization isValidJSONObject:normalizedMetrics]) { if (err != nil) { - *err = [[NSError alloc] - initWithDomain:@"SNTMetricRawJSONFileWriter" - code:EINVAL - userInfo:@{ - NSLocalizedDescriptionKey : @"unable to convert metrics to JSON: invalid metrics" - }]; + *err = [[NSError alloc] initWithDomain:@"SNTMetricRawJSONFileWriter" + code:EINVAL + userInfo:@{ + NSLocalizedDescriptionKey : @"unable to convert metrics to JSON: invalid metrics" + }]; } return nil; } From 6a9be5dbf37c7459b30c6b242a0b5cd7d4ae1c08 Mon Sep 17 00:00:00 2001 From: Pete Markowsky Date: Wed, 22 Sep 2021 13:36:38 -0400 Subject: [PATCH 13/14] Added tests for nil/NULL errors. --- .../Formats/SNTMetricRawJSONFormatTest.m | 7 +++++++ .../Writers/SNTMetricFileWriterTest.m | 14 ++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/Source/santametricservice/Formats/SNTMetricRawJSONFormatTest.m b/Source/santametricservice/Formats/SNTMetricRawJSONFormatTest.m index 6129bd8b7..41468fd1a 100644 --- a/Source/santametricservice/Formats/SNTMetricRawJSONFormatTest.m +++ b/Source/santametricservice/Formats/SNTMetricRawJSONFormatTest.m @@ -149,4 +149,11 @@ - (void)testMetricsConversionToJSON { XCTAssertEqualObjects(expectedJSONDict, jsonDict, @"generated JSON does not match golden file."); } +- (void)testPassingANilOrNullErrorDoesNotCrash { + SNTMetricRawJSONFormat *formatter = [[SNTMetricRawJSONFormat alloc] init]; + + NSArray *output = [formatter convert:validMetricsDict error:nil]; + output = [formatter convert:validMetricsDict error:NULL]; +} + @end diff --git a/Source/santametricservice/Writers/SNTMetricFileWriterTest.m b/Source/santametricservice/Writers/SNTMetricFileWriterTest.m index 229643c2c..6cd3e4aab 100644 --- a/Source/santametricservice/Writers/SNTMetricFileWriterTest.m +++ b/Source/santametricservice/Writers/SNTMetricFileWriterTest.m @@ -89,4 +89,18 @@ - (void)testWritingDataToFileWorks { testFileContents = [NSData dataWithContentsOfFile:url.path]; XCTAssertEqualObjects(expected, testFileContents); } + +- (void)testThatPassingANilOrNullErrorDoesNotCrash { + NSString *testFile = [NSString pathWithComponents:@[ @"file://", self.tempDir, @"test.data" ]]; + NSURL *url = [NSURL URLWithString:testFile]; + + SNTMetricFileWriter *fileWriter = [[SNTMetricFileWriter alloc] init]; + + NSData *firstLine = [@"AAAAAAAA" dataUsingEncoding:NSUTF8StringEncoding]; + + BOOL success = [fileWriter write:@[ firstLine ] toURL:url error:nil]; + XCTAssertTrue(success); + success = [fileWriter write:@[ firstLine ] toURL:url error:NULL]; + XCTAssertTrue(success); +} @end \ No newline at end of file From 3498893f62356c8410549045ca7fca0280269915 Mon Sep 17 00:00:00 2001 From: Pete Markowsky Date: Wed, 22 Sep 2021 14:07:29 -0400 Subject: [PATCH 14/14] Incorporated review feedback. --- .../Formats/SNTMetricRawJSONFormat.m | 13 ++++----- .../Formats/SNTMetricRawJSONFormatTest.m | 2 +- .../santametricservice/SNTMetricServiceTest.m | 27 +++++++++---------- .../Writers/SNTMetricFileWriter.m | 8 +++--- .../Writers/SNTMetricFileWriterTest.m | 3 +-- 5 files changed, 25 insertions(+), 28 deletions(-) diff --git a/Source/santametricservice/Formats/SNTMetricRawJSONFormat.m b/Source/santametricservice/Formats/SNTMetricRawJSONFormat.m index ba8b603dd..11ab5dd58 100644 --- a/Source/santametricservice/Formats/SNTMetricRawJSONFormat.m +++ b/Source/santametricservice/Formats/SNTMetricRawJSONFormat.m @@ -80,11 +80,12 @@ - (NSDictionary *)normalize:(NSDictionary *)metrics { if (![NSJSONSerialization isValidJSONObject:normalizedMetrics]) { if (err != nil) { - *err = [[NSError alloc] initWithDomain:@"SNTMetricRawJSONFileWriter" - code:EINVAL - userInfo:@{ - NSLocalizedDescriptionKey : @"unable to convert metrics to JSON: invalid metrics" - }]; + *err = [[NSError alloc] + initWithDomain:@"SNTMetricRawJSONFileWriter" + code:EINVAL + userInfo:@{ + NSLocalizedDescriptionKey : @"unable to convert metrics to JSON: invalid metrics" + }]; } return nil; } @@ -92,7 +93,7 @@ - (NSDictionary *)normalize:(NSDictionary *)metrics { NSData *json = [NSJSONSerialization dataWithJSONObject:normalizedMetrics options:NSJSONWritingPrettyPrinted error:err]; - if (json == nil && *err != nil) { + if (json == nil || (err != nil && *err != nil)) { return nil; } diff --git a/Source/santametricservice/Formats/SNTMetricRawJSONFormatTest.m b/Source/santametricservice/Formats/SNTMetricRawJSONFormatTest.m index 41468fd1a..4efcf91f2 100644 --- a/Source/santametricservice/Formats/SNTMetricRawJSONFormatTest.m +++ b/Source/santametricservice/Formats/SNTMetricRawJSONFormatTest.m @@ -124,7 +124,7 @@ - (void)testMetricsConversionToJSON { NSError *err = nil; NSArray *output = [formatter convert:validMetricsDict error:&err]; - XCTAssertEqual(1, [output count]); + XCTAssertEqual(1, output.count); XCTAssertNotNil(output[0]); XCTAssertNil(err); diff --git a/Source/santametricservice/SNTMetricServiceTest.m b/Source/santametricservice/SNTMetricServiceTest.m index 8944add32..9bfab9115 100644 --- a/Source/santametricservice/SNTMetricServiceTest.m +++ b/Source/santametricservice/SNTMetricServiceTest.m @@ -28,7 +28,7 @@ - (void)initializeValidMetricsDict { @"root_labels" : @{@"hostname" : @"testHost", @"username" : @"testUser"}, @"metrics" : @{ @"/build/label" : @{ - @"type" : [NSNumber numberWithInt:(int)SNTMetricTypeConstantString], + @"type" : @((int)SNTMetricTypeConstantString), @"fields" : @{ @"" : @[ @{ @"value" : @"", @@ -39,7 +39,7 @@ - (void)initializeValidMetricsDict { } }, @"/santa/events" : @{ - @"type" : [NSNumber numberWithInt:(int)SNTMetricTypeCounter], + @"type" : @((int)SNTMetricTypeCounter), @"fields" : @{ @"rule_type" : @[ @{ @@ -58,7 +58,7 @@ - (void)initializeValidMetricsDict { }, }, @"/santa/rules" : @{ - @"type" : [NSNumber numberWithInt:(int)SNTMetricTypeGaugeInt64], + @"type" : @((int)SNTMetricTypeGaugeInt64), @"fields" : @{ @"rule_type" : @[ @{ @@ -77,7 +77,7 @@ - (void)initializeValidMetricsDict { }, }, @"/santa/using_endpoint_security_framework" : @{ - @"type" : [NSNumber numberWithInt:(int)SNTMetricTypeConstantBool], + @"type" : @((int)SNTMetricTypeConstantBool), @"fields" : @{ @"" : @[ @{ @"value" : @"", @@ -88,13 +88,13 @@ - (void)initializeValidMetricsDict { } }, @"/proc/birth_timestamp" : @{ - @"type" : [NSNumber numberWithInt:(int)SNTMetricTypeConstantInt64], + @"type" : @((int)SNTMetricTypeConstantInt64), @"fields" : @{ @"" : @[ @{ @"value" : @"", @"created" : fixedDate, @"last_updated" : fixedDate, - @"data" : [NSNumber numberWithLong:1250999830800] + @"data" : @1250999830800L, } ] }, }, @@ -105,18 +105,18 @@ - (void)initializeValidMetricsDict { @"value" : @"", @"created" : fixedDate, @"last_updated" : fixedDate, - @"data" : [NSNumber numberWithInt:987654321] + @"data" : @987654321, } ] } }, @"/proc/memory/resident_size" : @{ - @"type" : [NSNumber numberWithInt:(int)SNTMetricTypeGaugeInt64], + @"type" : @((int)SNTMetricTypeGaugeInt64), @"fields" : @{ @"" : @[ @{ @"value" : @"", @"created" : fixedDate, @"last_updated" : fixedDate, - @"data" : [NSNumber numberWithInt:123456789] + @"data" : @123456789, } ] }, }, @@ -142,8 +142,7 @@ - (void)setUp { self.tempDir = [[NSFileManager defaultManager] stringWithFileSystemRepresentation:tempPath length:strlen(tempPath)]; - self.jsonURL = - [NSURL URLWithString:[NSString pathWithComponents:@[ @"file://", self.tempDir, @"test.json" ]]]; + self.jsonURL = [NSURL fileURLWithPathComponents:@[ self.tempDir, @"test.json" ]]; } - (void)tearDown { @@ -171,7 +170,7 @@ - (NSDictionary *)convertJSONDateStringsToNSDateWithJson:(NSDictionary *)jsonDat for (NSString *field in metric[@"fields"]) { NSMutableArray *values = metric[@"fields"][field]; - for (int i = 0; i < [values count]; i++) { + for (int i = 0; i < values.count; ++i) { values[i][@"created"] = [self createNSDateFromDateString:values[i][@"created"]]; values[i][@"last_updated"] = [self createNSDateFromDateString:values[i][@"last_updated"]]; } @@ -189,7 +188,7 @@ - (void)testDefaultConfigOptionsDoNotExport { // Check the temp dir NSArray *items = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:self.tempDir error:NULL]; - XCTAssertEqual(0, [items count]); + XCTAssertEqual(0, items.count, @"found unexpected files in %@", self.tempDir); } - (void)testWritingRawJSONFile { @@ -203,7 +202,7 @@ - (void)testWritingRawJSONFile { // Ensure that this has written 1 file that is well formed. NSArray *items = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:self.tempDir error:NULL]; - XCTAssertEqual(1, [items count], @"failed to create JSON metrics file"); + XCTAssertEqual(1, items.count, @"failed to create JSON metrics file"); NSData *jsonData = [NSData dataWithContentsOfFile:self.jsonURL.path options:NSDataReadingUncached diff --git a/Source/santametricservice/Writers/SNTMetricFileWriter.m b/Source/santametricservice/Writers/SNTMetricFileWriter.m index 6e15305c0..d4d3c5276 100644 --- a/Source/santametricservice/Writers/SNTMetricFileWriter.m +++ b/Source/santametricservice/Writers/SNTMetricFileWriter.m @@ -20,7 +20,7 @@ @implementation SNTMetricFileWriter /* * Open a file for appending. */ -- (NSFileHandle *)fileHandleForAppendingAtPath:(NSString *)path createMode:(mode_t)mode { +- (NSFileHandle *)fileHandleForNewFileAtPath:(NSString *)path createMode:(mode_t)mode { int fd; if (!path) { return nil; @@ -44,7 +44,7 @@ - (BOOL)write:(NSArray *)metrics toURL:(NSURL *)url error:(NSError **) return NO; } - NSFileHandle *file = [self fileHandleForAppendingAtPath:url.path createMode:0600]; + NSFileHandle *file = [self fileHandleForNewFileAtPath:url.path createMode:0600]; const char newline[1] = {'\n'}; if (file == nil) { @@ -60,9 +60,7 @@ - (BOOL)write:(NSArray *)metrics toURL:(NSURL *)url error:(NSError **) [entryData appendBytes:newline length:1]; if (@available(macos 10.15, *)) { - [file writeData:entryData error:error]; - - if (error != nil && *error != nil) { + if (![file writeData:entryData error:error]) { return NO; } } else { diff --git a/Source/santametricservice/Writers/SNTMetricFileWriterTest.m b/Source/santametricservice/Writers/SNTMetricFileWriterTest.m index 6cd3e4aab..5b8658cd4 100644 --- a/Source/santametricservice/Writers/SNTMetricFileWriterTest.m +++ b/Source/santametricservice/Writers/SNTMetricFileWriterTest.m @@ -49,8 +49,7 @@ - (void)testWritingToNonFileURLFails { } - (void)testWritingDataToFileWorks { - NSString *testFile = [NSString pathWithComponents:@[ @"file://", self.tempDir, @"test.data" ]]; - NSURL *url = [NSURL URLWithString:testFile]; + NSURL *url = [NSURL fileURLWithPathComponents:@[ self.tempDir, @"test.data" ]]; SNTMetricFileWriter *fileWriter = [[SNTMetricFileWriter alloc] init];