Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
050076B31C7A4354000819B5 /* SPTPersistentCachePosixWrapper.h in Headers */ = {isa = PBXBuildFile; fileRef = 050076B11C7A4354000819B5 /* SPTPersistentCachePosixWrapper.h */; };
050076B41C7A4354000819B5 /* SPTPersistentCachePosixWrapper.m in Sources */ = {isa = PBXBuildFile; fileRef = 050076B21C7A4354000819B5 /* SPTPersistentCachePosixWrapper.m */; };
050076B51C7A4DC7000819B5 /* SPTPersistentCachePosixWrapper.m in Sources */ = {isa = PBXBuildFile; fileRef = 050076B21C7A4354000819B5 /* SPTPersistentCachePosixWrapper.m */; };
5EB3C5EB1CEA1B900091739E /* NSError+SPTPersistentCacheDomainErrors.h in Headers */ = {isa = PBXBuildFile; fileRef = DD1D238C1C7785A900D0477A /* NSError+SPTPersistentCacheDomainErrors.h */; };
9C9E707B1C790F0B00E1CBE6 /* SPTPersistentCacheObjectDescription.m in Sources */ = {isa = PBXBuildFile; fileRef = 9C9E70791C790F0B00E1CBE6 /* SPTPersistentCacheObjectDescription.m */; };
9C9E707C1C790F0B00E1CBE6 /* SPTPersistentCacheObjectDescription.h in Headers */ = {isa = PBXBuildFile; fileRef = 9C9E707A1C790F0B00E1CBE6 /* SPTPersistentCacheObjectDescription.h */; };
9C9E707D1C790F3700E1CBE6 /* SPTPersistentCacheObjectDescription.m in Sources */ = {isa = PBXBuildFile; fileRef = 9C9E70791C790F0B00E1CBE6 /* SPTPersistentCacheObjectDescription.m */; };
Expand Down Expand Up @@ -236,6 +237,7 @@
DD1D23851C77857E00D0477A /* SPTPersistentCache.h in Headers */,
DD1D23B51C7785AE00D0477A /* SPTPersistentCache+Private.h in Headers */,
DD1D23BB1C7785AE00D0477A /* SPTPersistentCacheRecord+Private.h in Headers */,
5EB3C5EB1CEA1B900091739E /* NSError+SPTPersistentCacheDomainErrors.h in Headers */,
DD1D23B31C7785AE00D0477A /* crc32iso3309.h in Headers */,
DD1D23BD1C7785AE00D0477A /* SPTPersistentCacheResponse+Private.h in Headers */,
C4EA65071C7A547000A6091A /* SPTPersistentCacheDebugUtilities.h in Headers */,
Expand Down
360 changes: 123 additions & 237 deletions Sources/SPTPersistentCache.m

Large diffs are not rendered by default.

29 changes: 29 additions & 0 deletions Sources/SPTPersistentCacheHeader.m
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,12 @@
#import "NSError+SPTPersistentCacheDomainErrors.h"

#include "crc32iso3309.h"
#include <sys/xattr.h>

const SPTPersistentCacheMagicType SPTPersistentCacheMagicValue = 0x46545053; // SPTF
const size_t SPTPersistentCacheRecordHeaderSize = sizeof(SPTPersistentCacheRecordHeader);
static NSString* const SPTPersistentCacheRecordHeaderAttributeName = @"com.spotify.cache";
static int const SPTPersistentCacheHeaderInvalidResult = -1;

_Static_assert(sizeof(SPTPersistentCacheRecordHeader) == 64,
"Struct SPTPersistentCacheRecordHeader has to be packed without padding");
Expand Down Expand Up @@ -69,6 +72,32 @@ SPTPersistentCacheRecordHeader SPTPersistentCacheRecordHeaderMake(uint64_t ttl,
return (SPTPersistentCacheRecordHeader *)data;
}

