Index: src/OFData.h ================================================================== --- src/OFData.h +++ src/OFData.h @@ -21,10 +21,14 @@ OF_ASSUME_NONNULL_BEGIN @class OFString; @class OFURL; + +enum { + OF_DATA_SEARCH_BACKWARDS = 1 +}; /*! * @class OFData OFData.h ObjFW/OFData.h * * @brief A class for storing arbitrary data in an array. @@ -277,10 +281,27 @@ * @param index The number of the item to return * @return The specified item of the OFData */ - (const void *)itemAtIndex: (size_t)index OF_RETURNS_INNER_POINTER; +/*! + * @brief Returns the range of the data. + * + * @param data The data to search for + * @param options Options modifying search behavior.@n + * Possible values are: + * Value | Description + * ---------------------------|----------------------------- + * `OF_DATA_SEARCH_BACKWARDS` | Search backwards in the data + * @param range The range in which to search + * @return The range of the first occurrence of the data or a range with + * `OF_NOT_FOUND` as start position if it was not found. + */ +- (of_range_t)rangeOfData: (OFData *)data + options: (int)options + range: (of_range_t)range; + #ifdef OF_HAVE_FILES /*! * @brief Writes the OFData into the specified file. * * @param path The path of the file to write to Index: src/OFData.m ================================================================== --- src/OFData.m +++ src/OFData.m @@ -540,10 +540,53 @@ - (OFString *)stringByBase64Encoding { return of_base64_encode(_items, _count * _itemSize); } + +- (of_range_t)rangeOfData: (OFData *)data + options: (int)options + range: (of_range_t)range +{ + const char *search; + size_t searchLength; + + if (range.length > SIZE_MAX - range.location || + range.location + range.length > _count) + @throw [OFOutOfRangeException exception]; + + if (data == nil || [data itemSize] != _itemSize) + @throw [OFInvalidArgumentException exception]; + + if ((searchLength = [data count]) == 0) + return of_range(0, 0); + + if (searchLength > range.length) + return of_range(OF_NOT_FOUND, 0); + + search = [data items]; + + if (options & OF_DATA_SEARCH_BACKWARDS) { + for (size_t i = range.length - searchLength;; i--) { + if (memcmp(_items + i * _itemSize, search, + searchLength * _itemSize) == 0) + return of_range(i, searchLength); + + /* No match and we're at the last item */ + if (i == 0) + break; + } + } else { + for (size_t i = range.location; + i <= range.length - searchLength; i++) + if (memcmp(_items + i * _itemSize, search, + searchLength * _itemSize) == 0) + return of_range(i, searchLength); + } + + return of_range(OF_NOT_FOUND, 0); +} #ifdef OF_HAVE_FILES - (void)writeToFile: (OFString *)path { OFFile *file = [[OFFile alloc] initWithPath: path Index: src/OFString.h ================================================================== --- src/OFString.h +++ src/OFString.h @@ -960,11 +960,11 @@ /*! * @brief Returns the range of the string. * * @param string The string to search - * @param options Options modifying search behaviour.@n + * @param options Options modifying search behavior.@n * Possible values are: * Value | Description * -----------------------------|------------------------------- * `OF_STRING_SEARCH_BACKWARDS` | Search backwards in the string * @return The range of the first occurrence of the string or a range with Index: tests/OFDataTests.m ================================================================== --- tests/OFDataTests.m +++ tests/OFDataTests.m @@ -21,10 +21,11 @@ #import "OFData.h" #import "OFString.h" #import "OFAutoreleasePool.h" +#import "OFInvalidArgumentException.h" #import "OFOutOfRangeException.h" #import "TestsAppDelegate.h" static OFString *module = @"OFData"; @@ -35,10 +36,11 @@ { OFAutoreleasePool *pool = [[OFAutoreleasePool alloc] init]; OFMutableData *mutable; OFData *immutable; void *raw[2]; + of_range_t range; TEST(@"+[dataWithItemSize:]", (mutable = [OFMutableData dataWithItemSize: 4096])) OFObject *tmp = [[[OFObject alloc] init] autorelease]; @@ -94,10 +96,69 @@ TEST(@"-[insertItems:atIndex:count:]", R([mutable insertItems: "bc" atIndex: 1 count: 2]) && [mutable count] == 5 && memcmp([mutable items], "abcde", 5) == 0) + + immutable = [OFData dataWithItems: "aaabaccdacaabb" + itemSize: 2 + count: 7]; + TEST(@"-[rangeOfString:options:range:]", + R(range = [immutable rangeOfData: [OFData dataWithItems: "aa" + itemSize: 2 + count: 1] + options: 0 + range: of_range(0, 7)]) && + range.location == 0 && range.length == 1 && + R(range = [immutable rangeOfData: [OFData dataWithItems: "aa" + itemSize: 2 + count: 1] + options: OF_DATA_SEARCH_BACKWARDS + range: of_range(0, 7)]) && + range.location == 5 && range.length == 1 && + R(range = [immutable rangeOfData: [OFData dataWithItems: "ac" + itemSize: 2 + count: 1] + options: 0 + range: of_range(0, 7)]) && + range.location == 2 && range.length == 1 && + R(range = [immutable rangeOfData: [OFData dataWithItems: "aabb" + itemSize: 2 + count: 2] + options: 0 + range: of_range(0, 7)]) && + range.location == 5 && range.length == 2 && + R(range = [immutable rangeOfData: [OFData dataWithItems: "aa" + itemSize: 2 + count: 1] + options: 0 + range: of_range(1, 6)]) && + range.location == 5 && range.length == 1 && + R(range = [immutable rangeOfData: [OFData dataWithItems: "aa" + itemSize: 2 + count: 1] + options: OF_DATA_SEARCH_BACKWARDS + range: of_range(0, 5)]) && + range.location == 0 && range.length == 1) + + EXPECT_EXCEPTION( + @"-[rangeOfString:options:range:] failing on different itemSize", + OFInvalidArgumentException, + [immutable rangeOfData: [OFData dataWithItems: "aaa" + itemSize: 3 + count: 1] + options: 0 + range: of_range(0, 1)]) + + EXPECT_EXCEPTION( + @"-[rangeOfString:options:range:] failing on out of range", + OFOutOfRangeException, + [immutable rangeOfData: [OFData dataWithItems: "" + itemSize: 2 + count: 0] + options: 0 + range: of_range(8, 1)]) TEST(@"-[MD5Hash]", [[mutable MD5Hash] isEqual: [@"abcde" MD5Hash]]) TEST(@"-[RIPEMD160Hash]", [[mutable RIPEMD160Hash] isEqual: [@"abcde" RIPEMD160Hash]])