PubSub+ Messaging API For C  7.31.0.7
 All Data Structures Files Functions Variables Typedefs Enumerations Enumerator Macros Groups Pages
ex/ios/Example.m
/*
* Copyright 2014-2024 Solace Corporation. All rights reserved.
*/
#import "Example.h"
@implementation Example
@synthesize name;
@synthesize description;
@synthesize parameters;
@synthesize nullBridge_p;
- (id)initWithExampleInterface:(ExampleInterface *)exampleInterface {
self = [super init];
self.parameters = [[ParameterInterface alloc] init];
self.exampleInterface = exampleInterface;
self.nullBridge_p = malloc(sizeof(callbackBridge_t));
self.nullBridge_p->example_p = (__bridge void *)self;
self.nullBridge_p->user_p = NULL;
// Set up flags
self.running = NO;
self.requestRun = NO;
[self addObserver:self forKeyPath:@"requestRun" options:0 context:NULL];
self.requestCancel = NO;
[self addObserver:self forKeyPath:@"requestCancel" options:0 context:NULL];
return self;
}
- (void)start {
// Inform the UI that the example is now running
self.running = YES;
// Inform the example interface that this example is currently running
self.exampleInterface.runningExample = self;
// Run the example in a separate thread, to allow the UI to update
// immediately after receiving a log callback
[self performSelectorInBackground:@selector(run) withObject:nil];
}
- (void)run {
if ((rc = solClient_log_setCallback(logCallback, nullBridge_p) !=
[self handleErrorWithReturnCode:rc
errorString:"solClient_log_setCallback()"];
}
}
- (void)cancel {
// Inform the example that cancellation is requested
self.requestCancel = YES;
// Force a cleanup, to ensure that example stops more or less immediately
}
- (void)cleanup {
[self printToTextView:@"Example complete."];
// Inform the UI that the example has been cancelled
self.requestCancel = NO;
self.running = NO;
// Inform the example interface that no example is currently running
self.exampleInterface.runningExample = nil;
}
- (void)printToTextView:(NSString *)str {
// Send a notification to the UI to print to text view
[[NSNotificationCenter defaultCenter]
postNotificationName:@"output"
object:self
userInfo:[NSDictionary
dictionaryWithObjectsAndKeys:str, @"string",
nil]];
}
- (void)logCCSMPVersion {
if (solClient_version_get(&version_p) != SOLCLIENT_OK) {
"Unknown library version, solClient_version_get returns FAIL\n");
} else {
solClient_log(SOLCLIENT_LOG_NOTICE, "CCSMP Version %s (%s)",
version_p->version_p, version_p->dateTime_p);
solClient_log(SOLCLIENT_LOG_NOTICE, "CCSMP Variant: %s\n",
version_p->variant_p);
}
}
- (void)setLoggingLevel {
// Standard logging levels can be set independently for the API and the
// application. In this case, the ALL category is used to set the log level
// for both at the same time.
if ([[self.parameters parameterWithId:PARAMETER_LOGGING_LEVEL].value
isEqualToString:@"critical"]) {
return;
} else if ([[self.parameters parameterWithId:PARAMETER_LOGGING_LEVEL].value
isEqualToString:@"error"]) {
return;
} else if ([[self.parameters parameterWithId:PARAMETER_LOGGING_LEVEL].value
isEqualToString:@"warning"]) {
return;
} else if ([[self.parameters parameterWithId:PARAMETER_LOGGING_LEVEL].value
isEqualToString:@"notice"]) {
return;
} else if ([[self.parameters parameterWithId:PARAMETER_LOGGING_LEVEL].value
isEqualToString:@"info"]) {
return;
} else if ([[self.parameters parameterWithId:PARAMETER_LOGGING_LEVEL].value
isEqualToString:@"debug"]) {
return;
}
}
- (void)handleErrorWithReturnCode:(solClient_returnCode_t)rc
errorString:(const char *)errorStr {
"%s - ReturnCode=\"%s\", SubCode=\"%s\", ResponseCode=%d, Info=\"%s\"",
errorInfo->errorStr);
}
createAndConnectSessionWithContext:(solClient_opaqueContext_pt)context_p
session:(solClient_opaqueSession_pt *)session_p
messageCallback:
eventCallback_p {
return [self createAndConnectSessionWithContext:context_p
session:session_p
messageCallback:msgCallback_p
eventCallback:eventCallback_p
userData:self.nullBridge_p];
}
createAndConnectSessionWithContext:(solClient_opaqueContext_pt)context_p
session:(solClient_opaqueSession_pt *)session_p
messageCallback:
eventCallback_p
userData:(callbackBridge_t *)user_p {
// Return code
// Session Function Info
// Session properties
const char *sessionProps[50];
int propIndex = 0;
sessionFuncInfo.rxMsgInfo.callback_p = msgCallback_p;
sessionFuncInfo.rxMsgInfo.user_p = user_p;
sessionFuncInfo.eventInfo.callback_p = eventCallback_p;
sessionFuncInfo.eventInfo.user_p = user_p;
propIndex = 0;
sessionProps[propIndex++] = SOLCLIENT_SESSION_PROP_HOST;
sessionProps[propIndex++] =
[[self.parameters parameterWithId:PARAMETER_HOST].value
cStringUsingEncoding:NSASCIIStringEncoding];
sessionProps[propIndex++] = SOLCLIENT_SESSION_PROP_COMPRESSION_LEVEL;
sessionProps[propIndex++] =
[self.parameters parameterWithId:PARAMETER_COMPRESSION].value.boolValue
? "9"
: "0";
sessionProps[propIndex++] = SOLCLIENT_SESSION_PROP_CONNECT_RETRIES;
sessionProps[propIndex++] = "3";
sessionProps[propIndex++] = SOLCLIENT_SESSION_PROP_RECONNECT_RETRIES;
sessionProps[propIndex++] = "3";
// Note: Reapplying subscriptions allows Sessions to reconnect after failure
// and have all their subscriptions automatically restored. For Sessions
// with many subscriptions, this can increase the amount of time required
// for a successful reconnect.
sessionProps[propIndex++] = SOLCLIENT_SESSION_PROP_REAPPLY_SUBSCRIPTIONS;
sessionProps[propIndex++] = SOLCLIENT_PROP_ENABLE_VAL;
// Note: Including meta data fields such as sender timestamp, sender ID, and
// sequence number can reduce the maximum attainable throughput as
// significant extra encoding/decoding is required. This is true whether the
// fields are autogenerated or manually added.
sessionProps[propIndex++] = SOLCLIENT_PROP_ENABLE_VAL;
sessionProps[propIndex++] = SOLCLIENT_SESSION_PROP_GENERATE_SENDER_ID;
sessionProps[propIndex++] = SOLCLIENT_PROP_ENABLE_VAL;
sessionProps[propIndex++] = SOLCLIENT_PROP_ENABLE_VAL;
if (![[self.parameters parameterWithId:PARAMETER_HOST].value
isEqualToString:@""]) {
sessionProps[propIndex++] = SOLCLIENT_SESSION_PROP_VPN_NAME;
sessionProps[propIndex++] =
[[self.parameters parameterWithId:PARAMETER_VPN].value
cStringUsingEncoding:NSASCIIStringEncoding];
}
sessionProps[propIndex++] = SOLCLIENT_PROP_DISABLE_VAL;
sessionProps[propIndex++] = SOLCLIENT_SESSION_PROP_USERNAME;
sessionProps[propIndex++] =
[[self.parameters parameterWithId:PARAMETER_USERNAME].value
cStringUsingEncoding:NSASCIIStringEncoding];
sessionProps[propIndex++] = SOLCLIENT_SESSION_PROP_PASSWORD;
sessionProps[propIndex++] =
[[self.parameters parameterWithId:PARAMETER_PASSWORD].value
cStringUsingEncoding:NSASCIIStringEncoding];
sessionProps[propIndex] = NULL;
(char **)sessionProps, context_p, session_p, &sessionFuncInfo,
sizeof(sessionFuncInfo))) != SOLCLIENT_OK) {
[self handleErrorWithReturnCode:rc
errorString:"solClient_session_create()"];
return rc;
}
if ((rc = solClient_session_connect(*session_p)) != SOLCLIENT_OK) {
[self handleErrorWithReturnCode:rc
errorString:"solClient_session_connect()"];
return rc;
}
return SOLCLIENT_OK;
}
publishMessageWithSession:(solClient_opaqueSession_pt)session_p
topic:(char *)topic_p
deliveryMode:(solClient_uint32_t)deliveryMode {
// Return code
solClient_opaqueMsg_pt msg_p = NULL;
"publishMessageWithSession:topic:deliveryMode called.\n");
// Allocate memory for the message to be sent.
if ((rc = solClient_msg_alloc(&msg_p)) != SOLCLIENT_OK) {
[self handleErrorWithReturnCode:rc errorString:"solClient_msg_alloc()"];
return rc;
}
// Set the message delivery mode
if ((rc = solClient_msg_setDeliveryMode(msg_p, deliveryMode)) !=
[self handleErrorWithReturnCode:rc
errorString:"solClient_msg_setDeliveryMode()"];
goto freeMessage;
}
// Set the destination
destination.dest = topic_p;
msg_p, &destination, sizeof(destination))) != SOLCLIENT_OK) {
[self handleErrorWithReturnCode:rc
errorString:"solClient_msg_setDestination()"];
goto freeMessage;
}
// Send the message
if ((rc = solClient_session_sendMsg(session_p, msg_p)) != SOLCLIENT_OK) {
[self handleErrorWithReturnCode:rc
errorString:"solClient_session_sendMsg()"];
goto freeMessage;
}
freeMessage:
if ((rcFreeMsg = solClient_msg_free(&msg_p)) != SOLCLIENT_OK) {
[self handleErrorWithReturnCode:rcFreeMsg
errorString:"solClient_msg_free()"];
}
return rc;
}
- (pthread_t)startThreadWithFP:(FP)fp argument:(void *)arg {
pthread_t th = _NULL_THREAD_ID;
if (pthread_create(&th, NULL, fp, arg)) {
}
if (th == _NULL_THREAD_ID) {
return _NULL_THREAD_ID;
} else {
return th;
}
}
- (void)waitOnThread:(pthread_t)handle {
void *value_p;
pthread_join(handle, &value_p);
}
threadRetType contextThread(void *user_p) {
// The use of autoreleasepools in CCSMP callbacks is necessary to avoid
// memory leaks, to ensure that ARC works properly when in C scope (i.e.
// outside of Objective-C scope)
@autoreleasepool {
callbackBridge_t *bridge = (callbackBridge_t *)user_p;
Example *example = (__bridge Example *)bridge->example_p;
contextThreadInfo_t *contextInfo_p =
(contextThreadInfo_t *)bridge->user_p;
free(bridge);
return [example contextThread:contextInfo_p];
}
}
- (threadRetType)contextThread:(void *)contextInfo_p {
contextThreadInfo_t *info_p = (contextThreadInfo_t *)contextInfo_p;
solClient_log(SOLCLIENT_LOG_DEBUG, "Context thread initialized");
info_p->rc = 0;
// Loop until common_stopContextThread has been set to 1.
while (!info_p->stopContextThread) {
if ((rc = solClient_context_processEvents(info_p->context_p)) !=
[self handleErrorWithReturnCode:rc
errorString:"solClient_context_processEvents"];
break;
}
}
return NULL;
}
- (int)startContextThread:(contextThreadInfo_t *)info_p {
int rc = 1;
solClient_log(SOLCLIENT_LOG_DEBUG, "Starting Context thread");
info_p->stopContextThread = 0;
callbackBridge_t *bridge = malloc(sizeof(callbackBridge_t));
bridge->user_p = info_p;
bridge->example_p = (__bridge void *)self;
// Create the thread.
if (pthread_create(&(info_p->handle), NULL, contextThread, (void *)bridge) <
0) {
solClient_log(SOLCLIENT_LOG_ERROR, "Could not create context thread");
rc = 0;
} else {
info_p->contextThreadStarted = 1;
}
return rc;
}
- (void)stopContextThread:(contextThreadInfo_t *)info_p {
solClient_log(SOLCLIENT_LOG_DEBUG, "Stopping Context thread");
info_p->stopContextThread = 1;
void *value_p;
pthread_join(info_p->handle, &value_p);
}
- (UInt64)getTimeInUs {
struct timespec tv;
// timespec uses long; mach timespec uses unsigned int
mach_timespec_t mtv;
clock_serv_t cclock;
host_get_clock_service(mach_host_self(), CALENDAR_CLOCK, &cclock);
clock_get_time(cclock, &mtv);
mach_port_deallocate(mach_task_self(), cclock);
tv.tv_sec = mtv.tv_sec;
tv.tv_nsec = mtv.tv_nsec;
return ((UInt64)tv.tv_sec * (UInt64)1000000) +
((UInt64)tv.tv_nsec / (UInt64)1000);
}
- (void)getUsageTimeWithUserTime:(long long *)userTime_p
systemTime:(long long *)systemTime_p {
struct rusage usage;
getrusage(RUSAGE_SELF, &usage);
*userTime_p = (long long)usage.ru_utime.tv_sec * (long long)1000000 +
(long long)usage.ru_utime.tv_usec;
*systemTime_p = (long long)usage.ru_stime.tv_sec * (long long)1000000 +
(long long)usage.ru_stime.tv_usec;
}
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context {
// Run an example when requested by the UI
if ([keyPath isEqualToString:@"requestRun"]) {
if (self.requestRun) {
self.requestRun = NO;
[self start];
}
}
// Ignore cancellation requests when the example is not running
else if ([keyPath isEqualToString:@"requestCancel"]) {
if (self.requestCancel && !self.running) {
self.requestCancel = NO;
}
}
}
void eventCallback(solClient_opaqueSession_pt opaqueSession_p,
void *user_p) {
// The use of autoreleasepools in CCSMP callbacks is necessary to avoid
// memory leaks, to ensure that ARC works properly when in C scope (i.e.
// outside of Objective-C scope)
@autoreleasepool {
callbackBridge_t *bridge = (callbackBridge_t *)user_p;
id<EventCallbackProtocol> callbackProtocol =
(__bridge id<EventCallbackProtocol>)bridge->example_p;
[callbackProtocol eventCallbackWithSession:opaqueSession_p
eventInfo:eventInfo_p
userData:bridge->user_p];
}
}
- (void)eventCallbackWithSession:(solClient_opaqueSession_pt)opaqueSession_p
eventInfo:
userData:(void *)user_p {
switch (eventInfo_p->sessionEvent) {
// Non-error events are logged at the INFO level
"eventCallbackWithSession:eventInfo: called - %s\n",
break;
// Extra error information is available on error events
errorInfo_p = solClient_getLastErrorInfo();
// Error events are logged at the ERROR level
SOLCLIENT_LOG_ERROR, "eventCallbackWithSession:eventInfo: called - "
"%s; subCode %s, responseCode %d, "
"reason %s\n",
errorInfo_p->responseCode, errorInfo_p->errorStr);
break;
default:
// Unrecognized or deprecated events are logged at the NOTICE level.
SOLCLIENT_LOG_NOTICE, "eventCallbackWithSession:eventInfo: called "
"- %s. Unrecognized or deprecated "
"event.\n",
break;
}
}
void cacheEventCallback(solClient_opaqueSession_pt opaqueSession_p,
void *user_p) {
// The use of autoreleasepools in CCSMP callbacks is necessary to avoid
// memory leaks, to ensure that ARC works properly when in C scope (i.e.
// outside of Objective-C scope)
@autoreleasepool {
callbackBridge_t *bridge = (callbackBridge_t *)user_p;
id<EventCallbackProtocol> callbackProtocol =
(__bridge id<EventCallbackProtocol>)bridge->example_p;
[callbackProtocol cacheEventCallbackWithSession:opaqueSession_p
eventInfo:eventInfo_p
userData:bridge->user_p];
}
}
- (void)cacheEventCallbackWithSession:
(solClient_opaqueSession_pt)opaqueSession_p
eventInfo:(solCache_eventCallbackInfo_pt)eventInfo_p
userData:(void *)user_p {
"cacheEventCallbackWithSession:eventInfo called - %s\n"
"topic: %s\n"
"responseCode: (%d) %s\n"
"subCode: (%d) %s\n"
"cacheRequestId: %llu\n\n",
eventInfo_p->topic, eventInfo_p->rc,
eventInfo_p->subCode,
eventInfo_p->cacheRequestId);
}
void eventPerfCallback(solClient_opaqueSession_pt opaqueSession_p,
void *user_p) {
// The use of autoreleasepools in CCSMP callbacks is necessary to avoid
// memory leaks, to ensure that ARC works properly when in C scope (i.e.
// outside of Objective-C scope)
@autoreleasepool {
callbackBridge_t *bridge = (callbackBridge_t *)user_p;
id<EventCallbackProtocol> callbackProtocol =
(__bridge id<EventCallbackProtocol>)bridge->example_p;
[callbackProtocol eventPerfCallbackWithSession:opaqueSession_p
eventInfo:eventInfo_p
userData:bridge->user_p];
}
}
- (void)eventPerfCallbackWithSession:(solClient_opaqueSession_pt)opaqueSession_p
eventInfo_p
userData:(void *)user_p {
}
void logCallback(solClient_log_callbackInfo_pt logInfo_p, void *user_p) {
// The use of autoreleasepools in CCSMP callbacks is necessary to avoid
// memory leaks, to ensure that ARC works properly when in C scope (i.e.
// outside of Objective-C scope)
@autoreleasepool {
callbackBridge_t *bridge = (callbackBridge_t *)user_p;
id<LogCallbackProtocol> callbackProtocol =
(__bridge id<LogCallbackProtocol>)bridge->example_p;
[callbackProtocol logCallbackWithLogInfo:logInfo_p
userData:bridge->user_p];
}
}
- (void)logCallbackWithLogInfo:(solClient_log_callbackInfo_pt)logInfo_p
userData:(void *)user_p {
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
[formatter setDateFormat:@"hh:mm:ss"];
NSString *str = [NSString
stringWithFormat:@"%s %s %@ : %s",
solClient_log_categoryToString(logInfo_p->category),
solClient_log_levelToString(logInfo_p->level),
[formatter stringFromDate:[NSDate date]],
logInfo_p->msg_p];
[self printToTextView:str];
}
messageReceiveCallback(solClient_opaqueSession_pt opaqueSession_p,
solClient_opaqueMsg_pt msg_p, void *user_p) {
// The use of autoreleasepools in CCSMP callbacks is necessary to avoid
// memory leaks, to ensure that ARC works properly when in C scope (i.e.
// outside of Objective-C scope)
@autoreleasepool {
callbackBridge_t *bridge = (callbackBridge_t *)user_p;
id<MessageReceiveCallbackProtocol> callbackProtocol =
(__bridge id<MessageReceiveCallbackProtocol>)bridge->example_p;
return
[callbackProtocol messageReceiveCallbackWithSession:opaqueSession_p
message:msg_p
userData:bridge->user_p];
}
}
messageReceiveCallbackWithSession:
(solClient_opaqueSession_pt)opaqueSession_p
message:(solClient_opaqueMsg_pt)msg_p
userData:(void *)user_p {
const char *senderId_p;
/*
* Get the message sequence number and sender ID. Check to see if the
* fields exist, and use a default value if the field is not found.
*/
if ((rc = solClient_msg_getSequenceNumber(msg_p, &rxSeqNum)) !=
if (rc == SOLCLIENT_NOT_FOUND) {
rxSeqNum = 0;
} else {
[self
handleErrorWithReturnCode:rc
errorString:"solClient_msg_getSequenceNumber()"];
}
}
if ((rc = solClient_msg_getSenderId(msg_p, &senderId_p)) != SOLCLIENT_OK) {
if (rc == SOLCLIENT_NOT_FOUND) {
senderId_p = "";
} else {
[self handleErrorWithReturnCode:rc
errorString:"solClient_msg_getSenderId()"];
}
}
if (user_p != NULL) {
"%s received message from '%s' (seq# %llu)\n",
(char *)user_p, senderId_p, rxSeqNum);
} else {
"Received message from '%s' (seq# %llu)\n", senderId_p,
rxSeqNum);
}
/*
* Returning SOLCLIENT_CALLBACK_OK causes the API to free the memory
* used by the message. This is important to avoid leaks.
*/
}
messageReceivePrintMsgCallback(solClient_opaqueSession_pt opaqueSession_p,
solClient_opaqueMsg_pt msg_p, void *user_p) {
// The use of autoreleasepools in CCSMP callbacks is necessary to avoid
// memory leaks, to ensure that ARC works properly when in C scope (i.e.
// outside of Objective-C scope)
@autoreleasepool {
callbackBridge_t *bridge = (callbackBridge_t *)user_p;
id<MessageReceiveCallbackProtocol> callbackProtocol =
(__bridge id<MessageReceiveCallbackProtocol>)bridge->example_p;
return [callbackProtocol
messageReceivePrintMsgCallbackWithSession:opaqueSession_p
message:msg_p
userData:bridge->user_p];
}
}
messageReceivePrintMsgCallbackWithSession:
(solClient_opaqueSession_pt)opaqueSession_p
message:(solClient_opaqueMsg_pt)msg_p
userData:(void *)user_p {
if (user_p != NULL) {
solClient_log(SOLCLIENT_LOG_NOTICE, "%s received message:\n",
(char *)user_p);
} else {
solClient_log(SOLCLIENT_LOG_NOTICE, "Received message:\n");
}
char buffer[5000];
if ((rc = solClient_msg_dump(msg_p, buffer, 5000)) != SOLCLIENT_OK) {
[self handleErrorWithReturnCode:rc errorString:"solClient_msg_dump()"];
}
/*
* Returning SOLCLIENT_CALLBACK_OK causes the API to free the memory
* used by the message. This is important to avoid leaks.
*/
}
messageReceivePerfCallback(solClient_opaqueSession_pt opaqueSession_p,
solClient_opaqueMsg_pt msg_p, void *user_p) {
// The use of autoreleasepools in CCSMP callbacks is necessary to avoid
// memory leaks, to ensure that ARC works properly when in C scope (i.e.
// outside of Objective-C scope)
@autoreleasepool {
callbackBridge_t *bridge = (callbackBridge_t *)user_p;
id<MessageReceiveCallbackProtocol> callbackProtocol =
(__bridge id<MessageReceiveCallbackProtocol>)bridge->example_p;
return [callbackProtocol
messageReceivePerfCallbackWithSession:opaqueSession_p
message:msg_p
userData:bridge->user_p];
}
}
messageReceivePerfCallbackWithSession:
(solClient_opaqueSession_pt)opaqueSession_p
message:(solClient_opaqueMsg_pt)msg_p
userData:(void *)user_p {
/*
* Returning SOLCLIENT_CALLBACK_OK causes the API to free the memory
* used by the message. This is important to avoid leaks.
*/
}
@end