NSError* SPTPersistentCacheGetHeaderFromFileWithPath(NSString *filePath, SPTPersistentCacheRecordHeader *header)
{
memset(header, 0, SPTPersistentCacheRecordHeaderSize);
ssize_t size = getxattr([filePath UTF8String], [SPTPersistentCacheRecordHeaderAttributeName UTF8String], header, SPTPersistentCacheRecordHeaderSize, 0, 0);
if (size == SPTPersistentCacheHeaderInvalidResult) {
// try to load the legacy header. the fileHandle object uses file descriptor under the hood and responsible for closing it on deallocation.
NSFileHandle* fileHandle = [NSFileHandle fileHandleForReadingAtPath:filePath];
NSMutableData* rawData = [[fileHandle readDataOfLength:SPTPersistentCacheRecordHeaderSize] mutableCopy];
SPTPersistentCacheRecordHeader *legacyHeader = SPTPersistentCacheGetHeaderFromData(rawData.mutableBytes, rawData.length);
// If not enough data to cast to header, its not the file we can process
if (legacyHeader == NULL) {
return [NSError spt_persistentDataCacheErrorWithCode:SPTPersistentCacheLoadingErrorNotEnoughDataToGetHeader];
}
memcpy(header, legacyHeader, SPTPersistentCacheRecordHeaderSize);
}
return SPTPersistentCacheCheckValidHeader(header);
}

NSError* SPTPersistentCacheSetHeaderForFileWithPath(NSString *filepath, const SPTPersistentCacheRecordHeader *header) {
int res = setxattr([filepath UTF8String], [SPTPersistentCacheRecordHeaderAttributeName UTF8String], header, SPTPersistentCacheRecordHeaderSize, 0, 0);
if (res == SPTPersistentCacheHeaderInvalidResult) {
return [NSError spt_persistentDataCacheErrorWithCode:SPTPersistentCacheLoadingErrorFileAttributeOperationFail];
}
return nil;
}

