Index: src/Makefile ================================================================== --- src/Makefile +++ src/Makefile @@ -33,10 +33,11 @@ OFLHAArchiveEntry.m \ OFList.m \ OFLocale.m \ OFMD5Hash.m \ OFMapTable.m \ + OFMemoryStream.m \ OFMessagePackExtension.m \ OFMethodSignature.m \ OFMutableArray.m \ OFMutableData.m \ OFMutableDictionary.m \ ADDED src/OFMemoryStream.h Index: src/OFMemoryStream.h ================================================================== --- src/OFMemoryStream.h +++ src/OFMemoryStream.h @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2008-2022 Jonathan Schleifer + * + * All rights reserved. + * + * This file is part of ObjFW. It may be distributed under the terms of the + * Q Public License 1.0, which can be found in the file LICENSE.QPL included in + * the packaging of this file. + * + * Alternatively, it may be distributed under the terms of the GNU General + * Public License, either version 2 or 3, which can be found in the file + * LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this + * file. + */ + +#import "OFSeekableStream.h" + +OF_ASSUME_NONNULL_BEGIN + +/** + * @class OFMemoryStream OFMemoryStream.h ObjFW/OFMemoryStream.h + * + * @brief A seekable stream for reading from and writing to memory. + */ +OF_SUBCLASSING_RESTRICTED +@interface OFMemoryStream: OFSeekableStream +{ + char *_address; + size_t _size, _position; + bool _writable; +} + +/** + * @brief Creates a new OFMemoryStream with the specified memory. + * + * @warning The memory is not copied, so it is your responsibility that the + * specified memory stays alive for as long as the OFMemoryStream does! + * + * @param address The memory address for the stream + * @param size The size of the memory at the specified address + * @param writable Whether writes to memory should be allowed + * @return A new autoreleased OFMemoryStream + */ ++ (instancetype)streamWithMemoryAddress: (void *)address + size: (size_t)size + writable: (bool)writable; + +- (instancetype)init OF_UNAVAILABLE; + +/** + * @brief Initializes an already allocated OFMemoryStream with the specified + * memory. + * + * @warning The memory is not copied, so it is your responsibility that the + * specified memory stays alive for as long as the OFMemoryStream does! + * + * @param address The memory address for the stream + * @param size The size of the memory at the specified address + * @param writable Whether writes to memory should be allowed + * @return An initialized OFMemoryStream + */ +- (instancetype)initWithMemoryAddress: (void *)address + size: (size_t)size + writable: (bool)writable; +@end + +OF_ASSUME_NONNULL_END ADDED src/OFMemoryStream.m Index: src/OFMemoryStream.m ================================================================== --- src/OFMemoryStream.m +++ src/OFMemoryStream.m @@ -0,0 +1,130 @@ +/* + * Copyright (c) 2008-2022 Jonathan Schleifer + * + * All rights reserved. + * + * This file is part of ObjFW. It may be distributed under the terms of the + * Q Public License 1.0, which can be found in the file LICENSE.QPL included in + * the packaging of this file. + * + * Alternatively, it may be distributed under the terms of the GNU General + * Public License, either version 2 or 3, which can be found in the file + * LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this + * file. + */ + +#include "config.h" + +#include + +#import "OFMemoryStream.h" + +#import "OFInvalidArgumentException.h" +#import "OFOutOfRangeException.h" +#import "OFWriteFailedException.h" +#import "OFSeekFailedException.h" + +@implementation OFMemoryStream ++ (instancetype)streamWithMemoryAddress: (void *)address + size: (size_t)size + writable: (bool)writable +{ + return [[[self alloc] initWithMemoryAddress: address + size: size + writable: writable] autorelease]; +} + +- (instancetype)init OF_UNAVAILABLE +{ + OF_INVALID_INIT_METHOD +} + +- (instancetype)initWithMemoryAddress: (void *)address + size: (size_t)size + writable: (bool)writable +{ + self = [super init]; + + @try { + if (size > SSIZE_MAX || (ssize_t)size != (OFFileOffset)size) + @throw [OFOutOfRangeException exception]; + + _address = address; + _size = size; + _writable = writable; + } @catch (id e) { + [self release]; + @throw e; + } + + return self; +} + +- (size_t)lowlevelReadIntoBuffer: (void *)buffer length: (size_t)length +{ + if (SIZE_MAX - _position < length || _position + length > _size) + length = _size - _position; + + memcpy(buffer, _address + _position, length); + _position += length; + + return length; +} + +- (size_t)lowlevelWriteBuffer: (const void *)buffer length: (size_t)length +{ + size_t bytesWritten = length; + + if (!_writable) + @throw [OFWriteFailedException exceptionWithObject: self + requestedLength: length + bytesWritten: 0 + errNo: EBADF]; + + if (SIZE_MAX - _position < length || _position + length > _size) + bytesWritten = _size - _position; + + memcpy(_address + _position, buffer, bytesWritten); + _position += bytesWritten; + + if (bytesWritten != length) + @throw [OFWriteFailedException exceptionWithObject: self + requestedLength: length + bytesWritten: bytesWritten + errNo: EFBIG]; + + return bytesWritten; +} + +- (bool)lowlevelIsAtEndOfStream +{ + return (_position == _size); +} + +- (OFFileOffset)lowlevelSeekToOffset: (OFFileOffset)offset whence: (int)whence +{ + OFFileOffset new; + + switch (whence) { + case SEEK_SET: + new = offset; + break; + case SEEK_CUR: + new = (OFFileOffset)_position + offset; + break; + case SEEK_END: + new = (OFFileOffset)_size + offset; + break; + default: + @throw [OFInvalidArgumentException exception]; + } + + if (new < 0 || new > (OFFileOffset)_size) + @throw [OFSeekFailedException exceptionWithStream: self + offset: offset + whence: whence + errNo: EINVAL]; + + return (_position = (size_t)new); +} +@end Index: src/ObjFW.h ================================================================== --- src/ObjFW.h +++ src/ObjFW.h @@ -52,10 +52,12 @@ #import "OFNotification.h" #import "OFNotificationCenter.h" #import "OFStream.h" +#import "OFSeekableStream.h" +#import "OFMemoryStream.h" #import "OFStdIOStream.h" #import "OFInflateStream.h" #import "OFInflate64Stream.h" #import "OFGZIPStream.h" #import "OFLHAArchive.h" Index: tests/Makefile ================================================================== --- tests/Makefile +++ tests/Makefile @@ -23,10 +23,11 @@ OFInvocationTests.m \ OFJSONTests.m \ OFListTests.m \ OFLocaleTests.m \ OFMethodSignatureTests.m \ + OFMemoryStreamTests.m \ OFNotificationCenterTests.m \ OFNumberTests.m \ OFObjectTests.m \ OFPBKDF2Tests.m \ OFPropertyListTests.m \ ADDED tests/OFMemoryStreamTests.m Index: tests/OFMemoryStreamTests.m ================================================================== --- tests/OFMemoryStreamTests.m +++ tests/OFMemoryStreamTests.m @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2008-2022 Jonathan Schleifer + * + * All rights reserved. + * + * This file is part of ObjFW. It may be distributed under the terms of the + * Q Public License 1.0, which can be found in the file LICENSE.QPL included in + * the packaging of this file. + * + * Alternatively, it may be distributed under the terms of the GNU General + * Public License, either version 2 or 3, which can be found in the file + * LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this + * file. + */ + +#include "config.h" + +#import "TestsAppDelegate.h" + +static OFString *const module = @"OFMemoryStream"; +static const char string[] = "abcdefghijkl"; + +@implementation TestsAppDelegate (OFMemoryStreamTests) +- (void)memoryStreamTests +{ + void *pool = objc_autoreleasePoolPush(); + OFMemoryStream *stream; + char buffer[10]; + OFMutableData *data; + + TEST(@"+[streamWithMemoryAddress:size:writable:]", + (stream = [OFMemoryStream streamWithMemoryAddress: (char *)string + size: sizeof(string) + writable: false])); + + /* + * Test the lowlevel methods, as otherwise OFStream will do one big + * read and we will not test OFMemoryStream. + */ + + TEST(@"-[lowlevelReadIntoBuffer:length:]", + [stream lowlevelReadIntoBuffer: buffer length: 5] == 5 && + memcmp(buffer, "abcde", 5) == 0 && + [stream lowlevelReadIntoBuffer: buffer length: 3] == 3 && + memcmp(buffer, "fgh", 3) == 0 && + [stream lowlevelReadIntoBuffer: buffer length: 10] == 5 && + memcmp(buffer, "ijkl", 5) == 0) + + TEST(@"-[lowlevelIsAtEndOfStream]", [stream lowlevelIsAtEndOfStream]) + + TEST(@"-[lowlevelSeekToOffset:whence:]", + [stream lowlevelSeekToOffset: 0 whence: SEEK_CUR] == + sizeof(string) && [stream lowlevelIsAtEndOfStream] && + [stream lowlevelSeekToOffset: 4 whence: SEEK_SET] == 4 && + ![stream lowlevelIsAtEndOfStream] && + [stream lowlevelReadIntoBuffer: buffer length: 10] == 9 && + memcmp(buffer, "efghijkl", 9) == 0 && + [stream lowlevelSeekToOffset: -2 whence: SEEK_END] == 11 && + [stream lowlevelReadIntoBuffer: buffer length: 10] == 2 && + memcmp(buffer, "l", 2) == 0 && + [stream lowlevelReadIntoBuffer: buffer length: 10] == 0) + + EXPECT_EXCEPTION(@"Writes rejected on read-only stream", + OFWriteFailedException, [stream lowlevelWriteBuffer: "" length: 1]) + + data = [OFMutableData dataWithCapacity: 13]; + [data increaseCountBy: 13]; + stream = [OFMemoryStream streamWithMemoryAddress: data.mutableItems + size: data.count + writable: true]; + TEST(@"-[lowlevelWriteBuffer:length:]", + [stream lowlevelWriteBuffer: "abcde" length: 5] == 5 && + [stream lowlevelWriteBuffer: "fgh" length: 3] == 3 && + [stream lowlevelWriteBuffer: "ijkl" length: 5] == 5 && + memcmp(data.items, string, data.count) == 0 && + [stream lowlevelSeekToOffset: -3 whence: SEEK_END] == 10) + + EXPECT_EXCEPTION(@"Out of bound writes rejected", + OFWriteFailedException, + [stream lowlevelWriteBuffer: "xyz" length: 4]) + + TEST(@"Partial write for too long write", + memcmp(data.items, "abcdefghijxyz", 13) == 0) + + objc_autoreleasePoolPop(pool); +} +@end Index: tests/TestsAppDelegate.h ================================================================== --- tests/TestsAppDelegate.h +++ tests/TestsAppDelegate.h @@ -116,10 +116,14 @@ @end @interface TestsAppDelegate (OFJSONTests) - (void)JSONTests; @end + +@interface TestsAppDelegate (OFHMACTests) +- (void)HMACTests; +@end @interface TestsAppDelegate (OFKernelEventObserverTests) - (void)kernelEventObserverTests; @end @@ -132,10 +136,14 @@ @end @interface TestsAppDelegate (OFMD5HashTests) - (void)MD5HashTests; @end + +@interface TestsAppDelegate (OFMemoryStreamTests) +- (void)memoryStreamTests; +@end @interface TestsAppDelegate (OFMethodSignatureTests) - (void)methodSignatureTests; @end @@ -217,14 +225,10 @@ @interface TestsAppDelegate (OFSystemInfoTests) - (void)systemInfoTests; @end -@interface TestsAppDelegate (OFHMACTests) -- (void)HMACTests; -@end - @interface TestsAppDelegate (OFSocketTests) - (void)socketTests; @end @interface TestsAppDelegate (OFStreamTests) Index: tests/TestsAppDelegate.m ================================================================== --- tests/TestsAppDelegate.m +++ tests/TestsAppDelegate.m @@ -387,10 +387,11 @@ [self setTests]; [self dateTests]; [self valueTests]; [self numberTests]; [self streamTests]; + [self memoryStreamTests]; [self notificationCenterTests]; #ifdef OF_HAVE_FILES [self MD5HashTests]; [self RIPEMD160HashTests]; [self SHA1HashTests];