diff --git a/C/.gitignore b/C/.gitignore new file mode 100644 index 00000000..b5854e30 --- /dev/null +++ b/C/.gitignore @@ -0,0 +1,19 @@ + +# Don't try to track the binary programs +sampleput +sampleget +samplerequest +sampleresponse +samplepublish +samplesubscribe + +# Windows builds +*.exe +*.obj + +# My test scripts +doit +.gdbrc + +# On MacOS these directories may be created with debug info +*.dSYM diff --git a/C/Makefile b/C/Makefile new file mode 100644 index 00000000..8dc2c3db --- /dev/null +++ b/C/Makefile @@ -0,0 +1,40 @@ +COMMONSRC = common.c config.c +COMMONINC = common.h config.h + +MQINC=/opt/mqm/inc +MQLIB=/opt/mqm/lib64 + +APPS = sampleput \ + sampleget \ + samplerequest \ + sampleresponse \ + samplepublish \ + samplesubscribe + +# CDEBUG=-g + +all: $(APPS) + @rm -rf *.dSYM + +clean: + rm -f $(APPS) + @rm -rf *.dSYM + @rm -f *.exe *.obj + +sampleput: sampleput.c $(COMMONSRC) $(COMMONINC) Makefile + $(CC) $(CDEBUG) -o $@ $@.c $(COMMONSRC) -I$(MQINC) -L$(MQLIB) -lmqm_r + +sampleget: sampleget.c $(COMMONSRC) $(COMMONINC) Makefile + $(CC) $(CDEBUG) -o $@ $@.c $(COMMONSRC) -I$(MQINC) -L$(MQLIB) -lmqm_r + +samplerequest: samplerequest.c $(COMMONSRC) $(COMMONINC) Makefile + $(CC) $(CDEBUG) -o $@ $@.c $(COMMONSRC) -I$(MQINC) -L$(MQLIB) -lmqm_r + +sampleresponse: sampleresponse.c $(COMMONSRC) $(COMMONINC) Makefile + $(CC) $(CDEBUG) -o $@ $@.c $(COMMONSRC) -I$(MQINC) -L$(MQLIB) -lmqm_r + +samplepublish: samplepublish.c $(COMMONSRC) $(COMMONINC) Makefile + $(CC) $(CDEBUG) -o $@ $@.c $(COMMONSRC) -I$(MQINC) -L$(MQLIB) -lmqm_r + +samplesubscribe: samplesubscribe.c $(COMMONSRC) $(COMMONINC) Makefile + $(CC) $(CDEBUG) -o $@ $@.c $(COMMONSRC) -I$(MQINC) -L$(MQLIB) -lmqm_r diff --git a/C/README.md b/C/README.md new file mode 100644 index 00000000..b048a207 --- /dev/null +++ b/C/README.md @@ -0,0 +1,84 @@ +# IBM MQ C samples +These samples use the C MQI to demonstrate basic messaging operations. + +## Dependencies +* Install the IBM MQ client for your system, or unpack the Redistributable Client package if available. + * The SDK component is needed in order to compile these programs. +* You also need a C compiler + +## Introduction to the C samples + +**sampleput.c** - Puts message to a queue + +**sampleget.c** - Gets message from a queue + +**samplesubscribe.c** - Subscribes to a topic string and gets publications/messages + +**samplepublish.c** - Publishes messages to a topic string + +**samplerequest.c** - Puts a message on a request queue and waits for a response + +**sampleresponse.c**- Gets message from a request queue, does something with the message and puts it to the reply queue. + +***common.c*** - Common functions, including managing the connection to the queue manager + +***config.c*** - A simple parser for the configuration file + +The connection to the queue manager demonstrates a variety of approaches. Choices are made based on the configuration. +These include +* Authentication +* Client or local connections +* TLS when using clients +* Application name used by Uniform Clusters + +## Compiling the programs +On Linux and MacOS, you can use the Makefile by simply running _make_. The generated programs are created in the current +directory. If you have installed MQ into a non-default location, then you may need to change the directories named in +the Makefile. + +On Windows, the `win_dev.bat` script sets the environment for the Visual Studio 2022 C compiler. And then the +`win_bld.bat` script compiles the programs. Default installation paths are assumed, so you might need to edit these +scripts for your system. + +## Configuration +All of the programs read a JSON-formatted configuration file. The name of the file can be given as a command-line +parameter or by setting the JSON_CONFIG environment variable. If neither is set, the _env.json_ file from the parent +directory is used. + +These samples do not use a real JSON parsing library, and are sensitive to the exact layout of the configuration file. +In particular, each item is expected to be on a separate line of the file. +The default format, from the file in the root of this repository, should be referred to. + +All configuration parameters can also be set through environment variables. See _config.h_ for the names of these +environment variables. + +Setting the `DEBUG` environment variable to any value causes the active configuration to be printed. + +## Running the programs +Apart from the optional command line parameter naming the configuration file, there are no +other parameters to any of the programs. + +You might need to run `setmqenv` to create environment variables pointing at your MQ installation +libraries. And on MacOS, the `DYLD_LIBRARY_PATH` will usually need to be set to include the +`/opt/mqm/lib64` directory. + +See [here](https://www.ibm.com/docs/en/ibm-mq/latest?topic=reference-setmqenv-set-mq-environment) for +more information about `setmqenv`. + +### Put/Get +The `sampleput` application places a short string message onto the queue. + +The `sampleget` application reads all messages from the queue and displays the contents. + +### Publish/Subscribe +Run these samples as a pair. + +Start the `samplesubcribe` program in one window (or in the background) and immediately afterwards start the +`samplepublish` program in another window. + +### Request/Response +Run these samples as a pair. + +Start the `sampleresponse` program in one window (or in the background) and immediately afterwards start the +`samplerequest` program in another window. + diff --git a/C/common.c b/C/common.c new file mode 100644 index 00000000..18664afb --- /dev/null +++ b/C/common.c @@ -0,0 +1,221 @@ +/** + * Copyright 2024 IBM Corp. + * + * Licensed under the Apache License, Version 2.0 (the 'License'); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + **/ + +#include +#include +#include + +#include +#include +#include + +#include "common.h" +#include "config.h" + +static const char *hexChars = "0123456789ABCDEF"; + +/* + * Connect to a queue manager + * Use the loaded configuration information to build the various structures + * Both client and local connections can be made + */ +int connectQMgr(PMQHCONN pHConn) { + MQLONG compCode; + MQLONG reason; + int rc = 0; + int i; + + MQCNO mqcno = {MQCNO_DEFAULT}; + MQSCO mqsco = {MQSCO_DEFAULT}; + MQCSP mqcsp = {MQCSP_DEFAULT}; + MQCD mqcd = {MQCD_CLIENT_CONN_DEFAULT}; + MQCHAR ConnectionName[MQ_CONN_NAME_LENGTH + 1] = {0}; + int s = sizeof(ConnectionName); + + mqEndpoint_t ep = mqEndpoints[0]; + + // Set structure version high enough to include all the fields we might want to use + mqcno.Version = MQCNO_VERSION_8; + + // Build the connection information. If there is a CCDT, then point at that. + // Otherwise build the MQCD client channel structure if we have configuration for that + // Otherwise attempt the default type of connection which might be local bindings or client + // - MQSERVER and MQ_CONNECT_TYPE environment variables can control that process + if (ep.ccdtUrl) { + mqcno.Options |= MQCNO_CLIENT_BINDING; + mqcno.CCDTUrlPtr = ep.ccdtUrl; + mqcno.CCDTUrlLength = strlen(ep.ccdtUrl); + } else { + if (ep.channel && ep.host) { + mqcno.Options |= MQCNO_CLIENT_BINDING; + + // Use strncpy with the maximum length of the field in the structure + // The MQI does not use NULL-terminated strings in its char fields. + strncpy(mqcd.ChannelName,ep.channel,MQ_CHANNEL_NAME_LENGTH); + + // Build the connname. If there are multiple endpoints defined, + // then we use the host/port info from all of them to build the conname. + // Should end up with a string like "host1(port1),host2,host3(port3)" + for (i=0;i<=epIdx;i++) { + if (i > 0) { + strncat(ConnectionName,",", s); + } + strncat(ConnectionName,mqEndpoints[i].host,s); + if (mqEndpoints[i].port) { + strncat(ConnectionName,"(",s); + strncat(ConnectionName,mqEndpoints[i].port,s); + strncat(ConnectionName,")",s); + } + } + strncpy(mqcd.ConnectionName,ConnectionName,MQ_CONN_NAME_LENGTH); + + // Set the TLS Cipher to be used + if (ep.cipher) { + strncpy(mqcd.SSLCipherSpec,ep.cipher,MQ_SSL_CIPHER_SPEC_LENGTH); + } else if (ep.cipherSuite) { + strncpy(mqcd.SSLCipherSpec,ep.cipherSuite,MQ_SSL_CIPHER_SPEC_LENGTH); + } + + // Point at a certificate repository. This needs to contain. at minimum, + // the signing information for the queue manager's certificate. + // There are more options that COULD be used here, but this is the simplest. + if (ep.keyRepository) { + strncpy(mqsco.KeyRepository,ep.keyRepository,MQ_SSL_KEY_REPOSITORY_LENGTH); + mqcno.SSLConfigPtr = &mqsco; + } + + // The client configuration is now referenced from the connect options structure + mqcno.ClientConnPtr = &mqcd; + + } else { + // Just take the default connection type. Don't try to explicitly set + // client or local bindings. + } + + } + + // Authentication can apply for both local and client connections + // Using JWT tokens would require code to actually get the token from + // a server first, so that's not going in here for now. + if (ep.appUser) { + mqcsp.CSPUserIdPtr = ep.appUser; + mqcsp.CSPUserIdLength = strlen(ep.appUser); + mqcsp.CSPPasswordPtr = ep.appPassword; + mqcsp.CSPPasswordLength = strlen(ep.appPassword); + mqcsp.AuthenticationType = MQCSP_AUTH_USER_ID_AND_PWD; + mqcno.SecurityParmsPtr = &mqcsp; + } + + if (ep.applName) { + strncpy(mqcno.ApplName,ep.applName,MQ_APPL_NAME_LENGTH); + } + + // Finally we can try to connect to the queue manager + MQCONNX(ep.qmgr, &mqcno, pHConn, &compCode, &reason); + if (reason != MQRC_NONE) { + printError("MQCONNX", compCode, reason); + rc = -1; + } + + return rc; +} + +// Disconnect from the queue manager. No need for +// any error return code as there's no sensible recovery possible +// if it went wrong. +void disconnectQMgr(PMQHCONN pHConn) { + MQLONG compCode; + MQLONG reason; + + MQDISC(pHConn, &compCode, &reason); + if (reason != MQRC_NONE) { + printError("MQDISC", compCode, reason); + } +} + +// Close the object. No need for +// any error return code as there's no sensible recovery possible +// if it went wrong. Closing a queue is more common than closing other +// object types so we've got an explicitly-named function that does no more +// than calling the generic one. +void closeQueue(MQHCONN hConn, PMQHOBJ pHObj) { + closeObject(hConn,pHObj); +} + +void closeObject(MQHCONN hConn, PMQHOBJ pHObj) { + MQLONG compCode; + MQLONG reason; + MQLONG options = 0; + + MQCLOSE(hConn, pHObj, options, &compCode, &reason); + + if (reason != MQRC_NONE) { + printError("MQCLOSE", compCode, reason); + } + + return; +} + +// Print the CC/RC values in a formatted string. Show both the numeric and string values +void printError(char *verb, MQLONG compCode, MQLONG reason) { + printf("Call: %s CompCode: %d [%s] Reason: %d [%s]\n", verb, compCode, MQCC_STR(compCode), reason, MQRC_STR(reason)); + return; +} + + +/* A simple formatter for hex data showing chars and bytes */ +void dumpHex(const char *title, void *buf, int length) { + int i, j; + unsigned char *p = (unsigned char *)buf; + int rows; + int o; + char line[80]; + FILE *fp = stdout; + + fprintf(fp, "-- %s -- (%d bytes) --------------------\n", title, length); + + rows = (length + 15) / 16; + for (i = 0; i < rows; i++) { + + memset(line, ' ', sizeof(line)); + o = snprintf(line, sizeof(line)-1, "%8.8X : ", i * 16); + + for (j = 0; j < 16 && (j + (i * 16) < length); j++) { + line[o++] = hexChars[p[j] >> 4]; + line[o++] = hexChars[p[j] & 0x0F]; + if (j % 4 == 3) + line[o++] = ' '; + } + + o = 48; + line[o++] = '|'; + for (j = 0; j < 16 && (j + (i * 16) < length); j++) { + char c = p[j]; + if (!isalnum((int)c) && !ispunct((int)c) && (c != ' ')) + c = '.'; + line[o++] = c; + } + + o = 65; + line[o++] = '|'; + line[o++] = 0; + + fprintf(fp, "%s\n", line); + p += 16; + } + + return; +} \ No newline at end of file diff --git a/C/common.h b/C/common.h new file mode 100644 index 00000000..8662146d --- /dev/null +++ b/C/common.h @@ -0,0 +1,33 @@ +/** + * Copyright 2024 IBM Corp. + * + * Licensed under the Apache License, Version 2.0 (the 'License'); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + **/ + +#if !defined PATTERN_COMMON_H +#define PATTERN_COMMON_H + +#include + +int connectQMgr(PMQHCONN); +void disconnectQMgr(PMQHCONN); + +void closeQueue(MQHCONN hConn, PMQHOBJ pHObj); +void closeObject(MQHCONN hConn, PMQHOBJ pHObj); + +void printError(char *verb, MQLONG compCode, MQLONG reason); +void dumpHex(const char *title, void *buf, int length); + +#define DEFAULT_BUFFER_LENGTH 256 + +#endif \ No newline at end of file diff --git a/C/config.c b/C/config.c new file mode 100644 index 00000000..2bbd3a05 --- /dev/null +++ b/C/config.c @@ -0,0 +1,381 @@ +/** + * Copyright 2024 IBM Corp. + * + * Licensed under the Apache License, Version 2.0 (the 'License'); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + **/ + +#include +#include +#include +#include +#include + +#include "config.h" + +/* + * There are a number of 3rd-party C libraries to implement structures like maps (which might be + * preferred here instead of explicit structures) but we want to stick to standard + * functions and not rely on external packages. Similarly, we are not going to use + * a generic JSON-parsing library. + * + * Note that we are VERY dependent on the format of the JSON config file + * staying in its current format. Each element must be on a separate line. + */ + +typedef struct nameValue { + char *name; + char *value; +} nv_t; + +// Declaration of functions only used in this file +static char *stripLine(char *line); +static void splitLine(char *line, nv_t *nv); +static int overrideEnv(char **p, char *field); + +static char *cfStrdup(char *); +static void cfFree(void *); +static void dumpConfig(char *); + +#define LINE_LENGTH 255 // Long enough for the input file + +// Externalised config structures +jwtEndpoint_t jwt; +mqEndpoint_t mqEndpoints[MAX_MQ_ENDPOINTS]; +int epIdx = 0; +int debug = 0; + +// Internal to this file +static jwtEndpoint_t *pJwt = &jwt; +static int or ; // Was an environment variable used to override config of qmgr endpoint + +// Simplify checks when dumping strings that might be NULL +// The printf function does this on some, but not necessarily all, platforms +#define N(p) (p ? p : "(null)") + +// Return 0 on success, -1 on failure +// Parse the given filename and load values into configuration structures. +// Then look for any environment variables that could override. The filename +// can be NULL, in which case only the env vars are considered. +int parseConfig(char *filename) { + int rc = 0; + int len; + nv_t nv; + mqEndpoint_t *ep; + char *v; + + char line[LINE_LENGTH + 1] = {0}; + enum { ENDPOINT_MQ, ENDPOINT_JWT } section; + + epIdx = 0; + ep = &mqEndpoints[epIdx]; + memset(ep, 0, sizeof(*ep)); + memset(pJwt, 0, sizeof(*pJwt)); + + if (filename && (strlen(filename) > 0)) { + FILE *fp = fopen(filename, "r"); + if (!fp) { + printf("ERROR: Open of %s failed. Errno: %d [%s]\n", filename, errno, strerror(errno)); + return -1; + } + + while (rc == 0) { + if (fgets(line, LINE_LENGTH, fp) == NULL) { + break; + } + + len = strlen(line); + if (len >= LINE_LENGTH - 1 && line[len - 1] != '\n' && line[len - 1] != '\r') { + printf("ERROR: Line \"%s\" is too long\n", line); + fclose(fp); + return -1; + } + + // Convert the input line into a trimmed version with spaces and some special chars removed + stripLine(line); + + if (strstr(line, "MQ_ENDPOINTS:") != NULL) { + section = ENDPOINT_MQ; + } else if (section == ENDPOINT_MQ && strstr(line, "},{")) { + // Currently The only array is the MQ endpoints. This line indicates we've moved into the next entry in the list + if (epIdx == (MAX_MQ_ENDPOINTS - 1)) { + printf("ERROR: File has too many MQ endpoint elements in JSON Array\n"); + fclose(fp); + return -1; + } else { + epIdx++; + ep = &mqEndpoints[epIdx]; + memset(ep, 0, sizeof(*ep)); + } + + } else if (strstr(line, "JWT_ISSUER:")) { + section = ENDPOINT_JWT; + } + + // Now split the line into name/value pairs + splitLine(line, &nv); + + switch (section) { + case ENDPOINT_MQ: + if (!strcmp(nv.name, CONFIG_HOST)) + ep->host = nv.value; + else if (!strcmp(nv.name, CONFIG_PORT)) + ep->port = nv.value; + else if (!strcmp(nv.name, CONFIG_CHANNEL)) + ep->channel = nv.value; + else if (!strcmp(nv.name, CONFIG_CCDT_URL)) + ep->ccdtUrl = nv.value; + else if (!strcmp(nv.name, CONFIG_QMGR)) + ep->qmgr = nv.value; + else if (!strcmp(nv.name, CONFIG_APP_USER)) + ep->appUser = nv.value; + else if (!strcmp(nv.name, CONFIG_APPL_NAME)) + ep->applName = nv.value; + else if (!strcmp(nv.name, CONFIG_APP_PASSWORD)) + ep->appPassword = nv.value; + else if (!strcmp(nv.name, CONFIG_QUEUE_NAME)) + ep->queueName = nv.value; + else if (!strcmp(nv.name, CONFIG_BACKOUT_QUEUE)) + ep->backoutQueue = nv.value; + else if (!strcmp(nv.name, CONFIG_MODEL_QUEUE_NAME)) + ep->modelQueueName = nv.value; + else if (!strcmp(nv.name, CONFIG_DYNAMIC_QUEUE_PREFIX)) + ep->dynamicQueuePrefix = nv.value; + else if (!strcmp(nv.name, CONFIG_TOPIC_NAME)) + ep->topicName = nv.value; + else if (!strcmp(nv.name, CONFIG_CIPHER)) + ep->cipher = nv.value; + else if (!strcmp(nv.name, CONFIG_CIPHER_SUITE)) + ep->cipherSuite = nv.value; + else if (!strcmp(nv.name, CONFIG_KEY_REPOSITORY)) + ep->keyRepository = nv.value; + else if (!strcmp(nv.name, CONFIG_WAIT_INTERVAL)) + ep->waitInterval = nv.value; + break; + + case ENDPOINT_JWT: + if (!strcmp(nv.name, CONFIG_JWT_TOKEN_ENDPOINT)) + pJwt->tokenEndpoint = nv.value; + else if (!strcmp(nv.name, CONFIG_JWT_TOKEN_USERNAME)) + pJwt->tokenUserName = nv.value; + else if (!strcmp(nv.name, CONFIG_JWT_TOKEN_PWD)) + pJwt->tokenPwd = nv.value; + else if (!strcmp(nv.name, CONFIG_JWT_TOKEN_CLIENTID)) + pJwt->tokenClientId = nv.value; + break; + + default: // Ignore the line + break; + } + } + fclose(fp); + } + + // Now process any overriding environment variables. The MQ + // endpoints all go into the first structure + + ep = &mqEndpoints[0]; + or = 0; + or += overrideEnv(&ep->host, CONFIG_HOST); + or += overrideEnv(&ep->port, CONFIG_PORT); + or += overrideEnv(&ep->channel, CONFIG_CHANNEL); + or += overrideEnv(&ep->ccdtUrl, CONFIG_CCDT_URL); + or += overrideEnv(&ep->ccdtUrl, "MQCCDTURL"); // An alternative env var to match other samples + + or += overrideEnv(&ep->qmgr, CONFIG_QMGR); + or += overrideEnv(&ep->applName, CONFIG_APPL_NAME); + + or += overrideEnv(&ep->appUser, CONFIG_APP_USER); + or += overrideEnv(&ep->appPassword, CONFIG_APP_PASSWORD); + or += overrideEnv(&ep->queueName, CONFIG_QUEUE_NAME); + or += overrideEnv(&ep->backoutQueue, CONFIG_BACKOUT_QUEUE); + or += overrideEnv(&ep->modelQueueName, CONFIG_MODEL_QUEUE_NAME); + or += overrideEnv(&ep->dynamicQueuePrefix, CONFIG_DYNAMIC_QUEUE_PREFIX); + or += overrideEnv(&ep->topicName, CONFIG_TOPIC_NAME); + or += overrideEnv(&ep->cipher, CONFIG_CIPHER); + or += overrideEnv(&ep->cipherSuite, CONFIG_CIPHER_SUITE); + or += overrideEnv(&ep->keyRepository, CONFIG_KEY_REPOSITORY); + or += overrideEnv(&ep->waitInterval, CONFIG_WAIT_INTERVAL); + + // Has all the config for a qmgr come from environment variables? + if (or > 0 && epIdx == 0) { + epIdx = 1; + } + + // Don't need to know about JWT overrides as there's only one structure + (void)overrideEnv(&pJwt->tokenEndpoint, CONFIG_JWT_TOKEN_ENDPOINT); + (void)overrideEnv(&pJwt->tokenUserName, CONFIG_JWT_TOKEN_USERNAME); + (void)overrideEnv(&pJwt->tokenPwd, CONFIG_JWT_TOKEN_PWD); + (void)overrideEnv(&pJwt->tokenClientId, CONFIG_JWT_TOKEN_CLIENTID); + + // Call this if we want to check the config has been correctly parsed + if (debug) { + dumpConfig(filename); + } + + return rc; +} + +// Given an input line like "XXXX:YYYY", split it into name/value strings +// We can assume the line does not include a colon before the value starts, but +// the value itself COULD contain a colon. +// +// We create a strdup'ed copy of the value from the line. This +// memory is only allocated once during the execution so it may appear like a leak. +// But it should not be large enough to really worry about. If you do care, the freeConfig() +// function can be used once the configuration is no longer needed +static void splitLine(char *line, nv_t *nv) { + nv->value = NULL; + if (line) { + char *p = strtok(line, ":"); + + if (p) { + nv->name = p; // First token - don't need this ptr when we've finished working on the line + p = strtok(NULL, ""); // Don't want to split the rest of the line + if (p && strlen(p) > 0) + nv->value = cfStrdup(p); // Copy the value so it's not lost when we parse the next line + } + } +} + +/* + * Input line is guaranteed to be NULL-terminated. We overwrite its contents, shuffling + * valid chars downwards.So we end up with a left-justified line with no spaces and no quote chars. + * We assume that spaces (in particular) are never significant either in the structure or the specific values. + */ +static char *stripLine(char *line) { + int idx = 0; + int i; + + int len = strlen(line); + for (i = 0; i < len; i++) { + switch (line[i]) { + case ' ': + break; + case '\t': + break; + case '\"': + break; + case '\n': + break; + case '\r': // Deal with DOS line-endings + break; + default: + line[idx++] = line[i]; + break; + } + } + if (idx > 0 && line[idx - 1] == ',') { // Strip trailing commas + line[idx - 1] = 0; + } else { + line[idx] = 0; + } + + return line; +} + +// Get a value from an environment variable to override anything +// that's been set in the config file. +// Return 1 if we've read the value from the env; 0 otherwise +static int overrideEnv(char **p, char *field) { + int rc = 0; + char *e = getenv(field); + + if (e) { + rc = 1; + // The config structures are all strdup'd strings. So if we're going + // to override a value that's already loaded, we need + // to free it first to avoid leaks. + if (*p) { + cfFree(*p); + } + *p = cfStrdup(e); + } + return rc; +} + +static void dumpConfig(char *filename) { + int i; + + printf("Configuration read from %s\n",N(filename)); + for (i = 0; i <= epIdx; i++) { + mqEndpoint_t *ep = &mqEndpoints[i]; + printf("MQ Endpoint: %d\n", i); + printf(" Host: %s\n", N(ep->host)); + printf(" Port: %s\n", N(ep->port)); + printf(" Channel : %s\n", N(ep->channel)); + printf(" Qmgr : %s\n", N(ep->qmgr)); + printf(" AppUser : %s\n", N(ep->appUser)); + printf(" AppPassword : %s\n", N(ep->appPassword)); + printf(" QueueName : %s\n", N(ep->queueName)); + printf(" BackoutQueue : %s\n", N(ep->backoutQueue)); + printf(" ModelQueueName : %s\n", N(ep->modelQueueName)); + printf(" DynamicQueuePrefix : %s\n", N(ep->dynamicQueuePrefix)); + printf(" TopicName : %s\n", N(ep->topicName)); + printf(" Cipher : %s\n", N(ep->cipher)); + printf(" CipherSuite : %s\n", N(ep->cipherSuite)); + printf(" KeyRepository : %s\n", N(ep->keyRepository)); + } + + printf("JWT Endpoint:\n"); + printf(" TokenEndpoint :%s\n", N(pJwt->tokenEndpoint)); + printf(" TokenUserName :%s\n", N(pJwt->tokenUserName)); + printf(" TokenPwd :%s\n", N(pJwt->tokenPwd)); + printf(" TokenClientId :%s\n", N(pJwt->tokenClientId)); +} + +// Do the cleanup of the strdup'ed elements from the config. +void freeConfig() { + int i; + for (i = 0; i <= epIdx; i++) { + mqEndpoint_t *ep = &mqEndpoints[i]; + cfFree(ep->host); + cfFree((ep->port)); + cfFree(ep->channel); + cfFree(ep->qmgr); + cfFree(ep->appUser); + cfFree(ep->appPassword); + cfFree(ep->queueName); + cfFree(ep->backoutQueue); + cfFree(ep->modelQueueName); + cfFree(ep->dynamicQueuePrefix); + cfFree(ep->topicName); + cfFree(ep->cipher); + cfFree(ep->cipherSuite); + cfFree(ep->keyRepository); + } + + cfFree(pJwt->tokenEndpoint); + cfFree(pJwt->tokenUserName); + cfFree(pJwt->tokenPwd); + cfFree(pJwt->tokenClientId); +} + +// Wrapper around free() to avoid trying to free NULL pointers +static void cfFree(void *p) { + if (p) { + free(p); + } + return; +} + +// Wrapper around strdup() to check for NULL returns +static char *cfStrdup(char *s) { + char *p = strdup(s); + + // A failure to allocate memory is fatal. + if (!p) { + printf("ERROR: Cannot allocate memory.\n"); + exit(1); + } + return p; +} diff --git a/C/config.h b/C/config.h new file mode 100644 index 00000000..734d5d3a --- /dev/null +++ b/C/config.h @@ -0,0 +1,93 @@ +/** + * Copyright 2024 IBM Corp. + * + * Licensed under the Apache License, Version 2.0 (the 'License'); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + **/ + +#if !defined PATTERN_CONFIG_H +#define PATTERN_CONFIG_H + +// Where to read the configuration from unless overridden +#define DEFAULT_CONFIG_FILE "../env.json" +#define CONFIG_JSON_FILE "JSON_CONFIG" + +// Structures to hold configuration values +typedef struct mqEndpoint { + char *host; + char *port; + char *channel; + char *qmgr; + char *ccdtUrl; + char *appUser; + char *appPassword; + char *applName; + char *queueName; + char *backoutQueue; + char *modelQueueName; + char *dynamicQueuePrefix; + char *topicName; + char *cipher; + char *cipherSuite; + char *keyRepository; + char *waitInterval; +} mqEndpoint_t; + +typedef struct jwtEndpoint { + char *tokenEndpoint; + char *tokenUserName; + char *tokenPwd; + char *tokenClientId; +} jwtEndpoint_t; + +#define MAX_MQ_ENDPOINTS 10 // Put a limit on the number of entries in the config file for simplicity +extern mqEndpoint_t mqEndpoints[]; +extern int epIdx; +extern jwtEndpoint_t jwt; + +extern int debug; + +// External function declarations +int parseConfig(char *filename); +void freeConfig(); + +#define CONFIG_HOST "HOST" +#define CONFIG_PORT "PORT" +#define CONFIG_CHANNEL "CHANNEL" +#define CONFIG_CCDT_URL "CCDT_URL" +#define CONFIG_QMGR "QMGR" + +#define CONFIG_APPL_NAME "APPL_NAME" +#define CONFIG_APP_USER "APP_USER" +#define CONFIG_APP_PASSWORD "APP_PASSWORD" + +#define CONFIG_QUEUE_NAME "QUEUE_NAME" +#define CONFIG_BACKOUT_QUEUE "BACKOUT_QUEUE" +#define CONFIG_MODEL_QUEUE_NAME "MODEL_QUEUE_NAME" +#define CONFIG_DYNAMIC_QUEUE_PREFIX "DYNAMIC_QUEUE_PREFIX" +#define CONFIG_TOPIC_NAME "TOPIC_NAME" + +#define CONFIG_CIPHER "CIPHER" +#define CONFIG_CIPHER_SUITE "CIPHER_SUITE" +#define CONFIG_KEY_REPOSITORY "KEY_REPOSITORY" + +#define CONFIG_WAIT_INTERVAL "WAIT_INTERVAL" + +#define CONFIG_JWT_TOKEN_ENDPOINT "JWT_TOKEN_ENDPOINT" +#define CONFIG_JWT_TOKEN_USERNAME "JWT_TOKEN_USERNAME" +#define CONFIG_JWT_TOKEN_PWD "JWT_TOKEN_PWD" +#define CONFIG_JWT_TOKEN_CLIENTID "JWT_TOKEN_CLIENTID" + +// Environment variable only +#define CONFIG_DEBUG "DEBUG" + +#endif \ No newline at end of file diff --git a/C/sampleget.c b/C/sampleget.c new file mode 100644 index 00000000..e24fdb93 --- /dev/null +++ b/C/sampleget.c @@ -0,0 +1,173 @@ +/** + * Copyright 2024 IBM Corp. + * + * Licensed under the Apache License, Version 2.0 (the 'License'); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + **/ + +/* This is a demonstration showing the GET operations from an MQ Queue + * using the MQI C interface. + */ + +#include +#include +#include +#include + +#include + +#include "common.h" +#include "config.h" + +static int openQueue(MQHCONN hConn, PMQHOBJ pHObj); +static int getMessages(MQHCONN hConn, MQHOBJ hObj); + +// The only (optional) parameter to this program is the name of the +// configuration file +int main(int argc, char **argv) { + int rc = 0; + time_t now; + MQHCONN hConn = MQHC_UNUSABLE_HCONN; + MQHOBJ hObj = MQHO_UNUSABLE_HOBJ; + + if (getenv(CONFIG_DEBUG)) { + debug = 1; + } + + char *configFile = getenv(CONFIG_JSON_FILE); + if (argc > 1) { + configFile = argv[1]; + } + if (!configFile) { + configFile = DEFAULT_CONFIG_FILE; + } + + printf("Starting up Application: %s\n", argv[0]); + rc = parseConfig(configFile); + if (rc == 0) { + rc = connectQMgr(&hConn); + } + + if (rc == 0) { + rc = openQueue(hConn, &hObj); + } + if (rc == 0) { + rc = getMessages(hConn, hObj); + } + + if (hObj != MQHO_UNUSABLE_HOBJ) { + closeQueue(hConn, &hObj); + } + + if (hConn != MQHC_UNUSABLE_HCONN) { + disconnectQMgr(&hConn); + } + + printf("\nDone. Exit code:%d\n", rc); + exit(rc); +} + +/* + * Open a queue for INPUT as we will be getting messages + * + * Return 0 if OK; -1 otherwise + */ +static int openQueue(MQHCONN hConn, PMQHOBJ pHObj) { + MQLONG compCode; + MQLONG reason; + int rc = 0; + + MQOD mqod = {MQOD_DEFAULT}; + MQLONG options = MQOO_INPUT_EXCLUSIVE; + + strncpy(mqod.ObjectName, mqEndpoints[0].queueName, MQ_Q_NAME_LENGTH); + mqod.ObjectType = MQOT_Q; + + MQOPEN(hConn, &mqod, options, pHObj, &compCode, &reason); + + if (reason != MQRC_NONE) { + printError("MQOPEN", compCode, reason); + rc = -1; + } + + return rc; +} + +/* + * Get all available messages from the queue, printing the contents. + * Return 0 if OK; -1 otherwise + */ +static int getMessages(MQHCONN hConn, MQHOBJ hObj) { + + MQLONG compCode; + MQLONG reason; + int rc = 0; + int ok = 1; + MQMD mqmd = {MQMD_DEFAULT}; + MQGMO mqgmo = {MQGMO_DEFAULT}; + char buffer[DEFAULT_BUFFER_LENGTH]; + MQLONG datalength; + int msgCount = 0; + + // Structure version must be high enough to recognise the MatchOptions field + mqgmo.Version = MQGMO_VERSION_2; + + // Various options to control retrieval + mqgmo.Options = MQGMO_NO_SYNCPOINT; + mqgmo.Options |= MQGMO_FAIL_IF_QUIESCING; + mqgmo.Options |= MQGMO_NO_WAIT; + mqgmo.Options |= MQGMO_CONVERT; + + mqgmo.Options |= MQGMO_ACCEPT_TRUNCATED_MSG; // Process the message even if it + // is too long for the buffer + + // Not going to try to match on MsgId or CorrelId + mqgmo.MatchOptions = MQMO_NONE; + + // Loop until there are no more messages on the queue + while (ok) { + + MQGET(hConn, hObj, &mqmd, &mqgmo, sizeof(buffer), buffer, &datalength, + &compCode, &reason); + + if (reason == MQRC_NONE) { + msgCount++; + if (!strncmp(mqmd.Format, MQFMT_STRING, MQ_FORMAT_LENGTH)) { + printf("Rcvd Message: %*.*s\n", datalength, datalength, buffer); + } else { + char title[32]; + sprintf(title, "Rcvd Message Type:%*.*s", MQ_FORMAT_LENGTH, MQ_FORMAT_LENGTH, + mqmd.Format); + dumpHex(title, buffer, datalength); + } + } else { + printError("MQGET", compCode, reason); + switch (reason) { + case MQRC_NO_MSG_AVAILABLE: + // This is not really an error but we do need to break from the loop + ok = 0; + break; + case MQRC_TRUNCATED_MSG_ACCEPTED: + // Carry on if there are more messages + msgCount++; // This also increments the count + break; + default: + rc = -1; + ok = 0; + break; + } + } + } + + printf("\nMessages read: %d\n",msgCount); + return rc; +} diff --git a/C/samplepublish.c b/C/samplepublish.c new file mode 100644 index 00000000..51516850 --- /dev/null +++ b/C/samplepublish.c @@ -0,0 +1,154 @@ +/** + * Copyright 2024 IBM Corp. + * + * Licensed under the Apache License, Version 2.0 (the 'License'); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + **/ + +/* + * This is a demonstration showing the Publish operation to a MQ Topic + * using the MQI C interface + */ + +#include +#include +#include +#include + +#include + +#include "common.h" +#include "config.h" + +static int openTopic(MQHCONN hConn, PMQHOBJ pHObj); +static int publishMessage(MQHCONN hConn, MQHOBJ hObj, char *msg); + +// The only (optional) parameter to this program is the name of the configuration file +int main(int argc, char **argv) { + int rc = 0; + time_t now; + MQHCONN hConn = MQHC_UNUSABLE_HCONN; + MQHOBJ hObj = MQHO_UNUSABLE_HOBJ; + + if (getenv(CONFIG_DEBUG)) { + debug = 1; + } + + char *configFile = getenv(CONFIG_JSON_FILE); + if (argc > 1) { + configFile = argv[1]; + } + if (!configFile) { + configFile = DEFAULT_CONFIG_FILE; + } + + printf("Starting up Application: %s\n", argv[0]); + rc = parseConfig(configFile); + if (rc == 0) { + rc = connectQMgr(&hConn); + } + + if (rc == 0) { + rc = openTopic(hConn, &hObj); + } + + if (rc == 0) { + char msgData[DEFAULT_BUFFER_LENGTH]; + time(&now); + // ctime always returns a 26-byte buffer. But we want to strip off the trailing "\n" and NUL. + // So force the buffer to use 24 chars. + sprintf(msgData, "Hello from C at %24.24s", ctime(&now)); + rc = publishMessage(hConn, hObj, msgData); + } + + if (hObj != MQHO_UNUSABLE_HOBJ) { + closeQueue(hConn, &hObj); + } + + if (hConn != MQHC_UNUSABLE_HCONN) { + disconnectQMgr(&hConn); + } + + printf("\nDone. Exit code:%d\n", rc); + exit(rc); +} + +/* + * Open a topic for OUTPUT as we will be publishing a message + * + * Return 0 if OK; -1 otherwise + */ +static int openTopic(MQHCONN hConn, PMQHOBJ pHObj) { + MQLONG compCode; + MQLONG reason; + MQCHARV objectString = {MQCHARV_DEFAULT}; + char *topic; + int rc = 0; + + MQOD mqod = {MQOD_DEFAULT}; + MQLONG options = MQOO_OUTPUT; + + // The objectString contains the full topic string that we + // want to use. Alternatives allow a combination of administered object + // names and the objectString value, but that's more advanced. + topic = mqEndpoints[0].topicName; + objectString.VSPtr = topic; + objectString.VSLength = (MQLONG)strlen(topic); + + mqod.Version = MQOD_VERSION_4; // The ObjectString field requires this level of structure + mqod.ObjectType = MQOT_TOPIC; + mqod.ObjectString = objectString; + + MQOPEN(hConn, &mqod, options, pHObj, &compCode, &reason); + + if (reason != MQRC_NONE) { + printError("MQOPEN", compCode, reason); + rc = -1; + } + + return rc; +} + +/* + * Publish the message on the topic + * + * Return 0 if OK; -1 otherwise + */ +static int publishMessage(MQHCONN hConn, MQHOBJ hObj, char *msg) { + MQLONG compCode; + MQLONG reason; + int rc = 0; + MQMD mqmd = {MQMD_DEFAULT}; + MQPMO mqpmo = {MQPMO_DEFAULT}; + + // Options to control how the message is published. We might be interested + // to know if there are no subscribers. + mqpmo.Options = MQPMO_FAIL_IF_QUIESCING; + mqpmo.Options |= MQPMO_NO_SYNCPOINT; + mqpmo.Options |= MQPMO_NEW_MSG_ID; + mqpmo.Options |= MQPMO_WARN_IF_NO_SUBS_MATCHED; + + // We are sending a character string. Set the format to indicate + // that, so that the recipient can convert the codepage if necessary + memcpy(mqmd.Format,MQFMT_STRING,MQ_FORMAT_LENGTH); + + // Now publish the message + MQPUT(hConn, hObj, &mqmd, &mqpmo, strlen(msg), msg, &compCode, &reason); + if (reason != MQRC_NONE) { + printError("MQPUT", compCode, reason); + rc = -1; + } else { + printf("Sent Message: %s\n",msg); + } + + return rc; +} diff --git a/C/sampleput.c b/C/sampleput.c new file mode 100644 index 00000000..961093f1 --- /dev/null +++ b/C/sampleput.c @@ -0,0 +1,142 @@ +/** + * Copyright 2024 IBM Corp. + * + * Licensed under the Apache License, Version 2.0 (the 'License'); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + **/ + +/* + * This is a demonstration showing the PUT operation onto a MQ Queue + * using the MQI C interface + */ + +#include +#include +#include +#include + +#include + +#include "common.h" +#include "config.h" + +static int openQueue(MQHCONN hConn, PMQHOBJ pHObj); +static int putMessage(MQHCONN hConn, MQHOBJ hObj, char *msg); + +// The only (optional) parameter to this program is the name of the configuration file +int main(int argc, char **argv) { + int rc = 0; + time_t now; + MQHCONN hConn = MQHC_UNUSABLE_HCONN; + MQHOBJ hObj = MQHO_UNUSABLE_HOBJ; + + if (getenv(CONFIG_DEBUG)) { + debug = 1; + } + + char *configFile = getenv(CONFIG_JSON_FILE); + if (argc > 1) { + configFile = argv[1]; + } + if (!configFile) { + configFile = DEFAULT_CONFIG_FILE; + } + + printf("Starting up Application: %s\n", argv[0]); + rc = parseConfig(configFile); + if (rc == 0) { + rc = connectQMgr(&hConn); + } + + if (rc == 0) { + rc = openQueue(hConn, &hObj); + } + + if (rc == 0) { + char msgData[DEFAULT_BUFFER_LENGTH]; + time(&now); + // ctime always returns a 26-byte buffer. But we want to strip off the trailing "\n" and NUL. + // So force the buffer to use 24 chars. + sprintf(msgData, "Hello from C at %24.24s", ctime(&now)); + rc = putMessage(hConn, hObj, msgData); + } + + if (hObj != MQHO_UNUSABLE_HOBJ) { + closeQueue(hConn, &hObj); + } + + if (hConn != MQHC_UNUSABLE_HCONN) { + disconnectQMgr(&hConn); + } + + printf("\nDone. Exit code:%d\n", rc); + exit(rc); +} + +/* + * Open a queue for OUTPUT as we will be putting a message + * + * Return 0 if OK; -1 otherwise + */ +static int openQueue(MQHCONN hConn, PMQHOBJ pHObj) { + MQLONG compCode; + MQLONG reason; + int rc = 0; + + MQOD mqod = {MQOD_DEFAULT}; + MQLONG options = MQOO_OUTPUT; + + strncpy(mqod.ObjectName, mqEndpoints[0].queueName, MQ_Q_NAME_LENGTH); + mqod.ObjectType = MQOT_Q; + + MQOPEN(hConn, &mqod, options, pHObj, &compCode, &reason); + + if (reason != MQRC_NONE) { + printError("MQOPEN", compCode, reason); + rc = -1; + } + + return rc; +} + +/* + * Put the message to the queue. + * + * Return 0 if OK; -1 otherwise + */ +static int putMessage(MQHCONN hConn, MQHOBJ hObj, char *msg) { + MQLONG compCode; + MQLONG reason; + int rc = 0; + MQMD mqmd = {MQMD_DEFAULT}; + MQPMO mqpmo = {MQPMO_DEFAULT}; + + // Various options to control how the message is put + mqpmo.Options = MQPMO_FAIL_IF_QUIESCING; + mqpmo.Options |= MQPMO_NO_SYNCPOINT; + mqpmo.Options |= MQPMO_NEW_MSG_ID; + + // We are sending a character string. Set the format to indicate + // that, so that the recipient can convert the codepage if necessary + memcpy(mqmd.Format,MQFMT_STRING,MQ_FORMAT_LENGTH); + + // Now put the message to the queue + MQPUT(hConn, hObj, &mqmd, &mqpmo, strlen(msg), msg, &compCode, &reason); + if (reason != MQRC_NONE) { + printError("MQPUT", compCode, reason); + rc = -1; + } else { + printf("Sent Message: %s\n",msg); + } + + return rc; +} diff --git a/C/samplerequest.c b/C/samplerequest.c new file mode 100644 index 00000000..b097114e --- /dev/null +++ b/C/samplerequest.c @@ -0,0 +1,270 @@ +/** + * Copyright 2024 IBM Corp. + * + * Licensed under the Apache License, Version 2.0 (the 'License'); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + **/ + +/* This is part of a demonstration showing a common request/reply pattern + * using the C MQI. + * + * This program sends a request message and then waits for a reply from the responder. + * + * A temporary queue is used for the reply. + */ + +#include +#include +#include +#include + +#include + +#include "common.h" +#include "config.h" + +static int openRequestQueue(MQHCONN hConn, PMQHOBJ pHObj); +static int openReplyQueue(MQHCONN hConn, PMQHOBJ pHObj); + +static int putRequestMessage(MQHCONN hConn, MQHOBJ hObj, char *msg); +static int getReplyMessage(MQHCONN hConn, MQHOBJ hObj); + +static char replyToQ[MQ_Q_NAME_LENGTH] = {' '}; +static char replyToQMgr[MQ_Q_MGR_NAME_LENGTH] = {' '}; +static MQBYTE24 msgId = {MQMI_NONE_ARRAY}; + +#define DEFAULT_WAIT_INTERVAL 2 // seconds to wait for a reply + +// The only (optional) parameter to this program is the name of the configuration file +int main(int argc, char **argv) { + int rc = 0; + time_t now; + MQHCONN hConn = MQHC_UNUSABLE_HCONN; + MQHOBJ hObjRequest = MQHO_UNUSABLE_HOBJ; + MQHOBJ hObjReply = MQHO_UNUSABLE_HOBJ; + + if (getenv(CONFIG_DEBUG)) { + debug = 1; + } + + char *configFile = getenv(CONFIG_JSON_FILE); + if (argc > 1) { + configFile = argv[1]; + } + if (!configFile) { + configFile = DEFAULT_CONFIG_FILE; + } + + printf("Starting up Application: %s\n", argv[0]); + rc = parseConfig(configFile); + if (rc == 0) { + rc = connectQMgr(&hConn); + } + + if (rc == 0) { + rc = openRequestQueue(hConn, &hObjRequest); + } + + if (rc == 0) { + rc = openReplyQueue(hConn, &hObjReply); + } + + if (rc == 0) { + char msgData[DEFAULT_BUFFER_LENGTH]; + time(&now); + // ctime always returns a 26-byte buffer. But we want to strip off the trailing "\n" and NUL. + // So force the buffer to use 24 chars. + sprintf(msgData, "Hello from C at %24.24s", ctime(&now)); + rc = putRequestMessage(hConn, hObjRequest, msgData); + } + + if (rc == 0) { + rc = getReplyMessage(hConn, hObjReply); + } + + if (hObjRequest != MQHO_UNUSABLE_HOBJ) { + closeQueue(hConn, &hObjRequest); + } + + if (hObjReply != MQHO_UNUSABLE_HOBJ) { + closeQueue(hConn, &hObjReply); + } + + if (hConn != MQHC_UNUSABLE_HCONN) { + disconnectQMgr(&hConn); + } + + printf("\nDone. Exit code:%d\n", rc); + exit(rc); +} + +/* + * Open a queue for OUTPUT as we will be putting a message + * + * Return 0 if OK; -1 otherwise + */ +static int openRequestQueue(MQHCONN hConn, PMQHOBJ pHObj) { + MQLONG compCode; + MQLONG reason; + int rc = 0; + + MQOD mqod = {MQOD_DEFAULT}; + MQLONG options = MQOO_OUTPUT; + + strncpy(mqod.ObjectName, mqEndpoints[0].queueName, MQ_Q_NAME_LENGTH); + + mqod.ObjectType = MQOT_Q; + + MQOPEN(hConn, &mqod, options, pHObj, &compCode, &reason); + + if (reason != MQRC_NONE) { + printError("MQOPEN", compCode, reason); + rc = -1; + } + + return rc; +} + +/* + * Open a queue for INPUT from where we will get the reply message + * Use a model queue, so that a temporary reply queue is created and + * automatically destroyed when closed + * + * Return 0 if OK; -1 otherwise + */ +static int openReplyQueue(MQHCONN hConn, PMQHOBJ pHObj) { + MQLONG compCode; + MQLONG reason; + int rc = 0; + + MQOD mqod = {MQOD_DEFAULT}; + MQLONG options = MQOO_INPUT_EXCLUSIVE; + + mqEndpoint_t ep = mqEndpoints[0]; + strncpy(mqod.ObjectName, ep.modelQueueName, MQ_Q_NAME_LENGTH); + if (ep.dynamicQueuePrefix) { + // The dynamic queue has its name start with this prefix + strncpy(mqod.DynamicQName, ep.dynamicQueuePrefix, MQ_Q_NAME_LENGTH); + } + mqod.ObjectType = MQOT_Q; + + MQOPEN(hConn, &mqod, options, pHObj, &compCode, &reason); + + if (reason != MQRC_NONE) { + printError("MQOPEN", compCode, reason); + rc = -1; + } else { + // The name of the dynamically-created queue is now in the MQOD structure + // Stash it, so we can tell the responder its name. + strncpy(replyToQ, mqod.ObjectName, MQ_Q_NAME_LENGTH); + strncpy(replyToQMgr, mqod.ObjectQMgrName, MQ_Q_MGR_NAME_LENGTH); + } + + return rc; +} + +/* + * Put the message to the request queue. + * + * Return 0 if OK; -1 otherwise + */ +static int putRequestMessage(MQHCONN hConn, MQHOBJ hObj, char *msg) { + MQLONG compCode; + MQLONG reason; + int rc = 0; + MQMD mqmd = {MQMD_DEFAULT}; + MQPMO mqpmo = {MQPMO_DEFAULT}; + + // Options to control how the message is put + mqpmo.Options = MQPMO_FAIL_IF_QUIESCING; + mqpmo.Options |= MQPMO_NO_SYNCPOINT; + mqpmo.Options |= MQPMO_NEW_MSG_ID; + + // We are sending a character string. Set the format to indicate + // that, so that the recipient can convert the codepage if necessary + memcpy(mqmd.Format, MQFMT_STRING, MQ_FORMAT_LENGTH); + + strncpy(mqmd.ReplyToQ, replyToQ, MQ_Q_NAME_LENGTH); + strncpy(mqmd.ReplyToQMgr, replyToQMgr, MQ_Q_MGR_NAME_LENGTH); + mqmd.MsgType = MQMT_REQUEST; + + // We are using a temporary dynamic queue for the reply, which + // can only accept non-persistent messages. So we force that on + // the request message, expecting that the responder will copy it into the reply. + mqmd.Persistence = MQPER_NOT_PERSISTENT; + + // Put the message to the queue + MQPUT(hConn, hObj, &mqmd, &mqpmo, strlen(msg), msg, &compCode, &reason); + memcpy(msgId, mqmd.MsgId, MQ_MSG_ID_LENGTH); + if (reason != MQRC_NONE) { + printError("MQPUT", compCode, reason); + rc = -1; + } + + return rc; +} + +/* + * Wait for the response message to appear, and retrieve it + * Return 0 if OK; -1 otherwise + */ +static int getReplyMessage(MQHCONN hConn, MQHOBJ hObj) { + + MQLONG compCode; + MQLONG reason; + int rc = 0; + MQMD mqmd = {MQMD_DEFAULT}; + MQGMO mqgmo = {MQGMO_DEFAULT}; + char buffer[DEFAULT_BUFFER_LENGTH]; + MQLONG datalength; + MQLONG waitInterval = DEFAULT_WAIT_INTERVAL; + + + // Structure version must be high enough to recognise the MatchOptions field + mqgmo.Version = MQGMO_VERSION_2; + + // Options to control retrieval + mqgmo.Options = MQGMO_NO_SYNCPOINT; + mqgmo.Options |= MQGMO_FAIL_IF_QUIESCING; + mqgmo.Options |= MQGMO_WAIT; + mqgmo.Options |= MQGMO_CONVERT; + + mqgmo.Options |= MQGMO_ACCEPT_TRUNCATED_MSG; // Process the message even if it is too long for the buffer + + if (mqEndpoints[0].waitInterval) { + waitInterval = atoi(mqEndpoints[0].waitInterval); + } + + mqgmo.WaitInterval = waitInterval * 1000; // Convert seconds to milliseconds + + // Wait for a message that matches the original request. Default behaviour for a responder is to copy + // the original MsgId into the CorrelId, so we put that into the MQGET options + mqgmo.MatchOptions = MQMO_MATCH_CORREL_ID; + memcpy(mqmd.CorrelId, msgId, MQ_CORREL_ID_LENGTH); + + MQGET(hConn, hObj, &mqmd, &mqgmo, sizeof(buffer), buffer, &datalength, &compCode, &reason); + + if (reason == MQRC_NONE) { + if (!strncmp(mqmd.Format, MQFMT_STRING, MQ_FORMAT_LENGTH)) { + printf("Reply Message: %*.*s\n", datalength, datalength, buffer); + } else { + char title[32]; + sprintf(title, "Reply Message Type:%*.*s", MQ_FORMAT_LENGTH, MQ_FORMAT_LENGTH, mqmd.Format); + dumpHex(title, buffer, datalength); + } + } else { + printError("MQGET", compCode, reason); + rc = -1; + } + + return rc; +} diff --git a/C/sampleresponse.c b/C/sampleresponse.c new file mode 100644 index 00000000..462c837b --- /dev/null +++ b/C/sampleresponse.c @@ -0,0 +1,252 @@ +/** + * Copyright 2024 IBM Corp. + * + * Licensed under the Apache License, Version 2.0 (the 'License'); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + **/ + +/* This is part of a demonstration showing a common request/reply pattern + * using the C MQI. + * + * This program waits for a request message and then sends a reply. + * + */ + +#include +#include +#include +#include + +#include + +#include "common.h" +#include "config.h" + +static int openQueue(MQHCONN hConn, PMQHOBJ pHObj); +static int processRequests(MQHCONN, MQHOBJ); + +#define DEFAULT_WAIT_INTERVAL 10 // seconds to wait for a new request + +// The only (optional) parameter to this program is the name of the configuration file +int main(int argc, char **argv) { + int rc = 0; + time_t now; + MQHCONN hConn = MQHC_UNUSABLE_HCONN; + MQHOBJ hObj = MQHO_UNUSABLE_HOBJ; + + if (getenv(CONFIG_DEBUG)) { + debug = 1; + } + + char *configFile = getenv(CONFIG_JSON_FILE); + if (argc > 1) { + configFile = argv[1]; + } + if (!configFile) { + configFile = DEFAULT_CONFIG_FILE; + } + + printf("Starting up Application: %s\n", argv[0]); + rc = parseConfig(configFile); + if (rc == 0) { + rc = connectQMgr(&hConn); + } + + if (rc == 0) { + rc = openQueue(hConn, &hObj); + } + + if (rc == 0) { + rc = processRequests(hConn, hObj); + } + + if (hObj != MQHO_UNUSABLE_HOBJ) { + closeQueue(hConn, &hObj); + } + + if (hConn != MQHC_UNUSABLE_HCONN) { + disconnectQMgr(&hConn); + } + + printf("\nDone. Exit code:%d\n", rc); + exit(rc); +} + +/* + * Open a queue for INPUT as we will be getting a message + * + * Return 0 if OK; -1 otherwise + */ +static int openQueue(MQHCONN hConn, PMQHOBJ pHObj) { + MQLONG compCode; + MQLONG reason; + int rc = 0; + + MQOD mqod = {MQOD_DEFAULT}; + MQLONG options = MQOO_INPUT_EXCLUSIVE; + + strncpy(mqod.ObjectName, mqEndpoints[0].queueName, MQ_Q_NAME_LENGTH); + mqod.ObjectType = MQOT_Q; + + MQOPEN(hConn, &mqod, options, pHObj, &compCode, &reason); + + if (reason != MQRC_NONE) { + printError("MQOPEN", compCode, reason); + rc = -1; + } + + return rc; +} + +/* + * Read messages from the designated input queue. + * + * Do some processing on the message contents, and send a reply. + * Use MQPUT1 for the reply operation, as that can be more efficient + * than MQOPEN/MQPUT/MQCLOSE when the reply queue changes frequently: a + * common situation with the request/response pattern. + * + * Various fields are copied from the input message descriptor to the reply + * based on Report options that have been requested. + * + * Return 0 if OK; -1 otherwise + */ +static int processRequests(MQHCONN hConn, MQHOBJ hObj) { + + MQLONG compCode; + MQLONG reason; + int rc = 0; + int ok = 1; + int i; + int msgCount = 0; + MQMD inMqmd = {MQMD_DEFAULT}; + MQMD outMqmd = {MQMD_DEFAULT}; + MQLONG waitInterval = DEFAULT_WAIT_INTERVAL; + + MQGMO mqgmo = {MQGMO_DEFAULT}; + MQPMO mqpmo = {MQPMO_DEFAULT}; + MQOD mqod = {MQOD_DEFAULT}; + char inBuffer[DEFAULT_BUFFER_LENGTH]; + char outBuffer[DEFAULT_BUFFER_LENGTH]; + + MQLONG datalength; + + // Structure version must be high enough to recognise the MatchOptions field + mqgmo.Version = MQGMO_VERSION_2; + + // Various options to control retrieval + mqgmo.Options = MQGMO_NO_SYNCPOINT; + mqgmo.Options |= MQGMO_FAIL_IF_QUIESCING; + mqgmo.Options |= MQGMO_WAIT; + mqgmo.Options |= MQGMO_CONVERT; + + mqgmo.Options |= MQGMO_ACCEPT_TRUNCATED_MSG; // Process the message even if it is too long for the buffer + + if (mqEndpoints[0].waitInterval) { + waitInterval = atoi(mqEndpoints[0].waitInterval); + } + + mqgmo.WaitInterval = waitInterval * 1000; // Convert seconds to milliseconds + + // Not going to try to match on MsgId or CorrelId + mqgmo.MatchOptions = MQMO_NONE; + + // Loop until there are no more messages on the queue + while (ok) { + + MQGET(hConn, hObj, &inMqmd, &mqgmo, sizeof(inBuffer), inBuffer, &datalength, &compCode, &reason); + + if (reason != MQRC_NONE) { + printError("MQGET", compCode, reason); + } + + if (reason == MQRC_NONE || reason == MQRC_TRUNCATED_MSG_ACCEPTED) { + + msgCount++; + // Print the request message + if (!strncmp(inMqmd.Format, MQFMT_STRING, MQ_FORMAT_LENGTH)) { + printf("Request Message: %*.*s\n", datalength, datalength, inBuffer); + } else { + char title[32]; + sprintf(title, "Request Message Type:%*.*s", MQ_FORMAT_LENGTH, MQ_FORMAT_LENGTH, inMqmd.Format); + dumpHex(title, inBuffer, datalength); + } + + // Reverse the contents of the inbound message + memset(outBuffer, 0, sizeof(outBuffer)); + for (i = 0; i < datalength; i++) { + outBuffer[i] = inBuffer[datalength - i - 1]; + } + + // Set up the ReplyTo information + strncpy(mqod.ObjectName, inMqmd.ReplyToQ, MQ_Q_NAME_LENGTH); + strncpy(mqod.ObjectQMgrName, inMqmd.ReplyToQMgr, MQ_Q_MGR_NAME_LENGTH); + mqod.ObjectType = MQOT_Q; + + // Options to control how the message is put + mqpmo.Options = MQPMO_FAIL_IF_QUIESCING; + mqpmo.Options |= MQPMO_NO_SYNCPOINT; + + // Extract the report options that tell us how to construct the reply message's correlators. + // This value is a bit-field so we can use bitwise operations to test it. + MQLONG ro = inMqmd.Report & (MQRO_COPY_MSG_ID_TO_CORREL_ID | MQRO_PASS_MSG_ID | MQRO_PASS_CORREL_ID | MQRO_NEW_MSG_ID); + + // The default behaviour is to copy the inbound MsgId into the outbound CorrelId and create a new MsgId + if (ro & MQRO_COPY_MSG_ID_TO_CORREL_ID || ro & MQRO_NEW_MSG_ID || ro == 0) { + memcpy(outMqmd.CorrelId, inMqmd.MsgId, MQ_CORREL_ID_LENGTH); + mqpmo.Options |= MQPMO_NEW_MSG_ID; + } + // But there are options to allow a direct return of the MsgId and/or CorrelId + if (ro & MQRO_PASS_MSG_ID) { + memcpy(outMqmd.MsgId, inMqmd.MsgId, MQ_MSG_ID_LENGTH); + } + if (ro & MQRO_PASS_CORREL_ID) { + memcpy(outMqmd.CorrelId, inMqmd.CorrelId, MQ_CORREL_ID_LENGTH); + } + + // Also set report options that should be inherited + if (inMqmd.Report & MQRO_PASS_DISCARD_AND_EXPIRY) { + outMqmd.Expiry = inMqmd.Expiry; + if (inMqmd.Report & MQRO_DISCARD_MSG) { + outMqmd.Report = MQRO_DISCARD_MSG; + } else { + outMqmd.Report = MQRO_NONE; + } + } else { + outMqmd.Report = MQRO_NONE; + } + + // Set the reply message to be the same format and persistence as input + memcpy(outMqmd.Format, inMqmd.Format, MQ_FORMAT_LENGTH); + outMqmd.MsgType = MQMT_REPLY; + outMqmd.Persistence = inMqmd.Persistence; + + // Send the reply, using MQPUT1 + MQPUT1(hConn, &mqod, &outMqmd, &mqpmo, datalength, outBuffer, &compCode, &reason); + if (reason != MQRC_NONE) { + printError("MQPUT1", compCode, reason); + } + } else { + if (reason == MQRC_NO_MSG_AVAILABLE) { + // This is not really an error but we do need to break from the loop + ok = 0; + } else { + rc = -1; + ok = 0; + } + } + } + + printf("Requests processed: %d\n",msgCount); + + return rc; +} diff --git a/C/samplesubscribe.c b/C/samplesubscribe.c new file mode 100644 index 00000000..4086bff0 --- /dev/null +++ b/C/samplesubscribe.c @@ -0,0 +1,200 @@ +/** + * Copyright 2024 IBM Corp. + * + * Licensed under the Apache License, Version 2.0 (the 'License'); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + **/ + +/* This is a demonstration showing the GET operations from an MQ Queue + * using the MQI C interface. + */ + +#include +#include +#include +#include + +#include + +#include "common.h" +#include "config.h" + +static int subscribeTopic(MQHCONN hConn, PMQHOBJ pTopicHObj, PMQHOBJ pQueueHObj); +static int getMessages(MQHCONN hConn, MQHOBJ hObj); + +#define DEFAULT_WAIT_INTERVAL 10 // Seconds to wait for more publications + +// The only (optional) parameter to this program is the name of the configuration file +int main(int argc, char **argv) { + int rc = 0; + time_t now; + MQHCONN hConn = MQHC_UNUSABLE_HCONN; + MQHOBJ topicHObj = MQHO_UNUSABLE_HOBJ; + MQHOBJ queueHObj = MQHO_UNUSABLE_HOBJ; + + if (getenv(CONFIG_DEBUG)) { + debug = 1; + } + + char *configFile = getenv(CONFIG_JSON_FILE); + if (argc > 1) { + configFile = argv[1]; + } + if (!configFile) { + configFile = DEFAULT_CONFIG_FILE; + } + + printf("Starting up Application: %s\n", argv[0]); + rc = parseConfig(configFile); + if (rc == 0) { + rc = connectQMgr(&hConn); + } + + if (rc == 0) { + rc = subscribeTopic(hConn, &topicHObj, &queueHObj); + } + if (rc == 0) { + rc = getMessages(hConn, queueHObj); + } + + if (queueHObj != MQHO_UNUSABLE_HOBJ) { + closeQueue(hConn, &queueHObj); + } + + if (topicHObj != MQHO_UNUSABLE_HOBJ) { + closeQueue(hConn, &topicHObj); + } + + if (hConn != MQHC_UNUSABLE_HCONN) { + disconnectQMgr(&hConn); + } + + printf("\nDone. Exit code:%d\n", rc); + exit(rc); +} + +/* + * Subscribe to a topic + * + * Return 0 if OK; -1 otherwise + */ +static int subscribeTopic(MQHCONN hConn, PMQHOBJ pTopicHObj, PMQHOBJ pQueueHObj) { + MQLONG compCode; + MQLONG reason; + MQCHARV objectString = {MQCHARV_DEFAULT}; + char topic[1024] = {0}; + + int rc = 0; + + MQSD mqsd = {MQSD_DEFAULT}; + + mqsd.Options = MQSO_CREATE | MQSO_MANAGED; + mqsd.Options |= MQSO_NON_DURABLE; + mqsd.Options |= MQSO_FAIL_IF_QUIESCING; + + // The objectString contains the full topic string that we + // want to use. Alternatives allow a combination of administered object + // names and the objectString value, but that's more advanced. + strncpy(topic, mqEndpoints[0].topicName, sizeof(topic) - 1); + printf("Subscribing to %s\n", topic); + + objectString.VSPtr = topic; + objectString.VSLength = (MQLONG)strlen(topic); + mqsd.ObjectString = objectString; + + // Using the MANAGED option tells the queue manager to create a dynamic + // queue from which we can read the messages. A handle to that queue + // is returned from MQSUB, and we then use that for MQGET. + MQSUB(hConn, &mqsd, pQueueHObj, pTopicHObj, &compCode, &reason); + + if (reason != MQRC_NONE) { + printError("MQSUB", compCode, reason); + rc = -1; + } + + return rc; +} + +/* + * Get all available messages from the queue, printing the contents. + * Return 0 if OK; -1 otherwise + */ +static int getMessages(MQHCONN hConn, MQHOBJ hObj) { + + MQLONG compCode; + MQLONG reason; + int rc = 0; + int ok = 1; + MQMD mqmd = {MQMD_DEFAULT}; + MQGMO mqgmo = {MQGMO_DEFAULT}; + char buffer[DEFAULT_BUFFER_LENGTH]; + MQLONG datalength; + MQLONG waitInterval = DEFAULT_WAIT_INTERVAL; + + int msgCount = 0; + + // Structure version must be high enough to recognise the MatchOptions field + mqgmo.Version = MQGMO_VERSION_2; + + // Various options to control retrieval + mqgmo.Options = MQGMO_NO_SYNCPOINT; + mqgmo.Options |= MQGMO_FAIL_IF_QUIESCING; + mqgmo.Options |= MQGMO_WAIT; + mqgmo.Options |= MQGMO_CONVERT; + + mqgmo.Options |= MQGMO_ACCEPT_TRUNCATED_MSG; // Process the message even if it is too long for the buffer + + if (mqEndpoints[0].waitInterval) { + waitInterval = atoi(mqEndpoints[0].waitInterval); + } + printf("waitInterval: %d\n",waitInterval); + mqgmo.WaitInterval = waitInterval * 1000; // Convert seconds to milliseconds + + // Not going to try to match on MsgId or CorrelId + mqgmo.MatchOptions = MQMO_NONE; + + // Loop until there are no more messages on the queue + while (ok) { + + MQGET(hConn, hObj, &mqmd, &mqgmo, sizeof(buffer), buffer, &datalength, &compCode, &reason); + + if (reason == MQRC_NONE) { + msgCount++; + if (!strncmp(mqmd.Format, MQFMT_STRING, MQ_FORMAT_LENGTH)) { + printf("Rcvd Message: %*.*s\n", datalength, datalength, buffer); + } else { + char title[32]; + sprintf(title, "Rcvd Message Type:%*.*s", MQ_FORMAT_LENGTH, MQ_FORMAT_LENGTH, mqmd.Format); + dumpHex(title, buffer, datalength); + } + } else { + printError("MQGET", compCode, reason); + switch (reason) { + case MQRC_NO_MSG_AVAILABLE: + // This is not really an error but we do need to break from the loop + ok = 0; + break; + case MQRC_TRUNCATED_MSG_ACCEPTED: + msgCount++; + // Carry on if there are more messages + break; + default: + rc = -1; + ok = 0; + break; + } + } + } + + printf("\nMessages read: %d\n", msgCount); + return rc; +} diff --git a/C/win_bld.bat b/C/win_bld.bat new file mode 100644 index 00000000..1e07a770 --- /dev/null +++ b/C/win_bld.bat @@ -0,0 +1,11 @@ + +set MQ_FILE_PATH=c:\Program Files\IBM\MQ + +cl -MD /Fesampleget.exe common.c config.c sampleget.c "%MQ_FILE_PATH%\Tools\Lib64\mqm.lib" +cl -MD /Fesampleput.exe common.c config.c sampleput.c "%MQ_FILE_PATH%\Tools\Lib64\mqm.lib" + +cl -MD /Fesamplepublish.exe common.c config.c samplepublish.c "%MQ_FILE_PATH%\Tools\Lib64\mqm.lib" +cl -MD /Fesamplesubscribe.exe common.c config.c samplesubscribe.c "%MQ_FILE_PATH%\Tools\Lib64\mqm.lib" + +cl -MD /Fesamplerequest.exe common.c config.c samplerequest.c "%MQ_FILE_PATH%\Tools\Lib64\mqm.lib" +cl -MD /Fesampleresponse.exe common.c config.c sampleresponse.c "%MQ_FILE_PATH%\Tools\Lib64\mqm.lib" diff --git a/C/win_dev.bat b/C/win_dev.bat new file mode 100644 index 00000000..2d0bf1a2 --- /dev/null +++ b/C/win_dev.bat @@ -0,0 +1,3 @@ +rem This sets the environment for Visual Studio 2022 +rem Set up for a 64-bit program +"C:\Program Files\Microsoft Visual Studio\2022\Professional\VC\Auxiliary\Build"\vcvarsall x64 diff --git a/README.md b/README.md index e20c55eb..82853c4a 100644 --- a/README.md +++ b/README.md @@ -1,24 +1,31 @@ -## Issue Support +## Issue Support -The code in this repository, is provided and maintained on a community basis, and is not covered by any IBM commercial support agreement or warranty. +The code in this repository, is provided and maintained on a community basis, and is not covered by any IBM commercial +support agreement or warranty. For issues and fixes please just raise an issue against this repository. -## IBM MQ samples and patterns +## IBM MQ samples and patterns -When your application needs messaging, you don't want to spend countless hours learning the basics, you want to jump straight in and play, see how things work. +When your application needs messaging, you don't want to spend countless hours learning the basics, you want to jump +straight in and play, see how things work. -We have taken parts that make up the current set of our IBM MQ samples and built applications that you can use to do just that. +We have taken parts that make up the current set of our IBM MQ samples and built applications that you can use to do +just that. -You'll find `put/get`, `pub/sub`, `request/response` samples that you can run in the same language or you can try mixing things up and do a `put` in Java, `get` with Go etc. +You'll find `put/get`, `pub/sub`, `request/response` samples that you can run in the same language or you can try mixing +things up and do a `put` in Java, `get` with Go etc. There is a [README for each language](#readme-docs) that helps you with the initial setup. -You need an MQ server with a queue or topic to run these samples against. To find out more about what MQ is and how it works, start from [LearnMQ](http://ibm.biz/mq-badge). +You need an MQ server with a queue or topic to run these samples against. To find out more about what MQ is and how it +works, start from [LearnMQ](http://ibm.biz/mq-badge). To get your MQ server set up, check out [Ready, Set, Connect](https://developer.ibm.com/series/mq-ready-set-connect/). -You can use your own MQ server, you'll just have to adjust the MQ objects accordingly so they match on both the server and the client side. The samples in this repository inspect the following environment variables for their Queue Manager and messaging configuration. These values can also be provided via a `env.json` file. +You can use your own MQ server, you'll just have to adjust the MQ objects accordingly so they match on both the server +and the client side. The samples in this repository inspect the following environment variables for their Queue Manager +and messaging configuration. These values can also be provided via a `env.json` file. * **HOST** - Host name or IP address of your queue manager * **PORT** - Listener port for your queue manager @@ -30,15 +37,13 @@ You can use your own MQ server, you'll just have to adjust the MQ objects accord * **TOPIC_NAME** - Topic for `publish/subscribe` * **MODEL_QUEUE_NAME** - Model Queue used as template to base dynamic queues on for `request/response` * **DYNAMIC_QUEUE_PREFIX** - Prefix for dynamically created reply queue - you don't need to create this -* **CIPHER_SPEC** - If present in the `env,json`, TLS Cipher specification to use +* **CIPHER_SUITE** - If present in the `env,json`, TLS Cipher specification to use * **KEY_REPOSITORY** - Path to the `keystore` `.kbd` and `.sth` files. If running on Apple Silicon then this will the path to the queue manager's exported `.pem`. If present in the `env.json`, TLS is enabled - this is on the app side. -If instead you choose to provide a client channel definition table (CCDT) file, -then the **Host**, **PORT**, **Channel** and **Cipher** are provided by the -CCDT and you can leave them out of the `env.json`. All the samples check if the -environment variable `MQCCDTURL` is set and that the file it is pointing at exists. -If it does then the logic sets the connection definition or connection factory to -for CCDT processing. For example +If instead you choose to provide a client channel definition table (CCDT) file, then the **Host**, **PORT**, **Channel** +and **Cipher** are provided by the CCDT and you can leave them out of the `env.json`. All the samples check if the +environment variable `MQCCDTURL` is set and that the file it is pointing at exists. If it does then the logic sets the +connection definition or connection factory to for CCDT processing. For example ``` export MQCCDTURL=file:///Users/xyz/Documents/dev/mqsamples/mq-dev-patterns/ccdt.json @@ -46,8 +51,8 @@ export MQCCDTURL=file:///Users/xyz/Documents/dev/mqsamples/mq-dev-patterns/ccdt. We give you a sample `ccdt.json` from which you can base your own. - -If you use our MQ server for developers in Docker, Linux or Windows, with the default config, you'll have the following MQ objects on the MQ server; +If you use our MQ server for developers in Docker, Linux or Windows, with the default config, you'll have the following +MQ objects on the MQ server: ~~~Text Host - localhost @@ -65,15 +70,23 @@ Cipher suite - TLS_RSA_WITH_AES_128_CBC_SHA256 ### Model queue and MQ container image The default configuration for MQ objects that you get with our Docker container does not include a model queue. -We use a model queue in the `request/response` pattern as a template for the request application to create a temporary reply queue. +We use a model queue in the `request/response` pattern as a template for the request application to create a temporary +reply queue. -You can use the MQ Web Console to create the model queue. Access the MQ Web Console for your MQ running as a container at [https://localhost:9443/ibmmq/console/](https://localhost:9443/ibmmq/console/). You can log in with the [default admin details](https://github.com/ibm-messaging/mq-container/blob/4d4051312eb9d95a086e2ead76482d1f1616d149/docs/developer-config.md#web-console) or your own, if you made changes. +You can use the MQ Web Console to create the model queue. Access the MQ Web Console for your MQ running as a container +at [https://localhost:9443/ibmmq/console/](https://localhost:9443/ibmmq/console/). You can log in with the +[default admin details](https://github.com/ibm-messaging/mq-container/blob/4d4051312eb9d95a086e2ead76482d1f1616d149/docs/developer-config.md#web-console) +or your own, if you made changes. ## Environment variables -All the samples make use of the same environment variables to define MQ connection settings - these match the default developer config objects on the MQ server. This separates configuration and credentials details from the code, and conforms to standard configuration injection mechanisms used in Cloud and DevOps environments. +All the samples make use of the same environment variables to define MQ connection settings - these match the default +developer config objects on the MQ server. This separates configuration and credentials details from the code, and +conforms to standard configuration injection mechanisms used in Cloud and DevOps environments. -With so many many environment variables needed, we've tried to make this easier by providing one `env.json` file in the main `samples` directory, that all the samples use for default configuration setting. Default values specfied in the `env.json` file are overridden by envrionment variables. +With so many many environment variables needed, we've tried to make this easier by providing one `env.json` file in the +main `samples` directory, that all the samples use for default configuration setting. Default values specfied in the +`env.json` file are overridden by environment variables. ### env.json format @@ -96,44 +109,49 @@ With so many many environment variables needed, we've tried to make this easier ``` ### IBM Z Xplore -If you are running these samples on IBM Z Xplore then you can use the -`env-zbindings.json` file. Simply rename the `env-zbindings.json` to -`env.json` +If you are running these samples on IBM Z Xplore then you can use the `env-zbindings.json` file. Simply rename the +`env-zbindings.json` to `env.json` You can use the `env.json` file to 'switch on' or 'switch off' parts of the code. ### Endpoints Array -Having the endpoints in the `env.json` defined as an array allows us to define multiple endpoints -for the sample applications to use. +Having the endpoints in the `env.json` defined as an array allows us to define multiple endpoints for the sample +applications to use. ## TLS -For example, removing the CIPHER_SUITE and KEY_REPOSITORY lines (don't forget to remove the comma from the last line in the json) -will mean the sample will connect without using TLS. +For example, removing the CIPHER_SUITE and KEY_REPOSITORY lines (don't forget to remove the comma from the last line in +the json) will mean the sample will connect without using TLS. -If you have two docker containers, one with TLS and one without, changing the port number in the `env.json` allows you to switch between them. +If you have two docker containers, one with TLS and one without, changing the port number in the `env.json` allows you +to switch between them. ### Apple Silicon -The IBM MQ Client Toolkit on Apple Silicon (ARM64) makes use of OpenSSL libraries -for TLS. For the MQI based samples in this repository, this means that -`KEY_REPOSITORY` in the `env.json` file or environment -variable `KEY_REPOSITORY` be set to the path for the queue manager's exported `.pem` file. eg. If you have exported the `qmgrcert.pem` file to the root directory of this repository, then set `KEY_REPOSITORY` to `../qmgrcert.pem` . +The IBM MQ Client Toolkit on Apple Silicon (ARM64) made use of OpenSSL libraries for TLS before version 9.4.1. For the +MQI based samples in this repository, this means that `KEY_REPOSITORY` in the `env.json` file or environment variable +`KEY_REPOSITORY` be set to the path for the queue manager's exported `.pem` file. eg. If you have exported the +`qmgrcert.pem` file to the root directory of this repository, then set `KEY_REPOSITORY` to `../qmgrcert.pem`. +From 9.4.1, the toolkit uses the same keystore formats (`p12` or `kdb`) as Linux. ### Using port forwarding to run a multiple containers -Let's say you're already running MQ in a Docker container without TLS having set it up by following the [Ready, Set, Connect](https://developer.ibm.com/series/mq-ready-set-connect/) tutorial. +Let's say you're already running MQ in a Docker container without TLS having set it up by following the +[Ready, Set, Connect](https://developer.ibm.com/series/mq-ready-set-connect/) tutorial. Now you want to run the second Docker container to try the samples with TLS switched on. -You can have the same MQ objects set up in both, and switch between them by using the host port forwarding to make the non TLS queue manager available on port 1414 and the TLS one on port 1415. +You can have the same MQ objects set up in both, and switch between them by using the host port forwarding to make the +non TLS queue manager available on port 1414 and the TLS one on port 1415. ### Creating self signed certificates by using `openssl` -Do this in a directory you'll easily remember as you'll have to copy the server certificates over into a temporary folder each time you need to run MQ with TLS in a Docker container. +Do this in a directory you'll easily remember as you'll have to copy the server certificates over into a temporary +folder each time you need to run MQ with TLS in a Docker container. -You'll also have to point to the client keystore location from the `env.json` file so that if you want to run samples with TLS, the sample knows where to look. +You'll also have to point to the client keystore location from the `env.json` file so that if you want to run samples +with TLS, the sample knows where to look. 1. Create a new directory. Navigate inside this and generate a self-signed server key and certificate @@ -151,31 +169,43 @@ You'll also have to point to the client keystore location from the `env.json` fi 3. Create a client keystore. - - For **JMS and XMS** based clients, create a .jks client keystore and create and verify a keystore password: +- For **JMS and XMS** based clients, create a .jks client keystore and create and verify a keystore password: `keytool -keystore clientkey.jks -storetype jks -importcert -file key.crt -alias server-certificate` - - For MQI based Clients (**Node, Python, Go**) +- For MQI based Clients (**Node, Python, Go**) - Create a key database, a stash file. You will need to have installed the MQI client, so that you can run the runmqakm tool: + Create a key database, a stash file. You will need to have installed the MQI client, so that you can run the + runmqakm tool: `runmqakm -keydb -create -db clientkey.kdb -pw tru5tpassw0rd -type pkcs12 -expire 1000 -stash` - Import the server's public key certificate into the client key database +- Import the server's public key certificate into the client key database `runmqakm -cert -add -label QM1.cert -db clientkey.kdb -pw tru5tpassw0rd -trust enable -file key.crt` 4. Move the client keystore -- Move the client keystore somewhere you will remember. Ensure the only files in the current directory are the `key.key` and `key.crt` files, as IBM MQ will use the contents of this directory to configure security inside the container. +- Move the client keystore somewhere you will remember. Ensure the only files in the current directory are the `key.key` + and `key.crt` files, as IBM MQ will use the contents of this directory to configure security inside the container. 5. Run the new docker container - Give it a name, for example `mqtls` so you can differentiate it from your other MQ container when you `docker ps`, and point it at the location where you copied the server certificate. +- Give it a name, for example `mqtls` so you can differentiate it from your other MQ container when you `docker ps`, + and point it at the location where you copied the server certificate. - `docker run --name mqtls --env LICENSE=accept --env MQ_QMGR_NAME=QM1 --volume ___PATH TO SERVER_KEY/CERTIFICATE DIRECTORY___:/etc/mqm/pki/keys/mykey --publish 1415:1414 --publish 9444:9443 --detach --env MQ_APP_PASSWORD=passw0rd icr.io/ibm-messaging/mq:latest` +``` + docker run --name mqtls --env LICENSE=accept \ + --env MQ_QMGR_NAME=QM1 \ + --env MQ_APP_PASSWORD=passw0rd \ + --volume ___PATH TO SERVER_KEY/CERTIFICATE DIRECTORY___:/etc/mqm/pki/keys/mykey \ + --publish 1415:1414 \ + --publish 9444:9443 \ + --detach \ + icr.io/ibm-messaging/mq:latest +``` - Remember to use a secure password for `MQ_APP_PASSWORD`. +- Remember to use a secure password for `MQ_APP_PASSWORD`. You should be able to open the MQ Web console for this TLS container on https://localhost:9444/ibmmq/console. @@ -190,24 +220,14 @@ then run to start the container again. ## MQI Paths -The MQI samples; `Node.js`, `Python`, `Go`, require the MQI Client Toolkit to have been -installed and the paths - -`MQ_INSTALLATION_PATH` and - -`DYLD_LIBRARY_PATH` (MacOS) or `LD_LIBRARY_PATH` (Windows or Linux) set. - -If you have installed the MQI client manually, ensure that - -`MQ_INSTALLATION_PATH` is set to the root directory of your MQI Client installation and - -`DYLD_LIBRARY_PATH` or `LD_LIBRARY_PATH` is set to `$MQ_INSTALLATION_PATH\lib64`. - -Do not install any application requirements until +The MQI samples; `Node.js`, `Python`, `Go`, require the MQI Client Toolkit to have been installed and the paths +`MQ_INSTALLATION_PATH` and `DYLD_LIBRARY_PATH` (MacOS) or `LD_LIBRARY_PATH` (Windows or Linux) set. -`MQ_INSTALLATION_PATH` and +If you have installed the MQI client manually, ensure that `MQ_INSTALLATION_PATH` is set to the root directory of your +MQI Client installation and `DYLD_LIBRARY_PATH` or `LD_LIBRARY_PATH` is set to `$MQ_INSTALLATION_PATH/lib64`. -`DYLD_LIBRARY_PATH` or `LD_LIBRARY_PATH` are set and exported (see language README docs for more info). +Do not install any application requirements until `MQ_INSTALLATION_PATH` and `DYLD_LIBRARY_PATH` or `LD_LIBRARY_PATH` +are set and exported (see language README docs for more info). ## README docs @@ -217,6 +237,7 @@ Do not install any application requirements until #### [Python](/Python/README.md) #### [C# .Net](/dotnet/README.md) #### [Go](/Go/README.md) +#### [C](/C/README.md) ### REST samples #### [Rust](/Rust-REST/README.md)