int /*SPTPersistentCacheLoadingError*/ SPTPersistentCacheValidateHeader(const SPTPersistentCacheRecordHeader *header)
{
if (header == NULL) {
Expand Down
136 changes: 136 additions & 0 deletions Tests/SPTPersistentCacheHeaderTests.m
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@
#import <SPTPersistentCache/SPTPersistentCache.h>
#import "SPTPersistentCacheTypeUtilities.h"

static NSString* const SPTCacheRecordFileName = @"cache.record";


@interface SPTPersistentCacheHeaderTests : XCTestCase

Expand Down Expand Up @@ -101,4 +103,138 @@ - (void)testSPTPersistentCacheRecordHeaderMake
XCTAssertEqual(header.crc, SPTPersistentCacheCalculateHeaderCRC(&header));
}

- (void)testSPTPersistentCacheGetHeaderFromFileWithPath
{
// GIVEN
NSString *filePath = [NSTemporaryDirectory() stringByAppendingPathComponent:SPTCacheRecordFileName];
[self removeFileAtPath:filePath];
SPTPersistentCacheRecordHeader header = [self dummyHeader];
XCTAssertNil([self createRecordAtPath:filePath withHeader:&header]);

// WHEN
SPTPersistentCacheRecordHeader loadedHeader;
NSError* error = SPTPersistentCacheGetHeaderFromFileWithPath(filePath, &loadedHeader);

// THEN
XCTAssertNil(error);
XCTAssertEqual(header.reserved1, loadedHeader.reserved1);
XCTAssertEqual(header.reserved2, loadedHeader.reserved2);
XCTAssertEqual(header.reserved3, loadedHeader.reserved3);
XCTAssertEqual(header.reserved4, loadedHeader.reserved4);
XCTAssertEqual(header.flags, loadedHeader.flags);
XCTAssertEqual(header.magic, loadedHeader.magic);
XCTAssertEqual(header.headerSize, loadedHeader.headerSize);
XCTAssertEqual(header.refCount, loadedHeader.refCount);
XCTAssertEqual(header.ttl, loadedHeader.ttl);
XCTAssertEqual(header.payloadSizeBytes, loadedHeader.payloadSizeBytes);
XCTAssertEqual(header.updateTimeSec, loadedHeader.updateTimeSec);
XCTAssertEqual(header.crc, loadedHeader.crc);

[self removeFileAtPath:filePath];
}

- (void)testSPTPersistentCacheGetHeaderFromFileWithPathFailsWithError
{
// GIVEN
NSString *filePath = [NSTemporaryDirectory() stringByAppendingPathComponent:SPTCacheRecordFileName];

// WHEN
SPTPersistentCacheRecordHeader loadedHeader;
NSError* error = SPTPersistentCacheGetHeaderFromFileWithPath(filePath, &loadedHeader);

// THEN
XCTAssertNotNil(error);
}

- (void)testSPTPersistentCacheGetHeaderFromFileWithPathLegacy
{
// GIVEN
SPTPersistentCacheRecordHeader legacyHeader = [self dummyHeader];
NSData* payload = [[NSMutableData dataWithLength:legacyHeader.payloadSizeBytes] copy];
NSMutableData* rawData = [NSMutableData dataWithBytes:&legacyHeader length:SPTPersistentCacheRecordHeaderSize];
[rawData appendData:payload];
// create a record with legacy header (saved to the file with a payload).
NSString *filePath = [NSTemporaryDirectory() stringByAppendingPathComponent:SPTCacheRecordFileName];
[self removeFileAtPath:filePath];
XCTAssertTrue([[NSFileManager defaultManager] createFileAtPath:filePath contents:rawData attributes:nil]);

// WHEN
SPTPersistentCacheRecordHeader loadedHeader;
NSError* error = SPTPersistentCacheGetHeaderFromFileWithPath(filePath, &loadedHeader);

// THEN
XCTAssertNil(error);
XCTAssertEqual(legacyHeader.reserved1, loadedHeader.reserved1);
XCTAssertEqual(legacyHeader.reserved2, loadedHeader.reserved2);
XCTAssertEqual(legacyHeader.reserved3, loadedHeader.reserved3);
XCTAssertEqual(legacyHeader.reserved4, loadedHeader.reserved4);
XCTAssertEqual(legacyHeader.flags, loadedHeader.flags);
XCTAssertEqual(legacyHeader.magic, loadedHeader.magic);
XCTAssertEqual(legacyHeader.headerSize, loadedHeader.headerSize);
XCTAssertEqual(legacyHeader.refCount, loadedHeader.refCount);
XCTAssertEqual(legacyHeader.ttl, loadedHeader.ttl);
XCTAssertEqual(legacyHeader.payloadSizeBytes, loadedHeader.payloadSizeBytes);
XCTAssertEqual(legacyHeader.updateTimeSec, loadedHeader.updateTimeSec);
XCTAssertEqual(legacyHeader.crc, loadedHeader.crc);

[self removeFileAtPath:filePath];
}

- (void)testSPTPersistentCacheSetHeaderForFileWithPath
{
// GIVEN
NSString *filePath = [NSTemporaryDirectory() stringByAppendingPathComponent:SPTCacheRecordFileName];
[self removeFileAtPath:filePath];
SPTPersistentCacheRecordHeader header = [self dummyHeader];

// WHEN
NSError* error = [self createRecordAtPath:filePath withHeader:&header];

// THEN
XCTAssertNil(error);

[self removeFileAtPath:filePath];
}

- (void)testSPTPersistentCacheSetHeaderForFileWithPathFailsWithError
{
// GIVEN
NSString *filePath = [NSTemporaryDirectory() stringByAppendingPathComponent:SPTCacheRecordFileName];

// WHEN
SPTPersistentCacheRecordHeader loadedHeader;
NSError* error = SPTPersistentCacheSetHeaderForFileWithPath(filePath, &loadedHeader);

// THEN
XCTAssertNotNil(error);
}

#pragma mark - Private

- (NSError*)createRecordAtPath:(NSString*)filePath withHeader:(SPTPersistentCacheRecordHeader*)header
{
NSMutableData* data = [NSMutableData dataWithLength:header->payloadSizeBytes];

XCTAssertTrue([[NSFileManager defaultManager] createFileAtPath:filePath contents:data attributes:nil]);
return SPTPersistentCacheSetHeaderForFileWithPath(filePath, header);
}

- (void)removeFileAtPath:(NSString*)filePath
{
if ([[NSFileManager defaultManager] fileExistsAtPath:filePath]) {
NSError* error = nil;
[[NSFileManager defaultManager] removeItemAtPath:filePath error:&error];
XCTAssertNil(error);
}
}

- (SPTPersistentCacheRecordHeader)dummyHeader {
uint64_t ttl = 64;
uint64_t payloadSize = 400;
uint64_t updateTime = spt_uint64rint([[NSDate date] timeIntervalSince1970]);
BOOL isLocked = YES;

return SPTPersistentCacheRecordHeaderMake(ttl, payloadSize, updateTime, isLocked);
}

@end
Loading