/********************************************************************************/ /* */ /* -------------------------- */ /*------------------------- Enhanced FQPSK Modulator ---------------------------*/ /* -------------------------- */ /* */ /* Author: Mark J. Foster */ /* Last Updated: 11/18/03 */ /* */ /********************************************************************************/ /********************************************************************************/ /* This module implements an efficient _Enhanced_ Feher-patented Quadrature- */ /* Phase-Shift Keying (EFQPSK) modulator, as invented by M.K. Simon and */ /* T.-Y. Yan, in their seminal paper: */ /* */ /* "A Performance Evaluation and Interpretation of Unfiltered */ /* Feher-Patented Quadrature-Phase-Shift Keying (FQPSK) [1999] */ /* */ /* This paper is available online from NASA at the following URL: */ /* http://dsp.jpl.nasa.gov/members/yan/137C.pdf */ /* */ /* READ THIS EXCEPTIONAL PAPER, for it precisely describes the fundamental */ /* math behind EFQPSK, as well as recommending efficient implementation */ /* techniques. Furthermore, the clear and approachable writing style makes */ /* it well worth the time - HIGHLY recommended! */ /* */ /* ---------------- */ /* */ /* QPSK works by transmitting 2 bits per symbol, by utilizing "quadrature" */ /* modulation, where two independent waveforms are transmitted simultaneously */ /* on the same carrier, but they are 90 degrees out of phase with one another. */ /* Thanks to the mysteries of math, this is a "free lunch", since the two */ /* waveforms do not interfere with one another, thus enabling two bits to be */ /* sent in the same bandwidth as 1 bit! This is basic Quadrature-Phase-Shift */ /* Keying, or QPSK. The problem with QPSK is that it requires linear transmit */ /* and receive circuitry, which adds cost, and significantly reduces battery */ /* life. Without linear amplifiers, QPSK's non-constant signal envelope leads */ /* to significant bandwidth "splattering" as the signal is amplified, in a */ /* process known as spectral regrowth. In QPSK and its variants, the two */ /* separate waveforms are known as I & Q, and it is common terminology to refer */ /* to I & Q as though they were plotted on an X-Y chart as the I and Q "axes". */ /* With quadrature modulation, even bits are typically sent via the I waveform, */ /* and odd bits are typically sent via the Q waveform. */ /* */ /* The next refinement is patented by Kamilo Feher, and is Feher-patented QPSK, */ /* or FQPSK. In this approach, Feher realized that by correlating the I & Q */ /* waveforms, that is, by examining the bit transitions on both the I & Q */ /* channels, it was possible to create a constant-envelope signal that would */ /* not exhibit the spectral regrowth present in QPSK. Feher went on to develop */ /* a much more bandwidth-efficient scheme known as FQPSK-B (for Revision B), */ /* but it uses a proprietary filter following the I & Q D/A converters, and */ /* the filter coefficients are apparently only available under an NDA and */ /* payed-up license. Since this is an open source project, we'll skip FQPSK-B, */ /* despite its very attractive bandwidth efficiency. */ /* */ /* The final refinement in FQPSK is Enhanced FQPSK as invented by Simon and Yan.*/ /* They completely recast Feher's approach mathematically from half-symbol */ /* encoding to full-symbol encoding, and discovered in the process that the */ /* waveforms used by Feher could be improved to eliminate bandwidth-increasing */ /* signal slope discontinuities. They, like Feher, send one of a set of */ /* predetermined waveform segments on both the I & Q signals, but the waveforms */ /* they selected are significantly "cleaner" than Feher's. In addition, they */ /* also derived a very efficient method of selecting which of the 16 possible */ /* waveshapes (wavelets) would be transmitted on the I & Q axes during each */ /* symbol interval. */ /* */ /* As a consequence of the efficiency of this approach, once the waveform */ /* lookup tables have been constructed, the EFQPSK modulator below requires */ /* just _10_ active lines of C code per symbol! Now, while an EFQPSK receiver */ /* will be considerably more complex, EFQPSK is indeed quite efficient. */ /* */ /* THANKS AGAIN, to M.K. Simon & T.-Y. Yan! */ /* */ /********************************************************************************/ /********************************************************************************/ /* This code is released to the public domain, and may be freely used for any */ /* application, private or commercial. No restrictions are placed upon its */ /* use, but no warranty is provided for its performance, either! */ /********************************************************************************/ #include #include #include #include #include #include "bass.h" /********************************************************************************/ /* V A R I A B L E T Y P E D E F I N I T I O N S */ /********************************************************************************/ typedef unsigned char BYTE; /* Create a BYTE data type */ typedef unsigned short int WORD; /* Define the storage size of a transmitted word */ typedef unsigned short int SAMPLE; /* Define the size of a waveform sample */ /********************************************************************************/ /* C O N S T A N T / M A C R O D E F I N I T I O N S */ /********************************************************************************/ /* * Define the user-changeable constants that quantify this implementation. */ #define A (1.0f/sqrt(2.0f)) /* Define the bandwidth smoothing constant */ #define BITS_WORD 10 /* Number of bits in a communications word */ #define BITS_SAMPLE 16 /* Width of DAC output samples */ #define SAMPLES_WAVEFORM 16 /* Number of samples transmitted per waveform */ #define BITS_SECOND 10000 /* Output data rate in bits/second */ #define SYMBOLS_SECOND (BITS_SECOND/BITS_SYMBOL) /* Output data rate in symbols/second */ #define SAMPLES_SECOND (SYMBOLS_SECOND*SAMPLES_WAVEFORM) /* Output sampling rate */ /* * Define mathematical constants that don't change. */ #define PI 3.14159265f /* Approximate the root of circles & trig */ #define REF_STRING 0x1E24DB /* Reference data stream from the paper */ #define REF_LEN 22 /* Number of bits in the reference string */ #define WAVES 2 /* EFQPSK uses cross-correlated I & Q waves */ #define BITS_SYMBOL WAVES /* I & Q each send one bit per symbol */ #define SYMBOLS_WORD (BITS_WORD/BITS_SYMBOL) /* Number of symbols sent per WORD */ /* * Define constants used by the BASS system. */ #define SAMPLES_SYMBOL (WAVES*SAMPLES_WAVEFORM)/* Count total number of SAMPLEs sent each symbol time */ #define BYTES_SYMBOL (sizeof(SAMPLE)*SAMPLES_SYMBOL) /* Determine bytes output for each symbol */ /* * Define MACROS() that are used in this implementation of EFQPSK */ #define BIT(x) (1<<(x)) /* Macro to return 2^x */ #define BIT_0(x) ((x) & 0x01) /* Macro to isolate bit 0 from a word in LSB */ #define BITS_1_0(x) ((x) & 0x03) /* Macro to isolate 2 LSBs of a word in LSBs */ #define WORD_MASK (BIT(BITS_WORD)-1) /* Define a mask for all the bits in a word */ #define VOLUME (0.25f) /* Set output volume level to 1/4: no clipping */ #define SAMPLE_RANGE ((BIT(BITS_SAMPLE-1)-1)*VOLUME) /* Max +/- range of each SAMPLE output value */ #define WAVE(x) ((SAMPLE)((x)*SAMPLE_RANGE+0.5))/* Macro to emit a DAC SAMPLE from a -1..+1 float */ /* * Define the remapping this program uses for the s(n) waveform table. * Snn refers to the original waveform described in Simon and Yan's paper, * and the definition here is the index used by this program. See * init_efqpsk() below for more details. */ #define EFQPSK_WAVEFORMS 16 /* Number of possible s-sub-n waveforms in EFQPSK */ #define S0 12 /* S0: Constant value from +A to +A */ #define S1 13 /* S1: 1/2 symbol of A, then curves to +1 */ #define S2 14 /* S2: Curves from +1 to +A, 1/2 sym of +A */ #define S3 15 /* S3: Curves from +1 to +A, then +A to +1 */ #define S4 4 /* S4: Curves from -A to +A */ #define S5 5 /* S5: Curves from -A to +1 */ #define S6 6 /* S6: Curves from -1 to +A */ #define S7 7 /* S7: Curves from -1 to +1 */ #define S8 0 /* S8: Constant value from -A to -A */ #define S9 1 /* S9: 1/2 symbol of -A, then curves to -1 */ #define S10 2 /* S10: Curves from -1 to -A, 1/2 sym of -A */ #define S11 3 /* S11: Curves from -1 to -A, then -A to -1 */ #define S12 8 /* S12: Curves from +A to -A */ #define S13 9 /* S13: Curves from +A to -1 */ #define S14 10 /* S14: Curves from +1 to -A */ #define S15 11 /* S15: Curves from +1 to -1 */ /********************************************************************************/ /* W A V E F O R M T A B L E D E F I N I T I O N */ /********************************************************************************/ SAMPLE efqpsk[EFQPSK_WAVEFORMS][SAMPLES_WAVEFORM]; /* Assumes s-sub-n ordering from paper, 32X sampling */ SAMPLE *last_wave = efqpsk[S8]; /* Ptr to 2nd half of Q wave: pipelined to next sym */ SAMPLE *output; /* Global pointer to next output location */ /********************************************************************************/ /* W A V E F O R M T A B L E G E N E R A T O R */ /********************************************************************************/ /********************************************************************************/ /* init_efqpsk: () */ /* */ /* Init_EFQPSK() is called to build the sixteen oversampled waveform */ /* tables that are used in EFQPSK, as specified by the Simon and Yan paper. */ /* For efficiency, this implementation reorders these indices for faster */ /* lookup during modulation. Where Simon & Yan's first (non-lattice) algorithm */ /* specifies that the two most-significant table indices are DeltaI/~I(n-1) */ /* and DeltaQ/~Q(n-1), this implementation uses I(n)/I(n-1) and Q(n)/Q(n-1) */ /* directly, and also assumes bit values of 0 & 1, rather than -1 & +1. */ /* */ /* In the following code, the capital Snn notation refers to the original s(n) */ /* waveforms - the definitions for these constants specify the new table */ /* index for each waveform. In general, the waveforms for EFQPSK contain two */ /* independent components: one for the first half of a symbol, and one for the */ /* second half of a symbol (with the exception of S0, S3, and S7, which have */ /* a constant value through each symbol). */ /* */ /* After precomputing the primary components of the EFQPSK waveforms, */ /* init_efqpsk() builds the waveforms that are constant throughout the symbol */ /* duration, then builds the first half of the first eight waveforms, then */ /* builds the second half of the first eight waveforms. After S0 through S7 */ /* have been constructed, it then sets S8 through S15 to become a negative */ /* version of S0-S7, as specified by the paper. */ /* */ /* Input: */ /* NONE */ /* */ /* Output: */ /* NONE */ /********************************************************************************/ void init_efqpsk() { float sine[SAMPLES_WAVEFORM]; /* Sin() array for one symbol duration */ float cosine[SAMPLES_WAVEFORM]; /* Cos() array for one symbol duration */ float sine2[SAMPLES_WAVEFORM]; /* Precompute the value of sin()^2 */ float cosine2[SAMPLES_WAVEFORM]; /* Precompute the value of cos()^2 */ float time; /* Time within symbol as fractional pi */ int i; /* Waveworm segment counter */ for (i = 0; i < SAMPLES_WAVEFORM; ++i) { /* Begin by computing full-symbol values */ time=(-PI/2.0f)+((PI/SAMPLES_WAVEFORM)/2.0f) + /* Define symmetrical symbol sampling interval */ ((i * PI) / SAMPLES_WAVEFORM); /* => pi/2 to +pi/2 in SAMPLES_WAVEFORM steps */ sine[i] = (float) sin(time); /* Determine sin() at this sampling point */ cosine[i] = (float) cos(time); /* Determine cos() at this sampling point */ sine2[i] = sine[i] * sine[i]; /* Precompute sin()^2 for S(x) waveforms */ cosine2[i] = cosine[i] * cosine[i]; /* Precompute cos()^2 for S(x) waveforms */ efqpsk[S0][i]=WAVE(A); /* Compute waveform S(0) for entire symbol */ efqpsk[S3][i]=WAVE(1 - ((1-A) * cosine2[i])); /* Compute waveform S(3) for entire symbol */ efqpsk[S7][i]=WAVE(sine[i]); /* Compute waveform S(7) for entire symbol */ } for (i = 0; i < SAMPLES_WAVEFORM/2; ++i) { /* Next, compute 1st half of "split" waveforms */ efqpsk[S1][i]=WAVE(A); /* Compute the first half of S(1) */ efqpsk[S2][i]=WAVE(1 - ((1-A) * cosine2[i])); /* Compute the first half of S(2) */ efqpsk[S4][i]=WAVE(sine[i]+((1-A) * sine2[i])); /* Compute the first half of S(4) */ efqpsk[S5][i]=WAVE(sine[i]+((1-A) * sine2[i])); /* Compute the first half of S(5) */ efqpsk[S6][i]=WAVE(sine[i]); /* Compute the first half of S(6) */ } for (i = SAMPLES_WAVEFORM/2; i < SAMPLES_WAVEFORM; ++i) { /* Compute second half of split waves */ efqpsk[S1][i]=WAVE(1 - ((1-A) * cosine2[i])); /* Compute the second half of S(1) */ efqpsk[S2][i]=WAVE(A); /* Compute the second half of S(2) */ efqpsk[S4][i]=WAVE(sine[i]-((1-A) * sine2[i])); /* Compute the second half of S(4) */ efqpsk[S5][i]=WAVE(sine[i]); /* Compute the second half of S(5) */ efqpsk[S6][i]=WAVE(sine[i]-((1-A) * sine2[i])); /* Compute the second half of S(6) */ } for (i = 0; i < SAMPLES_WAVEFORM; ++i) { /* Now compute inverse waveforms S(8)-S(15) */ efqpsk[S8][i] = -efqpsk[S0][i]; /* S(8) = - S(0) */ efqpsk[S9][i] = -efqpsk[S1][i]; /* S(9) = - S(1) */ efqpsk[S10][i] = -efqpsk[S2][i]; /* S(10) = - S(2) */ efqpsk[S11][i] = -efqpsk[S3][i]; /* S(11) = - S(3) */ efqpsk[S12][i] = -efqpsk[S4][i]; /* S(12) = - S(4) */ efqpsk[S13][i] = -efqpsk[S5][i]; /* S(13) = - S(5) */ efqpsk[S14][i] = -efqpsk[S6][i]; /* S(14) = - S(6) */ efqpsk[S15][i] = -efqpsk[S7][i]; /* S(15) = - S(7) */ } } /* init_efqpsk */ /********************************************************************************/ /* transmit_symbol: (i_wave, q_wave) */ /* */ /* Transmit_Symbol() is called to send a single EFQPSK symbol to the */ /* transmitter. Thanks to the pipelining specified for EFQPSK, it actually */ /* transmits one complete symbol of I from the S(n) waveforms above, while */ /* transmitting the second half of the previous Q waveform, and the first half */ /* of the next Q waveform. It performs this pipelining by "remembering" a */ /* pointer to the next waveform segment of Q to be transmitted during the */ /* subsequent call to transmit_symbol(). */ /* */ /* In actual implementation, transmit_symbol() will pass the selected waveform */ /* segment pointers to a scatter/gather DMA chain, allowing a hardware DMA */ /* controller to pass the samples directly to the output digital/analog (D/A) */ /* converters with minimum CPU overhead. */ /* */ /* At present, this routine simply writes the output waves to the global */ /* SAMPLE pointer *output, as a sequence of (I, Q) pairs, advancing the */ /* pointer after each SAMPLE that is written. */ /* */ /* Input: */ /* i_wave: Pointer to an S(n) waveform for the I channel. */ /* q_wave: Pointer to the next S(n) waveform for the Q channel - this */ /* waveform will be sent to the output with a 180 degree (1/2 */ /* symbol) phase delay, as specified by EFQPSK. */ /* */ /* Output: */ /* NONE */ /********************************************************************************/ void transmit_symbol(SAMPLE *wave_i, SAMPLE *wave_q) { int i; for (i = 0; i < SAMPLES_WAVEFORM / 2; ++i) { /* Send first half of this symbol */ *output++ = *wave_i++; /* Transmit first half of this I waveform */ *output++ = *last_wave++; /* Transmit last half of previous Q waveform */ } for (i = 0; i < SAMPLES_WAVEFORM / 2; ++i) { /* Now send second half of this symbol */ *output++ = *wave_i++; /* Transmit second half of this I waveform */ *output++ = *wave_q++; /* Send first half of this I waveform */ } last_wave = wave_q; /* Save ptr to second half of this Q for later */ } /* transmit_symbol */ /********************************************************************************/ /* E F Q P S K M O D U L A T O R */ /********************************************************************************/ /********************************************************************************/ /* send_word: (word) */ /* */ /* Send_Word() is called to EFQPSK modulate an input WORD of length */ /* BITS_WORD (bits per word). It transmits the data two bits at a time, by */ /* using the EFQPSK coding rules to select two cross-correlated I & Q waveforms */ /* for "simultaneous" transmission: the waveforms are actually transmitted with */ /* a 180 degree (1/2 symbol) delay between I & Q (in addition to a 90 degree */ /* _carrier_ delay between I & Q in the RF section of the transmitter). */ /* */ /* Even bits (i.e. 0, 2, 4, 6, 8) are sent via the I channel, and odd bits */ /* (1, 3, 5, 7, 9) are sent via the Q channel. */ /* */ /* To select which waveform will be sent via each channel, send_word() uses */ /* four bits of state to select one of sixteen possible transmit waveshapes. */ /* For the I channel, these bits comprise the following: */ /* */ /* 1) The last bit that was sent via the I channel (either 0 or 1). */ /* 2) The next-to-last bit that was sent via the I channel. */ /* 3) A bit which is high if the next bit to be sent via the Q channel is */ /* different from the last bit sent via the Q channel. */ /* 4) A bit which is high if the last bit sent via the Q channel */ /* was different from the bit sent before that on the Q channel. */ /* * /* Surprisingly, the encoding rules for the Q channel are not symmetrical with */ /* those of the I channel. In terms of selecting the next waveform to be sent */ /* via the Q channel, which will actually begin transmission halfway through */ /* the current symbol period, the following four bits of state are used: */ /* */ /* 1) The next bit to be sent via the Q channel. */ /* 2) The last bit that was sent via the Q channel. */ /* 3) A bit which is high if the last bit sent via the I channel was */ /* different from the next-to-last bit sent via the I channel. */ /* 4) A bit which is high if the next-to-last bit sent via the I channel */ /* was different from the bit sent before that on the I channel. */ /* */ /* Inputs: */ /* word: WORD value to be transmitted to the receiver. */ /* */ /* Output: */ /* NONE */ /********************************************************************************/ void send_word(WORD word) { static BYTE i_bits, q_bits; /* Static storage for last BYTE of I & Q sent */ BYTE delta_i, delta_q; /* Define deltas == bit transitions on I & Q */ SAMPLE *wave_i, *wave_q; /* Define waveform pointers for I & Q */ BYTE last_bits; /* Storage for most recent I & Q bits */ int n; /* Initially, we've sent 0 bits of this word */ for (n = 0; n < BITS_WORD; n += BITS_SYMBOL) { /* Loop until all bits of this word are sent */ /* * Read the next odd data bit into the Q channel, keeping a running * list of bits sent in q_bits, and computing the Q bit transition * history in delta_q. */ last_bits = q_bits; /* Save the most recently transmitted Q bits */ q_bits = (q_bits << 1) | BIT_0(word >> (n + 1)); /* Add odd bits to the Q_bits vector */ delta_q = q_bits ^ last_bits; /* Compute bits which have changed in Q */ /* * Determine which of the sixteen waveshapes to be sent via the I channel. */ wave_i = efqpsk[(BITS_1_0(i_bits) << 2) | BITS_1_0(delta_q)]; /* Select the I next waveform */ /* * Read the next even data bit into the I channel, keeping a running * list of bits sent in i_bits, and computing the I bit transition * history in delta_i. */ last_bits = i_bits; /* Save the most recently transmitted I bits */ i_bits = (i_bits << 1) | BIT_0(word >> n); /* Add even bits to the I_bits vector */ delta_i = i_bits ^ last_bits; /* Compute bits which have changed in I */ /* * Determine which of the sixteen waveshapes to be sent via the Q channel. */ wave_q = efqpsk[(BITS_1_0(q_bits) << 2) | BITS_1_0(delta_i)]; /* Select the next Q waveform */ /* * Transmit the I & Q waveshapes to the output. */ transmit_symbol(wave_i, wave_q); /* Send samples to D/A converters, I leading Q */ } } /* send_word */ /********************************************************************************/ /* send_stream: () */ /* */ /* Send_Stream() is a callback function which actually transmits data to */ /* the BASS audio driver system. In this system, BASS periodically calls */ /* send_stream() to fill up its audio output buffer, which is then sent to */ /* the Windows sound system. Send_Stream() fills the buffer, then returns */ /* the number of bytes which it has written to the output (callback functions */ /* in BASS may return less data than requested). */ /* */ /* As currently implemented, each time send_stream() is called, it begins by */ /* sending the reference data string that was published as Figure 4 of the */ /* Simon and Yan paper. By comparing the output that this modulator creates */ /* to the diagram, it is possible to verify the correctness of this EFQPSK */ /* modulator. Following the reference string, send_stream() will send random */ /* data words until the output buffer is full. */ /* */ /* Inputs: */ /* NONE */ /* */ /* Output: */ /* NONE */ /********************************************************************************/ DWORD CALLBACK send_stream(HSTREAM handle, void *buff, DWORD len, DWORD user) { int send, sent; send = len / (BYTES_SYMBOL * SYMBOLS_WORD); /* Find out how much data is requested */ output = (SAMPLE *) buff; /* Set global output pointer to callback buff */ send_word(0x0DB); /* Send the standard reference string */ send_word(0x389); /* ...for debugging the correctness of the */ send_word(0x001); /* ...channel coding scheme */ sent = 3; /* Count these waveforms in words sent */ while (sent++ < send) { /* Repeat until data buffer is full */ send_word(((WORD) rand()) & WORD_MASK); /* Transmit a random word to the output */ } return(send * BYTES_SYMBOL * SYMBOLS_WORD); /* Return number of bytes sent to stream */ } /* send_stream */ /********************************************************************************/ /* main: () */ /* */ /* Main is the executive routine for this EFQPSK modulator. It calls */ /* init_efqpsk() to build the EFQPSK waveform tables, and then initializes the */ /* system to support the BASS audio driver library. After initializing BASS, */ /* it creates a stereo "stream" (i.e. an infinite sequence of I & Q outputs) */ /* that are sent to the output by the send_stream() routine. It then initiates */ /* playback of the stream - BASS will call send_stream whenever the audio */ /* buffers are running low. */ /* */ /* Main() will continue playback until the user presses a key (while the EFQPSK */ /* window has focus). Once a key has been pressed, it will ramp the volume */ /* down gradually to prevent an output "click", then will close BASS and exit. */ /* */ /* CREDITS: Note that the BASS interface code shown below is derived from the */ /* BASS audio driver library, and various example interface routines. Although */ /* this code isn't copyrighted, BASS is an excellent low-latency solution for */ /* audio control - thanks to the folks at Un4Seen developments for a great */ /* audio solution! For more details on the BASS system, please visit: */ /* */ /* http://www.un4seen.com */ /* */ /* */ /* Inputs: */ /* NONE - main() current ignores command line parameters */ /* */ /* Output: */ /* Return 0 if success, or 1 if an initialization error occurred. */ /********************************************************************************/ int main(int argc, char *argv[]) { HSTREAM stream; init_efqpsk(); /* Build the EFQPSK waveforms */ if (BASS_GetVersion() != MAKELONG(2,0)) { /* Check to see if BASS V2 is present */ printf("BASS V2.0 was not loaded - Incorrect BASS.DLL");/* Complain if the wrong DLL is present */ return(1); /* Can't run without BASS V2 - error out */ } if (!BASS_Init(1, SAMPLES_SECOND, 0, 0, NULL)) { /* Init the BASS subsystem for 80 KHz out */ printf("Can't initialize D/A output device"); /* Flag errors if system doesn't support 80 KHz */ } stream=BASS_StreamCreate(SAMPLES_SECOND,2,0,&send_stream,0);/* Make stereo stream filled by send_stream() */ if (!stream) { /* If no valid handle was received */ printf("Can't initiate output streaming"); /* Flag the stream creation error */ BASS_Free(); /* Close the BASS driver system */ exit(1); /* Accept an error exit */ } BASS_StreamPlay(stream, 0, 0); /* Begin playing the output stream */ while (!_kbhit() && BASS_ChannelIsActive(stream)) { /* Repeat until a key is hit or error occurs */ Sleep(50); /* Wait a small amount of time */ } BASS_ChannelSlideAttributes(stream,-1,-2,-101,200); /* Ramp volume down to prevent a click */ while (BASS_ChannelIsSliding(stream)) Sleep(1); /* Loop until volume is all the way down */ BASS_Free(); /* Close the BASS subsystem */ return(0); /* Return success to the caller */ } /* main */