Skip to content
Draft
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
10 changes: 5 additions & 5 deletions Source/ARTRealtimeAnnotations.m
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,6 @@ @implementation ARTRealtimeAnnotationsInternal {
__weak ARTRealtimeInternal *_realtime;
dispatch_queue_t _userQueue;
ARTEventEmitter<ARTEvent *, ARTAnnotation *> *_eventEmitter;
ARTDataEncoder *_dataEncoder;
}

- (instancetype)initWithChannel:(ARTRealtimeChannelInternal *)channel logger:(ARTInternalLog *)logger {
Expand All @@ -109,7 +108,6 @@ - (instancetype)initWithChannel:(ARTRealtimeChannelInternal *)channel logger:(AR
_queue = _realtime.rest.queue;
_logger = logger;
_eventEmitter = [[ARTInternalEventEmitter alloc] initWithQueue:_queue timeProvider:_realtime.rest.timeProvider];
_dataEncoder = _channel.dataEncoder;
}
return self;
}
Expand Down Expand Up @@ -150,7 +148,8 @@ - (void)publishAnnotation:(ARTOutboundAnnotation *)outboundAnnotation
extras:outboundAnnotation.extras];
art_dispatch_sync(_queue, ^{
NSError *error = nil;
ARTAnnotation *annotationToPublish = _dataEncoder ? [annotation encodeDataWithEncoder:_dataEncoder error:&error] : annotation; // RSAN1c3
ARTDataEncoder *dataEncoder = self->_channel.dataEncoder;
ARTAnnotation *annotationToPublish = dataEncoder ? [annotation encodeDataWithEncoder:dataEncoder error:&error] : annotation; // RSAN1c3
if (error) {
if (callback) {
callback([ARTErrorInfo createFromNSError:error]);
Expand Down Expand Up @@ -301,11 +300,12 @@ - (void)unsubscribe:(NSString *)type listener:(ARTEventListener *)listener {
}

- (void)onMessage:(ARTProtocolMessage *)message {
ARTDataEncoder *dataEncoder = _channel.dataEncoder;
for (ARTAnnotation *a in message.annotations) {
ARTAnnotation *annotation = a;
if (annotation.data && _dataEncoder) {
if (annotation.data && dataEncoder) {
NSError *decodeError = nil;
annotation = [a decodeDataWithEncoder:_dataEncoder error:&decodeError];
annotation = [a decodeDataWithEncoder:dataEncoder error:&decodeError];
if (decodeError != nil) {
ARTErrorInfo *errorInfo = [ARTErrorInfo wrap:[ARTErrorInfo createWithCode:ARTErrorUnableToDecodeMessage message:decodeError.localizedFailureReason] prepend:@"Failed to decode data: "];
ARTLogError(self.logger, @"RT:%p C:%p (%@) %@", _realtime, _channel, _channel.name, errorInfo.message);
Expand Down
5 changes: 2 additions & 3 deletions Source/ARTRestAnnotations.m
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,6 @@ @implementation ARTRestAnnotationsInternal {
__weak ARTRestChannelInternal *_channel; // weak because channel owns self
dispatch_queue_t _userQueue;
dispatch_queue_t _queue;
ARTDataEncoder *_dataEncoder;
}

- (instancetype)initWithChannel:(ARTRestChannelInternal *)channel logger:(ARTInternalLog *)logger {
Expand All @@ -109,7 +108,6 @@ - (instancetype)initWithChannel:(ARTRestChannelInternal *)channel logger:(ARTInt
_userQueue = channel.rest.userQueue;
_queue = channel.rest.queue;
_logger = logger;
_dataEncoder = _channel.dataEncoder;
}
return self;
}
Expand Down Expand Up @@ -158,7 +156,8 @@ - (void)publishAnnotation:(ARTOutboundAnnotation *)outboundAnnotation
art_dispatch_async(_queue, ^{
// RSAN1c3: encode annotation data
NSError *encodeError = nil;
ARTAnnotation *annotationToPublish = self->_dataEncoder ? [annotation encodeDataWithEncoder:self->_dataEncoder error:&encodeError] : annotation;
ARTDataEncoder *dataEncoder = self->_channel.dataEncoder;
ARTAnnotation *annotationToPublish = dataEncoder ? [annotation encodeDataWithEncoder:dataEncoder error:&encodeError] : annotation;
if (encodeError) {
if (callback) {
callback([ARTErrorInfo createFromNSError:encodeError]);
Expand Down
71 changes: 71 additions & 0 deletions Test/AblyTests/Tests/RealtimeAnnotationsTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -379,4 +379,75 @@ class RealtimeAnnotationsTests: XCTestCase {
}
}
}

// The annotations object must read the channel's data encoder at encode/decode time
// rather than capturing it at construction, so that a cipher added by setOptions
// after the channel was created is still applied to annotation data.
func test__annotation_data_uses_a_cipher_added_by_setOptions_after_channel_creation() throws {
let test = Test()
let options = try AblyTests.commonAppSetup(for: test)
options.testOptions.channelNamePrefix = nil

let realtimeClient = ARTRealtime(options: options)
defer { realtimeClient.dispose(); realtimeClient.close() }

let channelName = test.uniqueChannelName(prefix: "mutable:")

// Create the channel with no cipher, so the annotations object would capture a
// cipher-less encoder if it cached one.
let initialOptions = ARTRealtimeChannelOptions()
initialOptions.modes = [.publish, .subscribe, .annotationPublish, .annotationSubscribe]
let channel = realtimeClient.channels.get(channelName, options: initialOptions)

// Now add a cipher.
let key = ARTCrypto.generateRandomKey()
let encryptedOptions = ARTRealtimeChannelOptions(cipherKey: key as ARTCipherKeyCompatible)
encryptedOptions.modes = [.publish, .subscribe, .annotationPublish, .annotationSubscribe]
waitUntil(timeout: testTimeout) { done in
channel.setOptions(encryptedOptions) { error in
XCTAssertNil(error)
done()
}
}

let annotationData = "secret annotation data"
var receivedAnnotation: ARTAnnotation!

waitUntil(timeout: testTimeout) { done in
channel.subscribe { message in
guard message.action == .create else {
return
}
// multiple.v1 because an anonymous client may only publish the
// multiple.v1 and total.v1 aggregation methods
let annotation = ARTOutboundAnnotation(
id: nil,
type: "reaction:multiple.v1",
clientId: nil,
name: "👍",
count: 1,
data: annotationData,
extras: nil
)
channel.annotations.publish(for: message, annotation: annotation) { error in
XCTAssertNil(error)
}
}

channel.annotations.subscribe { annotation in
receivedAnnotation = annotation
done()
}

channel.publish([ARTMessage(name: "test", data: "test message")])
}

// Decrypted with the cipher set by setOptions; if the stale encoder were used the
// data would have been published as plaintext and arrive as ciphertext.
guard let receivedAnnotation else {
XCTFail("No annotation received")
return
}
XCTAssertEqual(receivedAnnotation.data as? String, annotationData)
}
}
Loading