o2-1.0/0000755000175000017500000000000013072261166012130 5ustar zmoelnigzmoelnigo2-1.0/test/0000755000175000017500000000000013401500070013071 5ustar zmoelnigzmoelnigo2-1.0/test/oscanytest.c0000644000175000017500000000467113072261166015457 0ustar zmoelnigzmoelnig// oscanytest.c - test of o2_osc_port_new() to receive any message // // this test is designed to run with oscsendtest.c #include "stdio.h" #include "o2.h" #include "string.h" #include "assert.h" #ifdef WIN32 #include "usleep.h" #else #include #endif int message_count = 0; o2_time timed_start = 0; int timed_count = 0; // note: this failed with 0.02 error bound. I ran it again // and it worked, so I increased the error bound to 0.03. // 30ms seems like a lot, but I haven't done the analysis, // and perhaps with VS running in the background, and Python // running the regression test, we just got hit by some // worst-case behavior. int approx(double x) { return (x > -0.03) && (x < 0.03); } void osc_i_handler(o2_msg_data_ptr data, const char *types, o2_arg_ptr *argv, int argc, void *user_data) { assert(argv); assert(argc == 1); assert(strcmp(types, "i") == 0); int i = argv[0]->i; if (i == 1234) { printf("osc_i_handler received 1234 at /oscrecv\n"); message_count++; } else if (i == 2000) { timed_start = o2_time_get(); timed_count = 1; } else if (2000 < i && i < 2010) { printf("osc_i_handler received %d at elapsed %g\n", i, o2_time_get() - timed_start); i -= 2000; assert(i == timed_count); #ifndef NDEBUG o2_time now = o2_time_get(); // only needed in assert() #endif assert(approx(timed_start + i * 0.1 - now)); timed_count++; } else { assert(FALSE); // unexpected message } } int main(int argc, const char * argv[]) { printf("Usage: oscrecvtest [flags] " "(see o2.h for flags, use a for all, also u for UDP)\n"); int tcpflag = TRUE; if (argc == 2) { o2_debug_flags(argv[1]); tcpflag = (strchr(argv[1], 'u') == NULL); } if (argc > 2) { printf("WARNING: o2server ignoring extra command line argments\n"); } o2_initialize("test"); printf("tcpflag %d\n", tcpflag); int err = o2_osc_port_new("oscrecv", 8100, tcpflag); assert(err == O2_SUCCESS); o2_clock_set(NULL, NULL); o2_service_new("oscrecv"); o2_method_new("/oscrecv/i", NULL, osc_i_handler, NULL, FALSE, TRUE); while (message_count < 10 || timed_count < 10) { o2_poll(); usleep(2000); // 2ms } o2_osc_port_free(8100); o2_finish(); printf("OSCANY DONE\n"); sleep(1); // allow TCP to finish up return 0; } o2-1.0/test/midiserver.c0000644000175000017500000000352113072261166015425 0ustar zmoelnigzmoelnig// midiserver.c - example program, receive O2, send MIDI // // This program works with midiclient.c. // #include "o2.h" #include "stdio.h" #include "string.h" #include "portmidi.h" #include "porttime.h" // maybe not needed #define TIME_PROC ((int32_t (*)(void *)) Pt_Time) #define TIME_INFO NULL PmStream *midi_out = NULL; // MIDI output stream #ifdef WIN32 #include "usleep.h" // special windows implementation of sleep/usleep #else #include #endif // this is a handler for incoming messages. It simply builds a // MIDI message and sends it using portmidi // void midi_handler(o2_msg_data_ptr msg, const char *types, o2_arg ** argv, int argc, void *user_data) { int status = argv[0]->i32; int data1 = argv[1]->i32; int data2 = argv[2]->i32; Pm_WriteShort(midi_out, 0, Pm_Message(status, data1, data2)); printf("Pm_WriteShort(%2x %2x %2x at %g\n", status, data1, data2, o2_time_get()); } int main(int argc, const char * argv[]) { o2_debug_flags("3"); // start portmidi Pt_Start(1, 0, 0); int dev_num = Pm_GetDefaultOutputDeviceID(); printf("Using default PortMidi ouput device number %d\n", dev_num); Pm_OpenOutput(&midi_out, dev_num, NULL, 0, TIME_PROC, TIME_INFO, 0); o2_initialize("miditest"); // ideally, this application name should be // passed from the command line so we provide service to any application // we are the master clock o2_clock_set(NULL, NULL); o2_service_new("midi"); // add our handler for incoming messages to each server address o2_method_new("/midi/midi", "iii", &midi_handler, NULL, TRUE, TRUE); printf("Here we go! ...\ntime is %g.\n", o2_time_get()); while (TRUE) { o2_poll(); usleep(1000); // 1ms } o2_finish(); Pm_Close(midi_out); Pm_Terminate(); return 0; } o2-1.0/test/cmtio.h0000644000175000017500000000023113072261166014367 0ustar zmoelnigzmoelnig#define NOCHAR -2 extern int IOinputfd; extern int IOnochar; int IOsetup(int inputfd); int IOcleanup(void); int IOgetchar(void); int IOwaitchar(void); o2-1.0/test/cmtio.c0000644000175000017500000000712613072261166014374 0ustar zmoelnigzmoelnig/* ********************************************************************** * File io.c ********************************************************************** * * Non blocking input routine * Works by puttng the terminal in CBREAK mode and using the FIONREAD * ioctl call to determine the number of characters in the input queue */ #include "stdio.h" #include "stdlib.h" #include "unistd.h" #include #include #include #include #include "cmtio.h" //#include "cext.h" int IOinputfd; /* input file descriptor (usually 0) */ int IOnochar; /* Value to be returned by IOgetchar() where there is no input to be had */ static struct sgttyb IOoldmodes, IOcurrentmodes; /* Initial and current tty modes */ /* * IOsetup(inputfd) * Args: * inputfd - input file descriptor (should be zero for standard input) * Returns: * 0 - if all goes well * -1 - if an ioctl fails (also calls perror) * Side Effects: * Puts the terminal in CBREAK mode - before process termination * IOcleanup() should be called to restore old terminal modes * Catch's interrupts (if they are not already being caught) and * calls IOcleanup before exiting * */ #define ERROR(s) return (perror(s), -1) /* * IOcleanup() * Returns: * 0 - if all goes well * -1 - if an ioctl fails (also calls perror) * Side Effects: * Restores initial terminal modes */ int IOcleanup() { if(ioctl(IOinputfd, TIOCSETP, &IOoldmodes) < 0) ERROR("IOclean"); return 0; } static void IOdiegracefully() { write(2, "\nBye\n", 5); IOcleanup(); exit(2); } int IOsetup(inputfd) { void (*interrupt_handler)(int); IOinputfd = inputfd; IOnochar = NOCHAR; if(ioctl(IOinputfd, TIOCGETP, &IOoldmodes) < 0) ERROR("IOsetup"); IOcurrentmodes = IOoldmodes; IOcurrentmodes.sg_flags |= CBREAK; IOcurrentmodes.sg_flags &= ~ECHO; if(ioctl(IOinputfd, TIOCSETP, &IOcurrentmodes)) ERROR("IOsetup-2"); if( (interrupt_handler = signal(SIGINT, IOdiegracefully)) != 0) signal(SIGINT, interrupt_handler); return 0; } /* * IOgetchar() * Returns: * A character off the input queue if there is one, * IOnochar if there is no character waiting to be read, * -1 if an ioctl fails (shouldn't happen if IOsetup went OK) */ int IOgetchar() { int n; char c; if(ioctl(IOinputfd, FIONREAD, &n) < 0) ERROR("IOgetchar"); if(n <= 0) return IOnochar; switch(read(IOinputfd, &c, 1)) { case 1: return c; case 0: return EOF; default: ERROR("IOgetchar-read"); } } #ifdef IOGETCHAR2 int IOgetchar2() { int nfds, readfds = 1 << IOinputfd; char c; static struct timeval zero; if(IOinputfd < 0 || IOinputfd >= 32) { printf("IOgetchar2: bad IOinputfd (%d)%s\n", IOinputfd, IOinputfd == -1 ? "Did you call IOsetup(fd)?" : ""); } nfds = select(32, &readfds, 0, 0, &zero); if(nfds > 0) { switch(read(IOinputfd, &c, 1)) { case 0: return EOF; case 1: return c; default: printf("IOgetchar2: read failed!\n"); return NOCHAR; } } else if(nfds < 0) printf("IOgetchar2: select failed!\n"); return NOCHAR; } #endif /* * IOwaitchar() * Returns: * A character off the input queue. Waits if necessary. */ int IOwaitchar() { char c; if (read(IOinputfd, &c, 1) == 1) return c; else return EOF; } o2-1.0/test/lo_bndlrecv.c0000644000175000017500000000671013072261166015550 0ustar zmoelnigzmoelnig// lo_bndlrecv.c - test program to receive OSC bundles // // this test is designed to run with bndlsendtest.c // this test is based on bndlrecvtest.c #include "stdio.h" #include "assert.h" #include "lo/lo.h" #include "string.h" #ifdef WIN32 #include #include "usleep.h" // special windows implementation of sleep/usleep #else #include #endif int ints[] = {1005, 2005, 1006, 2006, 1007, 2007, 1008, 2008, 1009, 2009, 3001, 3002, 3003, 4001, 4002, 4003, 999}; char *strings[] = { "an arbitrary string at 2.5", "another arbitrary string at 2.5", "an arbitrary string at 2.6", "another arbitrary string at 2.6", "an arbitrary string at 2.7", "another arbitrary string at 2.7", "an arbitrary string at 2.8", "another arbitrary string at 2.8", "an arbitrary string at 2.9", "another arbitrary string at 2.9", "first string at 3", "msg1 string at 0", "msg2 string at 0", "first string at 3.1", "msg1 string at 3.2", "msg2 string at 3.2", "not a valid string"}; double times[] = {2.5, 2.5, 2.6, 2.6, 2.7, 2.7, 2.8, 2.8, 2.9, 2.9, 3.0, 3.0, 3.0, 3.1, 3.2, 3.2, 999}; int msg_count = 0; double start_time = 0.0; // test if x and y are within 20ms // Note: this failed with 10ms tolerance, which was surprising // It seemed to be jitter and latency rather than systematic // error (too early or too late), maybe just due to printing. int approximate(double x, double y) { double diff = x - y; return (diff < 0.02) && (diff > -0.02); } #define JAN_1970 0x83aa7e80 /* 2208988800 1970 - 1900 in seconds */ // we'll use secs since 1970 for a little more precision double timetag_to_secs(lo_timetag tt) { return (tt.sec - JAN_1970) + tt.frac * 0.00000000023283064365; } int meta_handler(char *name, lo_arg **argv, int argc) { lo_timetag ttnow; lo_timetag_now(&ttnow); double now = timetag_to_secs(ttnow); if (msg_count == 0) { start_time = now - 2.5; } printf("%s receieved %d, \"%s\"\n", name, argv[0]->i, &(argv[1]->s)); printf(" elapsed time: %g\n", now - start_time); assert(argv); assert(argc == 2); assert(argv[0]->i == ints[msg_count]); assert(strcmp(&(argv[1]->s), strings[msg_count]) == 0); assert(approximate(now - start_time, times[msg_count])); msg_count++; return 0; } #define ARGS const char *path, const char *types, \ lo_arg **argv, int argc, void *msg, void *user_data int first_handler(ARGS) { return meta_handler("first_handler", argv, argc); } int msg1_handler(ARGS) { return meta_handler("msg1_handler", argv, argc); } int msg2_handler(ARGS) { return meta_handler("msg2_handler", argv, argc); } int main(int argc, const char * argv[]) { int tcpflag = 1; printf("Usage: lo_bndlrecv [u] (u means use UDP)\n"); if (argc == 2) { tcpflag = (strchr(argv[1], 'u') == NULL); } printf("tcpflag %d\n", tcpflag); lo_server server = lo_server_new_with_proto( "8100", tcpflag ? LO_TCP : LO_UDP, NULL); lo_server_add_method(server, "/xyz/msg1", "is", &msg1_handler, NULL); lo_server_add_method(server, "/abcdefg/msg2", "is", &msg2_handler, NULL); lo_server_add_method(server, "/first", "is", &first_handler, NULL); while (msg_count < 16) { lo_server_recv_noblock(server, 0); usleep(10000); // 10ms } lo_server_free(server); sleep(1); printf("OSCRECV DONE\n"); return 0; } o2-1.0/test/clockslave.c0000644000175000017500000000421113072261166015377 0ustar zmoelnigzmoelnig// clockslave.c - clock synchronization test/demo //// // see clockmaster.c for details #include "o2.h" #include "stdio.h" #include "string.h" #ifdef WIN32 #include "usleep.h" // special windows implementation of sleep/usleep #else #include #endif o2_time cs_time = 1000000.0; void clockslave(o2_msg_data_ptr msg, const char *types, o2_arg ** argv, int argc, void *user_data) { int ss = o2_status("server"); int cs = o2_status("client"); double mean_rtt, min_rtt; o2_roundtrip(&mean_rtt, &min_rtt); printf("clockslave: local time %g global time %g " "ss %d cs %d mean %g min %g\n", o2_local_time(), o2_time_get(), ss, cs, mean_rtt, min_rtt); if (ss == O2_REMOTE) { if (o2_time_get() < cs_time) { cs_time = o2_time_get(); printf("clockslave sync time %g\n", cs_time); } } // stop 10s later if (o2_time_get() > cs_time + 10) { o2_stop_flag = TRUE; printf("clockslave set stop flag TRUE at %g\n", o2_time_get()); } // Since the clock slave cannot immediately send scheduled messages // due to there being no global time reference, we will schedule // messages directly on the local scheduler o2_send_start(); o2_message_ptr m = o2_message_finish(o2_local_time() + 1, "!client/clockslave", TRUE); o2_schedule(&o2_ltsched, m); } int main(int argc, const char * argv[]) { printf("Usage: clockslave [debugflags] " "(see o2.h for flags, use a for all)\n"); if (argc == 2) { o2_debug_flags(argv[1]); printf("debug flags are: %s\n", argv[1]); } if (argc > 2) { printf("WARNING: clockslave ignoring extra command line argments\n"); } o2_initialize("test"); o2_service_new("client"); o2_method_new("/client/clockslave", "", &clockslave, NULL, FALSE, FALSE); // this particular handler ignores all parameters, so this is OK: // start polling and reporting status clockslave(NULL, NULL, NULL, 0, NULL); o2_run(100); o2_finish(); sleep(1); printf("CLOCKSLAVE DONE\n"); return 0; } o2-1.0/test/regression_tests.py0000644000175000017500000001441213072261166017065 0ustar zmoelnigzmoelnig# regression_tests.py -- a rewrite of regression_tests.sh for Windows # using Python 3 (and hopefully compatible with Python 2.7 on OS X) # # Roger Dannenberg Jan 2017 # # Run this in the o2/tests directory where it is found # # get print to be compatible even if using python 2.x: from __future__ import print_function import os import platform import subprocess import shlex import threading allOK = True # Is this Windows? EXE = "" if os.name == 'nt': EXE = ".exe" # Find the binaries if os.path.isdir(os.path.join(os.getcwd(), '../Debug')): BIN="../Debug" else: BIN=".." # In linux, there is likely to be a debug version of the # library copied to ../Debug, but tests are built in .. if platform.system() == 'Linux': BIN=".." def findLineInString(line, aString): return ('\n' + line + '\n') in aString class RunInBackground(threading.Thread): def __init__(self, command): self.command = command self.output = "" self.errout = "" threading.Thread.__init__(self) def run(self): output1 = "" output2 = "" args = shlex.split(self.command) args[0] = BIN + '/' + args[0] + EXE process = subprocess.Popen(args, shell=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE) (self.output, self.errout) = process.communicate() if os.name == 'nt': self.output = self.output.decode("utf-8").replace('\r\n', '\n') self.errout = self.errout.decode("utf-8").replace('\r\n', '\n') # runTest testname - runs testname, saving output in output.txt, # searches output.txt for single full line containing "DONE", # returns status=0 if DONE was found (indicating success), or # else status=-1 (indicating failure). def runTest(command): global allOK print(command.rjust(30) + ": ", end='') args = shlex.split(command) args[0] = BIN + '/' + args[0] + EXE process = subprocess.Popen(args, shell=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE) (stdout, stderr) = process.communicate() if os.name == 'nt': stdout = stdout.decode("utf-8").replace('\r\n', '\n') stderr = stderr.decode("utf-8").replace('\r\n', '\n') if findLineInString("DONE", stdout): print("PASS") return True else: print("FAIL") print("**** Failing output:") print(stdout) print("**** Failing error output:") print(stderr) allOK = False return False def runDouble(prog1, out1, prog2, out2): global allOK print((prog1 + '+' + prog2).rjust(30) + ": ", end='') p1 = RunInBackground(prog1) p1.start() p2 = RunInBackground(prog2) p2.start() p1.join() p2.join() if findLineInString(out1, p1.output): if findLineInString(out2, p2.output): print("PASS") return True print("FAIL") print("**** Failing output from " + prog1) print(p1.output) print("**** Failing error output from " + prog1) print(p1.errout) print("**** Failing output from " + prog2) print(p2.output) print("**** Failing error output from " + prog2) print(p2.errout) allOK = False return False def runAllTests(): print("Running regression tests for O2 ...") if not runTest("dispatchtest"): return if not runTest("typestest"): return if not runTest("coercetest"): return if not runTest("longtest"): return if not runTest("arraytest"): return if not runTest("bundletest"): return if not runDouble("clockmaster", "CLOCKMASTER DONE", "clockslave", "CLOCKSLAVE DONE"): return if not runDouble("o2client", "CLIENT DONE", "o2server", "SERVER DONE"): return if not runDouble("oscsendtest u", "OSCSEND DONE", "oscrecvtest u", "OSCRECV DONE"): return if not runDouble("oscsendtest u", "OSCSEND DONE", "oscanytest u", "OSCANY DONE"): return if not runDouble("oscsendtest", "OSCSEND DONE", "oscrecvtest", "OSCRECV DONE"): return if not runDouble("tcpclient", "CLIENT DONE", "tcpserver", "SERVER DONE"): return if not runDouble("oscbndlsend u", "OSCSEND DONE", "oscbndlrecv u", "OSCRECV DONE"): return if not runDouble("oscbndlsend", "OSCSEND DONE", "oscbndlrecv", "OSCRECV DONE"): return # tests for compatibility with liblo are run only if the binaries were built # In CMake, set BUILD_TESTS_WITH_LIBLO to create the binaries if os.path.isfile(BIN + '/' + "lo_oscrecv" + EXE): if not runDouble("oscsendtest Mu", "OSCSEND DONE", "lo_oscrecv u", "OSCRECV DONE"): return if not runDouble("oscsendtest M", "OSCSEND DONE", "lo_oscrecv", "OSCRECV DONE"): return if os.path.isfile(BIN + '/' + "lo_oscsend" + EXE): if not runDouble("lo_oscsend u", "OSCSEND DONE", "oscrecvtest u", "OSCRECV DONE"): return if not runDouble("lo_oscsend", "OSCSEND DONE", "oscrecvtest", "OSCRECV DONE"): return if os.path.isfile(BIN + '/' + "lo_bndlsend" + EXE): if not runDouble("lo_bndlsend u", "OSCSEND DONE", "oscbndlrecv u", "OSCRECV DONE"): return if not runDouble("lo_bndlsend", "OSCSEND DONE", "oscbndlrecv", "OSCRECV DONE"): return if os.path.isfile(BIN + '/' + "lo_bndlrecv" + EXE): if not runDouble ("oscbndlsend Mu", "OSCSEND DONE", "lo_bndlrecv u", "OSCRECV DONE"): return if not runDouble ("oscbndlsend M", "OSCSEND DONE", "lo_bndlrecv", "OSCRECV DONE"): return runAllTests() if allOK: print("**** All O2 regression tests PASSED.") else: print("ERROR: Exiting regression tests because a test failed.") print(" See above for output from the failing test(s).") print("\nNOTE: If firewall pop-ups requested access to the network,") print(" that *might* affect timing and cause a test to fail.") print(" If you granted access, permission should be granted") print(" without delay if you run the test again.") o2-1.0/test/bundletest.c0000644000175000017500000000365713072261166015437 0ustar zmoelnigzmoelnig// dispatchtest.c -- dispatch messages between local services // #include #include "o2.h" #include "assert.h" #include "o2_message.h" #define N_ADDRS 20 int expected = 0; void service_one(o2_msg_data_ptr data, const char *types, o2_arg_ptr *argv, int argc, void *user_data) { assert(argc == 1); assert(argv[0]->i == 1234); printf("service_one called\n"); assert(expected % 10 == 1); expected /= 10; } void service_two(o2_msg_data_ptr data, const char *types, o2_arg_ptr *argv, int argc, void *user_data) { assert(argc == 1); assert(argv[0]->i == 2345); printf("service_two called\n"); assert(expected % 10 == 2); expected /= 10; } int main(int argc, const char * argv[]) { o2_initialize("test"); o2_service_new("one"); o2_method_new("/one/i", "i", &service_one, NULL, TRUE, TRUE); o2_service_new("two"); o2_method_new("/two/i", "i", &service_two, NULL, TRUE, TRUE); // make a bundle, starting with two messages o2_send_start(); o2_add_int32(1234); o2_message_ptr one = o2_message_finish(0.0, "/one/i", TRUE); o2_send_start(); o2_add_int32(2345); o2_message_ptr two = o2_message_finish(0.0, "/two/i", TRUE); expected = 21; o2_send_start(); o2_add_message(one); o2_add_message(two); o2_send_finish(0.0, "#one", TRUE); assert(expected == 0); expected = 21; o2_send_start(); o2_add_message(one); o2_add_message(two); o2_send_finish(0.0, "#two", TRUE); assert(expected == 0); // make a nested bundle ((12)(12)) o2_send_start(); o2_add_message(one); o2_add_message(two); o2_message_ptr bdl = o2_message_finish(0.0, "#one", TRUE); expected = 2121; o2_send_start(); o2_add_message(bdl); o2_add_message(bdl); o2_send_finish(0.0, "#two", TRUE); assert(expected == 0); o2_finish(); printf("DONE\n"); return 0; } o2-1.0/test/lo_oscsend.c0000644000175000017500000000363113072261166015406 0ustar zmoelnigzmoelnig// lo_oscsend.c - test to send simple OSC messages // // this test is designed to run with oscrecvtest.c #include "stdio.h" #include "assert.h" #include "lo/lo.h" #include "string.h" #ifdef WIN32 #include "usleep.h" // special windows implementation of sleep/usleep #else #include #endif lo_timetag start; #define TWO32 4294967296.0 void timetag_add(lo_timetag *timetag, lo_timetag x, double y) { double secs = x.sec + (x.frac / TWO32); secs += y; timetag->sec = (uint32_t) secs; timetag->frac = (uint32_t) ((secs - timetag->sec) * TWO32); } void wait_until(double offset) { lo_timetag deadline; lo_timetag now; lo_timetag_now(&now); timetag_add(&deadline, start, offset); while (lo_timetag_diff(deadline, now) > 0) { usleep(2000); lo_timetag_now(&now); } } int main(int argc, const char * argv[]) { int tcpflag = 1; printf("Usage: lo_oscsend [u] (u means use UDP)\n"); if (argc == 2) { tcpflag = (strchr(argv[1], 'u') == NULL); } printf("tcpflag %d\n", tcpflag); sleep(2); // allow some time for server to start lo_address client = lo_address_new_with_proto(tcpflag ? LO_TCP : LO_UDP, "localhost", "8100"); printf("client: %p\n", client); // send 12 messages, 1 every 0.5s, and stop for (int n = 0; n < 12; n++) { lo_send(client, "/i", "i", 1234); printf("sent 1234 to /i\n"); // pause for 0.5s, but keep running O2 by polling for (int i = 0; i < 250; i++) { usleep(2000); // 2ms } } // send 10 messages with timestamps spaced by 0.1s lo_timetag_now(&start); for (int n = 0; n < 10; n++) { wait_until(n * 0.1); lo_send(client, "/i", "i", 2000 + n); } sleep(1); // make sure messages go out lo_address_free(client); printf("OSCSEND DONE\n"); return 0; } o2-1.0/test/arraytest.c0000644000175000017500000006725613072261166015311 0ustar zmoelnigzmoelnig// arraytest.c -- test array/vector messages // // What does this test? // 1. sending typestring [i] (an array with one integer) // 2. sending typestring [] (an array with no integers) // 3. sending typestring [ii] (an array with 2 integers) // 4. sending typestring [xixdx] where x is one of: ihfdcBbtsSmTFIN // (just in case a mix of sizes causes problems) // 5. sending typestring i[ih][fdt]d to test multiple arrays // 6. sending typestring [ddddd...] where there are 1 to 100 d's // 7. sending typestring vi (with length 0 to 100) // 8. sending typestring vf (with length 0 to 100) // 9. sending typestring vh (with length 0 to 100) // 10. sending typestring vd (with length 0 to 100) // 11. sending typestring vt (with length 0 to 100) // 12. sending typestring ifvtif (with vector length 0 to 100) // (this last test is an extra check for embedded vectors) // 13. sending typestring vivd (with lenghts 0 to 100) // (another test to look for bugs in allocation, receiving multiple // vectors in one message) // 14. sending i[xxxx...]i where x is in ihfdt and there are 0 to 100 // of them AND the data is received as a vector using coercion // 15. sending ivxi where x is in ihfdt and there are 0 to 100 // of them AND the data is received as an array using coercion #include #include "o2.h" #include "assert.h" #include "string.h" #ifdef WIN32 #define snprintf _snprintf #endif int got_the_message = FALSE; o2_blob_ptr a_blob; uint32_t a_midi_msg; char xtype = 0; // used to tell handler what type(s) to expect when char ytype = 0; // used to tell handler what type(s) to coerce to // a handler is used to accept multiple message types int arg_count = 0; // used to tell handler how many is correct // 1. sending typestring [i] (an array with one integer) // void service_ai(o2_msg_data_ptr data, const char *types, o2_arg_ptr *argv, int argc, void *user_data) { o2_extract_start(data); assert(*types++ == '['); #ifndef NDEBUG o2_arg_ptr arg = // only needed by assert() #endif o2_get_next('['); assert(arg == o2_got_start_array); assert(*types++ == 'i'); #ifndef NDEBUG arg = // only needed by assert() #endif o2_get_next('i'); assert(arg->i == 3456); assert(*types++ == ']'); #ifndef NDEBUG arg = // only needed by assert() #endif o2_get_next(']'); assert(arg == o2_got_end_array); assert(*types == 0); got_the_message = TRUE; } // 2. sending typestring [] (an array with no integers) // void service_a(o2_msg_data_ptr data, const char *types, o2_arg_ptr *argv, int argc, void *user_data) { o2_extract_start(data); assert(*types++ == '['); #ifndef NDEBUG o2_arg_ptr arg = // only needed by assert() #endif o2_get_next('['); assert(arg == o2_got_start_array); assert(*types++ == ']'); #ifndef NDEBUG arg = // only needed by assert() #endif o2_get_next(']'); assert(arg == o2_got_end_array); assert(*types == 0); got_the_message = TRUE; } // 3. sending typestring [ii] (an array with 2 integers) // void service_aii(o2_msg_data_ptr data, const char *types, o2_arg_ptr *argv, int argc, void *user_data) { o2_extract_start(data); assert(*types++ == '['); #ifndef NDEBUG o2_arg_ptr arg = // only needed by assert() #endif o2_get_next('['); assert(arg == o2_got_start_array); assert(*types++ == 'i'); #ifndef NDEBUG arg = // only needed by assert() #endif o2_get_next('i'); assert(arg->i == 123); assert(*types++ == 'i'); #ifndef NDEBUG arg = // only needed by assert() #endif o2_get_next('i'); assert(arg->i == 234); assert(*types++ == ']'); #ifndef NDEBUG arg = // only needed by assert() #endif o2_get_next(']'); assert(arg == o2_got_end_array); assert(*types == 0); got_the_message = TRUE; } void check_val(char actual_type) { o2_arg_ptr arg; assert(actual_type == xtype); arg = o2_get_next(xtype); switch (xtype) { case O2_INT32: assert(arg->i == 1234); break; case O2_INT64: assert(arg->h == 12345); break; case O2_FLOAT: assert(arg->f == 1234.56F); break; case O2_DOUBLE: assert(arg->d == 1234.567); break; case O2_TIME: assert(arg->t == 2345.678); break; case O2_BOOL: assert(arg->B); break; case O2_CHAR: assert(arg->c == '$'); break; case O2_TRUE: case O2_FALSE: case O2_INFINITUM: case O2_NIL: assert(arg); break; case O2_BLOB: assert(arg->b.size == a_blob->size && memcmp(arg->b.data, a_blob->data, 15) == 0); break; case O2_STRING: assert(strcmp(arg->S, "This is a string") == 0); break; case O2_SYMBOL: assert(strcmp(arg->S, "This is a symbol") == 0); break; case O2_MIDI: assert(arg->m == a_midi_msg); break; default: assert(FALSE); } return; } void icheck(char typ, int val) { assert(typ == 'i'); #ifndef NDEBUG o2_arg_ptr arg = // only needed for assert() #endif o2_get_next('i'); assert(arg && arg->i == val); } void hcheck(char typ, int64_t val) { assert(typ == 'h'); #ifndef NDEBUG o2_arg_ptr arg = // only needed for assert() #endif o2_get_next('h'); assert(arg && arg->h == val); } void dcheck(char typ, double val) { assert(typ == 'd'); #ifndef NDEBUG o2_arg_ptr arg = // only needed for assert() #endif o2_get_next('d'); assert(arg && arg->d == val); } void tcheck(char typ, double val) { assert(typ == 't'); #ifndef NDEBUG o2_arg_ptr arg = // only needed for assert() #endif o2_get_next('t'); assert(arg && arg->t == val); } void fcheck(char typ, float val) { assert(typ == 'f'); #ifndef NDEBUG o2_arg_ptr arg = // only needed for assert() #endif o2_get_next('f'); assert(arg && arg->f == val); } void acheck(char typ) { assert(typ == '['); #ifndef NDEBUG o2_arg_ptr arg = // only needed for assert() #endif o2_get_next('['); assert(arg && arg == o2_got_start_array); } void zcheck(char typ) { assert(typ == ']'); #ifndef NDEBUG o2_arg_ptr arg = // only needed for assert() #endif o2_get_next(']'); assert(arg && arg == o2_got_end_array); } // 4. sending typestring [xixdx] where x is one of: ihfdcBbtsSmTFIN // (just in case a mix of sizes causes problems); the global // char xtype; provides the value of x // void service_xixdx(o2_msg_data_ptr data, const char *types, o2_arg_ptr *argv, int argc, void *user_data) { o2_extract_start(data); acheck(*types++); check_val(*types++); icheck(*types++, 456); check_val(*types++); dcheck(*types++, 234.567); check_val(*types++); zcheck(*types++); assert(*types == 0); got_the_message = TRUE; } // 5. sending typestring i[ih][fdt]d to test multiple arrays // void service_2arrays(o2_msg_data_ptr data, const char *types, o2_arg_ptr *argv, int argc, void *user_data) { o2_extract_start(data); icheck(*types++, 456); acheck(*types++); icheck(*types++, 1234); hcheck(*types++, 12345); zcheck(*types++); acheck(*types++); fcheck(*types++, 1234.56F); dcheck(*types++, 1234.567); tcheck(*types++, 2345.678); zcheck(*types++); dcheck(*types++, 1234.567); assert(*types == 0); got_the_message = TRUE; } // 6. sending typestring [ddddd...] where there are 1 to 100 d's // void service_bigarray(o2_msg_data_ptr data, const char *types, o2_arg_ptr *argv, int argc, void *user_data) { o2_extract_start(data); acheck(*types++); for (int i = 0; i < arg_count; i++) { dcheck(*types++, 123.456 + i); } zcheck(*types++); assert(*types == 0); // got all of typestring got_the_message = TRUE; } // 7. sending typestring vi (with length 0 to 100) // void service_vi(o2_msg_data_ptr data, const char *types, o2_arg_ptr *argv, int argc, void *user_data) { o2_extract_start(data); assert(*types++ == 'v'); #ifndef NDEBUG o2_arg_ptr arg = // only needed for assert() #endif o2_get_next('v'); assert(arg); assert(*types++ == 'i'); #ifndef NDEBUG o2_arg_ptr arg2 = // only needed for assert() #endif o2_get_next('i'); assert(arg2); assert(arg2 == arg); assert(arg->v.len == arg_count); assert(arg->v.typ == 'i'); for (int i = 0; i < arg_count; i++) { assert(arg->v.vi); assert(arg->v.vi[i] == 1234 + i); } assert(*types == 0); // got all of typestring got_the_message = TRUE; } // 8. sending typestring vf (with length 0 to 100) // void service_vf(o2_msg_data_ptr data, const char *types, o2_arg_ptr *argv, int argc, void *user_data) { o2_extract_start(data); assert(*types++ == 'v'); #ifndef NDEBUG o2_arg_ptr arg = // only needed by assert() #endif o2_get_next('v'); assert(arg); assert(*types++ == 'f'); #ifndef NDEBUG o2_arg_ptr arg2 = // only needed by assert() #endif o2_get_next('f'); assert(arg2); assert(arg2 == arg); assert(arg->v.len == arg_count); assert(arg->v.typ == 'f'); for (int i = 0; i < arg_count; i++) { #ifndef NDEBUG float correct = 123.456F + i; // only used by asserts #endif assert(arg->v.vf); assert(arg->v.vf[i] == correct); } assert(*types == 0); // got all of typestring got_the_message = TRUE; } // 9. sending typestring vh (with length 0 to 100) void service_vh(o2_msg_data_ptr data, const char *types, o2_arg_ptr *argv, int argc, void *user_data) { o2_extract_start(data); assert(*types++ == 'v'); #ifndef NDEBUG o2_arg_ptr arg = // only needed by assert() #endif o2_get_next('v'); assert(arg); assert(*types++ == 'h'); #ifndef NDEBUG o2_arg_ptr arg2 = // only needed by assert() #endif o2_get_next('h'); assert(arg2); assert(arg2 == arg); assert(arg->v.len == arg_count); assert(arg->v.typ == 'h'); for (int i = 0; i < arg_count; i++) { #ifndef NDEBUG int64_t correct = 123456 + i; // only used by asserts #endif assert(arg->v.vh); assert(arg->v.vh[i] == correct); } assert(*types == 0); // got all of typestring got_the_message = TRUE; } // 10. sending typestring vd (with length 0 to 100) void service_vd(o2_msg_data_ptr data, const char *types, o2_arg_ptr *argv, int argc, void *user_data) { o2_extract_start(data); assert(*types++ == 'v'); #ifndef NDEBUG o2_arg_ptr arg = // only needed by assert() #endif o2_get_next('v'); assert(arg); assert(*types++ == 'd'); #ifndef NDEBUG o2_arg_ptr arg2 = // only needed by assert() #endif o2_get_next('d'); assert(arg2); assert(arg2 == arg); assert(arg->v.len == arg_count); assert(arg->v.typ == 'd'); for (int i = 0; i < arg_count; i++) { #ifndef NDEBUG double correct = 1234.567 + i; #endif assert(arg->v.vd); assert(arg->v.vd[i] == correct); } assert(*types == 0); // got all of typestring got_the_message = TRUE; } // 12. sending typestring ifv?if (with vector length 0 to 100) // (this last test is an extra check for embedded vectors) void service_ifvxif(o2_msg_data_ptr data, const char *types, o2_arg_ptr *argv, int argc, void *user_data) { o2_extract_start(data); icheck(*types++, 2345); fcheck(*types++, 345.67F); assert(*types++ == 'v'); #ifndef NDEBUG o2_arg_ptr arg = // only needed by assert() #endif o2_get_next('v'); assert(arg); assert(*types++ == xtype); #ifndef NDEBUG o2_arg_ptr arg2 = // only needed by assert() #endif o2_get_next(xtype); assert(arg2); assert(arg2 == arg); assert(arg->v.len == arg_count); assert(arg->v.typ == xtype); for (int i = 0; i < arg_count; i++) { assert(arg->v.vd); switch (xtype) { case 'i': { assert(arg->v.vi[i] == 1234 + i); break; } case 'h': { assert(arg->v.vh[i] == 123456 + i); break; } case 'f': { assert(arg->v.vf[i] == 123.456F + i); break; } case 'd': { assert(arg->v.vd[i] == 1234.567 + i); break; } default: assert(FALSE); break; } } icheck(*types++, 4567); fcheck(*types++, 567.89F); assert(*types == 0); // got all of typestring got_the_message = TRUE; } // 13. sending typestring vivd (with lenghts 0 to 100) // (another test to look for bugs in allocation, receiving multiple // vectors in one message) void service_vivd(o2_msg_data_ptr data, const char *types, o2_arg_ptr *argv, int argc, void *user_data) { o2_extract_start(data); assert(*types++ == 'v'); #ifndef NDEBUG o2_arg_ptr arg = // only needed by assert() #endif o2_get_next('v'); assert(arg); assert(*types++ == 'i'); #ifndef NDEBUG o2_arg_ptr arg2 = // only needed by assert() #endif o2_get_next('i'); assert(arg2); assert(arg2 == arg); assert(arg->v.len == arg_count); assert(arg->v.typ == 'i'); for (int i = 0; i < arg_count; i++) { assert(arg->v.vi); assert(arg->v.vi[i] == 1234 + i); } assert(*types++ == 'v'); #ifndef NDEBUG arg = // only needed by assert() #endif o2_get_next('v'); assert(arg); assert(*types++ == 'd'); #ifndef NDEBUG arg2 = // only needed by assert() #endif o2_get_next('d'); assert(arg2); assert(arg2 == arg); assert(arg->v.len == arg_count); assert(arg->v.typ == 'd'); for (int i = 0; i < arg_count; i++) { assert(arg->v.vi); assert(arg->v.vd[i] == 1234.567 + i); } assert(*types == 0); // got all of typestring got_the_message = TRUE; } // 14. sending i[xxxx...]i where x is in ihfdt and there are 0 to 100 // of them AND the data is received as a vector using coercion void service_coerce(o2_msg_data_ptr data, const char *types, o2_arg_ptr *argv, int argc, void *user_data) { o2_extract_start(data); icheck(*types++, 5678); #ifndef NDEBUG o2_arg_ptr arg = // only needed by assert() #endif o2_get_next('v'); assert(*types++ == '['); #ifndef NDEBUG o2_arg_ptr arg2 = // only needed by assert() #endif o2_get_next(ytype); assert(arg2); assert(arg2 == arg); assert(arg->v.len == arg_count); assert(arg->v.typ == ytype); for (int i = 0; i < arg_count; i++) { assert(arg->v.vi); double expected; switch (xtype) { case 'i': expected = 543 + i; break; case 'h': expected = 543 + i; break; case 'f': expected = (float) (543.21 + i); break; case 'd': expected = 543.21 + i; break; } switch (ytype) { case 'i': assert(arg->v.vi[i] == (int32_t) expected); break; case 'h': assert(arg->v.vh[i] == (int64_t) expected); break; case 'f': assert(arg->v.vf[i] == (float) expected); break; case 'd': assert(arg->v.vd[i] == expected); break; default: assert(FALSE); } assert(*types++ == xtype); } assert(*types++ == ']'); #ifndef NDEBUG arg2 = // only needed by assert() #endif o2_get_next(']'); assert(arg2 == o2_got_end_array); icheck(*types++, 6789); assert(*types == 0); // got all of typestring got_the_message = TRUE; } // 15. sending ivxi where x is in ihfdt and there are 0 to 100 // of them AND the data is received as an array using coercion void service_coerce2(o2_msg_data_ptr data, const char *types, o2_arg_ptr *argv, int argc, void *user_data) { o2_extract_start(data); icheck(*types++, 5678); fcheck(*types++, 567.89F); assert(*types++ == 'v'); assert(*types++ == xtype); o2_arg_ptr arg = o2_get_next('['); assert(arg); for (int i = 0; i < arg_count; i++) { arg = o2_get_next(ytype); assert(arg); double expected; switch (xtype) { case 'i': { expected = 1234 + i; break; } case 'h': { expected = 123456 + i; break; } case 'f': { expected = 123.456F + i; break; } case 'd': { expected = 1234.567 + i; break; } default: assert(FALSE); break; } switch (ytype) { case 'i': assert(arg->i == (int32_t) expected); break; case 'h': assert(arg->h == (int64_t) expected); break; case 'f': assert(arg->f == (float) expected); break; case 'd': case 't': assert(arg->d == expected); break; default: assert(FALSE); } } #ifndef NDEBUG arg = // only needed by assert() #endif o2_get_next(']'); assert(arg == o2_got_end_array); icheck(*types++, 6789); fcheck(*types++, 567.89F); assert(*types == 0); // got all of typestring got_the_message = TRUE; } void send_the_message() { for (int i = 0; i < 1000000; i++) { if (got_the_message) break; o2_poll(); } assert(got_the_message); got_the_message = FALSE; } // add a parameter of type xtype void add_x_parameter() { switch (xtype) { case O2_INT32: o2_add_int32(1234); break; case O2_INT64: o2_add_int64(12345); break; case O2_FLOAT: o2_add_float(1234.56F); break; case O2_DOUBLE: o2_add_double(1234.567); break; case O2_TIME: o2_add_time(2345.678); break; case O2_CHAR: o2_add_char('$'); break; case O2_BOOL: o2_add_bool(TRUE); break; case O2_TRUE: o2_add_true(); break; case O2_FALSE: o2_add_false(); break; case O2_INFINITUM: o2_add_infinitum(); break; case O2_NIL: o2_add_nil(); break; case O2_BLOB: o2_add_blob(a_blob); break; case O2_STRING: o2_add_string("This is a string"); break; case O2_SYMBOL: o2_add_symbol("This is a symbol"); break; case O2_MIDI: o2_add_midi(a_midi_msg); break; default: assert(FALSE); } return; } int main(int argc, const char * argv[]) { a_blob = malloc(20); a_blob->size = 15; memcpy(a_blob->data, "This is a blob", 15); a_midi_msg = (0x90 << 16) + (60 << 8) + 100; o2_initialize("test"); o2_service_new("one"); o2_method_new("/one/service_ai", "[i]", &service_ai, NULL, FALSE, FALSE); o2_method_new("/one/service_a", "[]", &service_a, NULL, FALSE, FALSE); o2_method_new("/one/service_aii", "[ii]", &service_aii, NULL, FALSE, FALSE); // [xixdx] where x is one of: hfcBbtsSmTFIN char *xtypes = "ihfdcBbtsSmTFIN"; for (char *xtp = xtypes; *xtp; xtp++) { xtype = *xtp; char type_string[32]; snprintf(type_string, 32, "[%ci%cd%c]", xtype, xtype, xtype); char address[32]; snprintf(address, 32, "/one/service_%ci%cd%c", xtype, xtype, xtype); o2_method_new(address, type_string, &service_xixdx, NULL, FALSE, FALSE); } o2_method_new("/one/service_2arrays", "i[ih][fdt]d", &service_2arrays, NULL, FALSE, FALSE); // use NULL for type string to disable type-string checking o2_method_new("/one/service_bigarray", NULL, &service_bigarray, NULL, FALSE, FALSE); o2_method_new("/one/service_vi", NULL, &service_vi, NULL, FALSE, FALSE); o2_method_new("/one/service_vf", NULL, &service_vf, NULL, FALSE, FALSE); o2_method_new("/one/service_vh", NULL, &service_vh, NULL, FALSE, FALSE); o2_method_new("/one/service_vd", NULL, &service_vd, NULL, FALSE, FALSE); o2_method_new("/one/service_ifvxif", NULL, &service_ifvxif, NULL, FALSE, FALSE); o2_method_new("/one/service_vivd", NULL, &service_vivd, NULL, FALSE, FALSE); o2_method_new("/one/service_coerce", NULL, &service_coerce, NULL, FALSE, FALSE); o2_method_new("/one/service_coerce2", NULL, &service_coerce2, NULL, FALSE, FALSE); o2_send_start(); o2_add_start_array(); o2_add_int32(3456); o2_add_end_array(); o2_send_finish(0, "/one/service_ai", TRUE); send_the_message(); printf("DONE sending [3456]\n"); o2_send_start(); o2_add_start_array(); o2_add_end_array(); o2_send_finish(0, "/one/service_a", TRUE); send_the_message(); printf("DONE sending []\n"); o2_send_start(); o2_add_start_array(); o2_add_int32(123); o2_add_int32(234); o2_add_end_array(); o2_send_finish(0, "/one/service_aii", TRUE); send_the_message(); printf("DONE sending [123, 234]\n"); // 4. sending typestring [xixdx] where x is one of: hfcBbtsSmTFIN for (char *xtp = xtypes; *xtp; xtp++) { xtype = *xtp; o2_send_start(); o2_add_start_array(); add_x_parameter(); o2_add_int32(456); add_x_parameter(); o2_add_double(234.567); add_x_parameter(); o2_add_end_array(); char address[32]; snprintf(address, 32, "/one/service_%ci%cd%c", xtype, xtype, xtype); o2_send_finish(0, address, TRUE); send_the_message(); } printf("DONE sending [xixdx] messages\n"); // 5. sending typestring i[ih][fdt]d to test multiple arrays o2_send_start(); o2_add_int32(456); o2_add_start_array(); o2_add_int32(1234); o2_add_int64(12345); o2_add_end_array(); o2_add_start_array(); o2_add_float(1234.56F); o2_add_double(1234.567); o2_add_time(2345.678); o2_add_end_array(); o2_add_double(1234.567); o2_send_finish(0, "/one/service_2arrays", TRUE); send_the_message(); printf("DONE sending 456,[456,12345][1234.56,1234.567,2345.678],1234.567\n"); // 6. sending typestring [ddddd...] where there are 1 to 100 d's for (int i = 0; i < 101; i++) { arg_count = i; o2_send_start(); o2_add_start_array(); for (int j = 0; j < i; j++) { o2_add_double(123.456 + j); } o2_add_end_array(); o2_send_finish(0, "/one/service_bigarray", TRUE); send_the_message(); } printf("DONE sending [ddd...], size 0 through 100\n"); // 7. sending typestring vi (with length 0 to 100) int ivec[102]; for (int j = 0; j < 102; j++) { ivec[j] = 1234 + j; } for (int i = 0; i < 101; i++) { arg_count = i; o2_send_start(); o2_add_vector('i', i, ivec); o2_send_finish(0, "/one/service_vi", TRUE); send_the_message(); } printf("DONE sending vi, size 0 through 100\n"); // 8. sending typestring vf (with length 0 to 100) float fvec[102]; for (int j = 0; j < 102; j++) { fvec[j] = 123.456F + j; } for (int i = 0; i < 101; i++) { arg_count = i; o2_send_start(); o2_add_vector('f', i, fvec); o2_send_finish(0, "/one/service_vf", TRUE); send_the_message(); } printf("DONE sending vf, size 0 through 100\n"); // 9. sending typestring vh (with length 0 to 100) int64_t hvec[102]; for (int j = 0; j < 102; j++) { hvec[j] = 123456 + j; } for (int i = 0; i < 101; i++) { arg_count = i; o2_send_start(); o2_add_vector('h', i, hvec); o2_send_finish(0, "/one/service_vh", TRUE); send_the_message(); } printf("DONE sending vh, size 0 through 100\n"); // 10. sending typestring vd (with length 0 to 100) double dvec[102]; for (int j = 0; j < 102; j++) { dvec[j] = 1234.567 + j; } for (int i = 0; i < 101; i++) { arg_count = i; o2_send_start(); o2_add_vector('d', i, dvec); o2_send_finish(0, "/one/service_vd", TRUE); send_the_message(); } printf("DONE sending vd, size 0 through 100\n"); // 12. sending typestring ifvxif (with length 0 to 100) for (char *xtp = "ihfd"; *xtp; xtp++) { xtype = *xtp; for (int i = 0; i < 101; i++) { o2_send_start(); o2_add_int32(2345); o2_add_float(345.67F); arg_count = i; switch (xtype) { case 'i': o2_add_vector('i', i, ivec); break; case 'h': o2_add_vector('h', i, hvec); break; case 'f': o2_add_vector('f', i, fvec); break; case 'd': o2_add_vector('d', i, dvec); break; default: assert(FALSE); } o2_add_int32(4567); o2_add_float(567.89F); o2_send_finish(0, "/one/service_ifvxif", TRUE); send_the_message(); } } printf("DONE sending ifvxif, types ihfd, size 0 through 100\n"); // 13. sending typestring vivd (with length 0 to 100) for (int i = 0; i < 101; i++) { o2_send_start(); arg_count = i; o2_add_vector('i', i, ivec); o2_add_vector('d', i, dvec); o2_send_finish(0, "/one/service_vivd", TRUE); send_the_message(); } printf("DONE sending vivd, size 0 through 100\n"); // 14. sending i[xxxx...]i where x is in ihfdt and there are 0 to 100 // of them AND the data is received as a vector using coercion for (char *xtp = "ihfd"; *xtp; xtp++) { xtype = *xtp; for (char *ytp = "ihfd"; *ytp; ytp++) { ytype = *ytp; for (int i = 0; i < 101; i++) { o2_send_start(); o2_add_int32(5678); arg_count = i; o2_add_start_array(); for (int j = 0; j < i; j++) { switch (xtype) { case 'i': o2_add_int32(543 + j); break; case 'h': o2_add_int64(543 + j); break; case 'f': o2_add_float(543.21F + j); break; case 'd': o2_add_double(543.21 + j); break; default: assert(FALSE); } } o2_add_end_array(); o2_add_int32(6789); o2_send_finish(0, "/one/service_coerce", TRUE); send_the_message(); } } } printf("DONE sending ifvxif, types ihfdt, size 0 through 100\n"); // 15. sending ivxi where x is in ihfd and there are 0 to 100 // of them AND the data is received as an array using coercion for (char *x = "ihfd"; *x; x++) { xtype = *x; for (char *y = "ihfdt"; *y; y++) { ytype = *y; for (int i = 0; i < 101; i++) { o2_send_start(); o2_add_int32(5678); o2_add_float(567.89F); arg_count = i; switch (xtype) { case 'i': o2_add_vector('i', i, ivec); break; case 'h': o2_add_vector('h', i, hvec); break; case 'f': o2_add_vector('f', i, fvec); break; case 'd': o2_add_vector('d', i, dvec); break; default: assert(FALSE); } o2_add_int32(6789); o2_add_float(567.89F); o2_send_finish(0, "/one/service_coerce2", TRUE); send_the_message(); } } } printf("DONE\n"); o2_finish(); return 0; } o2-1.0/test/broadcastserver.c0000644000175000017500000001125713072261166016452 0ustar zmoelnigzmoelnig/* udp-broadcast-server.c: * udp broadcast server example * Example Stock Index Broadcast: */ #ifdef WIN32 #include #include #else #include #include #include #include #include #endif #include #include #include #include #include #include #ifndef TRUE #define TRUE 1 #define FALSE 0 #endif #define MAXQ 4 static struct { char *index; int start; int volit; int current; } quotes[] = { { "DJIA", 1030330, 375 }, { "NASDAQ", 276175, 125 }, { "S&P 500", 128331, 50 }, { "TSE 300", 689572, 75 }, }; /* * Initialize: */ static void initialize(void) { short x; time_t td; /* * Seed the random number generator: */ time(&td); srand((int)td); for ( x=0; x < MAXQ; ++x ) quotes[x].current = quotes[x].start; } /* * Randomly change one index quotation: */ static void gen_quote(void) { short x; /* Index */ short v; /* Volatility of index */ short h; /* Half of v */ short r; /* Random change */ x = rand() % MAXQ; v = quotes[x].volit; h = (v / 2) - 2; r = rand() % v; if ( r < h ) r = -r; quotes[x].current += r; } /* * This function reports the error and * exits back to the shell: */ static void displayError(const char *on_what) { fputs(strerror(errno),stderr); fputs(": ",stderr); fputs(on_what,stderr); fputc('\n',stderr); exit(1); } int main(int argc, char **argv) { short x; /* index of Stock Indexes */ double I0; /* Initial index value */ double I; /* Index value */ char bcbuf[512], *bp;/* Buffer and ptr */ int z; /* Status return code */ int broadcast_sock; /* Socket */ int local_send_sock; /* Socket */ struct sockaddr_in broadcast_to_addr; /* AF_INET */ struct sockaddr_in local_to_addr; /* AF_INET */ static int so_broadcast = TRUE; /* * Form the dest address: */ memset(&broadcast_to_addr, 0, sizeof(broadcast_to_addr)); #ifndef WIN32 broadcast_to_addr.sin_len = sizeof(broadcast_to_addr); #endif broadcast_to_addr.sin_family = AF_INET; if (inet_pton(AF_INET, "255.255.255.255", &(broadcast_to_addr.sin_addr.s_addr)) != 1) { displayError("inet_pton"); } broadcast_to_addr.sin_port = htons(8124); /* * Create a UDP socket to use: */ broadcast_sock = socket(AF_INET, SOCK_DGRAM, 0); if (broadcast_sock == -1) displayError("socket()"); /* * Allow broadcasts: */ z = setsockopt(broadcast_sock, SOL_SOCKET, SO_BROADCAST, (const char *) &so_broadcast, sizeof so_broadcast); if (z == -1) displayError("setsockopt(SO_BROADCAST)"); /* * Form the local dest address: */ #ifndef WIN32 local_to_addr.sin_len = sizeof(broadcast_to_addr); #endif local_to_addr.sin_family = AF_INET; if (inet_pton(AF_INET, "127.0.0.1", &(local_to_addr.sin_addr.s_addr)) != 1) { displayError("inet_pton"); } local_to_addr.sin_port = htons(8123); /* * Create a UDP socket to use: */ local_send_sock = socket(AF_INET, SOCK_DGRAM, 0); if (local_send_sock == -1) displayError("socket()"); /* * Now start serving quotes: */ initialize(); for (;;) { /* * Update one quote in the list: */ gen_quote(); /* * Form a packet to send out: */ bp = bcbuf; for (x = 0; x < MAXQ; ++x) { I0 = quotes[x].start / 100.0; I = quotes[x].current / 100.0; sprintf(bp, "%-7.7s %8.2f %+.2f\n", quotes[x].index, I, I - I0); bp += strlen(bp); } /* * Broadcast the updated info: */ z = sendto(broadcast_sock, bcbuf, strlen(bcbuf), 0, (struct sockaddr *)&broadcast_to_addr, sizeof(broadcast_to_addr)); if (z == -1) displayError("broadcast sendto()"); z = sendto(local_send_sock, bcbuf, strlen(bcbuf), 0, (struct sockaddr *)&local_to_addr, sizeof(broadcast_to_addr)); if (z == -1) displayError("local sendto()"); #ifdef WIN32 Sleep(4); #else sleep(4) #endif } return 0; } o2-1.0/test/oscsendtest.c0000644000175000017500000000424513072261166015616 0ustar zmoelnigzmoelnig// oscsendtest.c - test o2_osc_delegate() // // this test is designed to run with oscrecvtest.c #include "o2.h" #include "stdio.h" #include "string.h" #include "assert.h" #ifdef WIN32 #include "usleep.h" // special windows implementation of sleep/usleep #else #include #endif int main(int argc, const char * argv[]) { printf("Usage: oscsendtest [flags] (see o2.h for flags, " "use a for all, also u for UDP, M for master)\n"); int tcpflag = TRUE; int master = FALSE; if (argc == 2) { o2_debug_flags(argv[1]); tcpflag = (strchr(argv[1], 'u') == NULL); master = (strchr(argv[1], 'M') != NULL); } if (argc > 2) { printf("WARNING: o2server ignoring extra command line argments\n"); } printf("tcpflag %d master %d\n", tcpflag, master); o2_initialize("test"); // you can make this run without an O2 server by passing "master" if (master) o2_clock_set(NULL, NULL); else if (argc > 2) printf("Usage: oscsendtest [master]\n"); if (master) sleep(2); // wait for liblo server to come up if we are master printf("Waiting for clock sync\n"); while (!o2_clock_is_synchronized) { usleep(2000); o2_poll(); } int err = o2_osc_delegate("oscsend", "localhost", 8100, tcpflag); assert(err == O2_SUCCESS); // send 12 messages, 1 every 0.5s, and stop for (int n = 0; n < 12; n++) { err = o2_send("/oscsend/i", 0, "i", 1234); assert(err == O2_SUCCESS); printf("sent 1234 to /oscsend/i\n"); // pause for 0.5s, but keep running O2 by polling for (int i = 0; i < 250; i++) { o2_poll(); usleep(2000); // 2ms } } // send 10 messages with timestamps spaced by 0.1s o2_time now = o2_time_get(); for (int n = 0; n < 10; n++) { o2_send("/oscsend/i", now + n * 0.1, "i", 2000 + n); } // pause for 1s to make sure messages are sent for (int i = 0; i < 500; i++) { o2_poll(); usleep(2000); // 2ms } o2_service_free("oscsend"); o2_finish(); sleep(1); // finish closing sockets printf("OSCSEND DONE\n"); return 0; } o2-1.0/test/clockmaster.c0000644000175000017500000000361413072261166015566 0ustar zmoelnigzmoelnig// clockmaster.c - clock synchronization test/demo // // This program works with clockslave.c. It monitors clock // synchronization and status updates. // #include "o2.h" #include "stdio.h" #include "string.h" #ifdef WIN32 #include "usleep.h" // special windows implementation of sleep/usleep #else #include #endif o2_time cs_time = 1000000.0; // this is a handler that polls for current status // void clockmaster(o2_msg_data_ptr msg, const char *types, o2_arg_ptr *argv, int argc, void *user_data) { int ss = o2_status("server"); int cs = o2_status("client"); printf("clockmaster: local time %g global time %g " "server status %d client status %d\n", o2_local_time(), o2_time_get(), ss, cs); // record when the client synchronizes if (cs == O2_REMOTE) { if (o2_time_get() < cs_time) { cs_time = o2_time_get(); printf("clockmaster sync time %g\n", cs_time); } } // stop 10s later if (o2_time_get() > cs_time + 10) { o2_stop_flag = TRUE; printf("clockmaster set stop flag TRUE at %g\n", o2_time_get()); } o2_send("!server/clockmaster", o2_time_get() + 1, ""); } int main(int argc, const char * argv[]) { printf("Usage: clockmaster [debugflags] " "(see o2.h for flags, use a for all)\n"); if (argc == 2) { o2_debug_flags(argv[1]); printf("debug flags are: %s\n", argv[1]); } if (argc > 2) { printf("WARNING: clockmaster ignoring extra command line argments\n"); } o2_initialize("test"); o2_service_new("server"); o2_method_new("/server/clockmaster", "", &clockmaster, NULL, FALSE, FALSE); // we are the master clock o2_clock_set(NULL, NULL); o2_send("!server/clockmaster", 0.0, ""); // start polling o2_run(100); o2_finish(); sleep(1); printf("CLOCKMASTER DONE\n"); return 0; } o2-1.0/test/tcpclient.c0000644000175000017500000000535213072261166015245 0ustar zmoelnigzmoelnig// tcpclient.c - O2 over tcp check, and part of performance benchmark // // see tcpserver.c for details #include "o2.h" #include "stdio.h" #include "string.h" #include "assert.h" #ifdef WIN32 #include "usleep.h" // special windows implementation of sleep/usleep #else #include #endif #define N_ADDRS 20 int max_msg_count = 50000; char *server_addresses[N_ADDRS]; int msg_count = 0; int running = TRUE; void client_test(o2_msg_data_ptr data, const char *types, o2_arg_ptr *argv, int argc, void *user_data) { msg_count++; int32_t i = msg_count + 1; // server will shut down when it gets data == -1 if (msg_count >= max_msg_count) { i = -1; running = FALSE; } o2_send_cmd(server_addresses[msg_count % N_ADDRS], 0, "i", i); if (msg_count % 10000 == 0) { printf("client received %d messages\n", msg_count); } if (msg_count < 100) { printf("client message %d is %d\n", msg_count, argv[0]->i32); } assert(msg_count == argv[0]->i32); } int main(int argc, const char * argv[]) { printf("Usage: tcpclient [msgcount [flags]] " "(see o2.h for flags, use a for all)\n"); if (argc >= 2) { max_msg_count = atoi(argv[1]); printf("max_msg_count set to %d\n", max_msg_count); } if (argc >= 3) { o2_debug_flags(argv[2]); printf("debug flags are: %s\n", argv[2]); } if (argc > 3) { printf("WARNING: tcpclient ignoring extra command line arguments\n"); } o2_initialize("test"); o2_service_new("client"); for (int i = 0; i < N_ADDRS; i++) { char path[100]; sprintf(path, "/client/benchmark/%d", i); o2_method_new(path, "i", &client_test, NULL, FALSE, TRUE); } for (int i = 0; i < N_ADDRS; i++) { char path[100]; sprintf(path, "!server/benchmark/%d", i); server_addresses[i] = (char *) (O2_MALLOC(strlen(path))); strcpy(server_addresses[i], path); } while (o2_status("server") < O2_LOCAL) { o2_poll(); usleep(2000); // 2ms } printf("We discovered the server.\ntime is %g.\n", o2_time_get()); double now = o2_time_get(); while (o2_time_get() < now + 1) { o2_poll(); usleep(2000); } printf("Here we go! ...\ntime is %g.\n", o2_time_get()); o2_send_cmd("!server/benchmark/0", 0, "i", 1); while (running) { o2_poll(); //usleep(2000); // 2ms // as fast as possible } // poll some more to make sure last message goes out for (int i = 0; i < 100; i++) { o2_poll(); usleep(2000); // 2ms } o2_finish(); sleep(1); // finish cleaning up sockets printf("CLIENT DONE\n"); return 0; } o2-1.0/test/o2server.c0000644000175000017500000000562013072261166015025 0ustar zmoelnigzmoelnig// o2server.c - benchmark for local message passing // // This program works with o2client.c. It is a performance test // that sends a message back and forth between a client and server. // #include "o2.h" #include "stdio.h" #include "string.h" #include "assert.h" #ifdef WIN32 #include "usleep.h" // special windows implementation of sleep/usleep #else #include #endif // To put some weight on fast address lookup, we create N_ADDRS // different addresses to use. // #define N_ADDRS 20 #define MAX_MSG_COUNT 50000 char *client_addresses[N_ADDRS]; int msg_count = 0; int running = TRUE; // this is a handler for incoming messages. It simply sends a message // back to one of the client addresses // void server_test(o2_msg_data_ptr msg, const char *types, o2_arg_ptr *argv, int argc, void *user_data) { assert(argc == 1); msg_count++; o2_send(client_addresses[msg_count % N_ADDRS], 0, "i", msg_count); if (msg_count % 10000 == 0) { printf("server received %d messages\n", msg_count); } if (msg_count < 100) { printf("server message %d is %d\n", msg_count, argv[0]->i32); } if (argv[0]->i32 == -1) { running = FALSE; } else { assert(msg_count == argv[0]->i32); } } int main(int argc, const char *argv[]) { printf("Usage: o2server [debugflags] " "(see o2.h for flags, use a for all)\n"); if (argc == 2) { o2_debug_flags(argv[1]); printf("debug flags are: %s\n", argv[1]); } if (argc > 2) { printf("WARNING: o2server ignoring extra command line argments\n"); } o2_initialize("test"); o2_service_new("server"); // add our handler for incoming messages to each server address for (int i = 0; i < N_ADDRS; i++) { char path[100]; sprintf(path, "/server/benchmark/%d", i); o2_method_new(path, "i", &server_test, NULL, FALSE, TRUE); } // create an address for each destination so we do not have to // do string manipulation to send a message for (int i = 0; i < N_ADDRS; i++) { char path[100]; sprintf(path, "!client/benchmark/%d", i); client_addresses[i] = (char *) (O2_MALLOC(strlen(path))); strcpy(client_addresses[i], path); } // we are the master clock o2_clock_set(NULL, NULL); // wait for client service to be discovered while (o2_status("client") < O2_REMOTE) { o2_poll(); usleep(2000); // 2ms } printf("We discovered the client at time %g.\n", o2_time_get()); // delay 1 second double now = o2_time_get(); while (o2_time_get() < now + 1) { o2_poll(); usleep(2000); } printf("Here we go! ...\ntime is %g.\n", o2_time_get()); while (running) { o2_poll(); //usleep(2000); // 2ms // as fast as possible } o2_finish(); printf("SERVER DONE\n"); return 0; } o2-1.0/test/typestest.c0000644000175000017500000005025013072261166015321 0ustar zmoelnigzmoelnig// typestest.c -- send messages of all (but vector and array) types // #include #include "o2.h" #include "assert.h" #include "string.h" int got_the_message = FALSE; o2_blob_ptr a_blob; uint32_t a_midi_msg; void service_none(o2_msg_data_ptr data, const char *types, o2_arg_ptr *argv, int argc, void *user_data) { o2_extract_start(data); assert(strcmp(types, "") == 0); printf("service_none types=%s\n", types); got_the_message = TRUE; } void service_nonep(o2_msg_data_ptr data, const char *types, o2_arg_ptr *argv, int argc, void *user_data) { assert(strcmp(types, "") == 0); assert(argc == 0); printf("service_ip types=%s\n", types); got_the_message = TRUE; } void service_i(o2_msg_data_ptr data, const char *types, o2_arg_ptr *argv, int argc, void *user_data) { o2_extract_start(data); assert(strcmp(types, "i") == 0); o2_arg_ptr arg = o2_get_next('i'); assert(arg->i == 1234); printf("service_i types=%s int32=%d\n", types, arg->i); got_the_message = TRUE; } void service_ip(o2_msg_data_ptr data, const char *types, o2_arg_ptr *argv, int argc, void *user_data) { assert(strcmp(types, "i") == 0); assert(argc == 1); assert(argv[0]->i == 1234); printf("service_ip types=%s int32=%d\n", types, argv[0]->i); got_the_message = TRUE; } void service_c(o2_msg_data_ptr data, const char *types, o2_arg_ptr *argv, int argc, void *user_data) { o2_extract_start(data); assert(strcmp(types, "c") == 0); o2_arg_ptr arg = o2_get_next('c'); assert(arg->c == 'Q'); printf("service_c types=%s char=%c\n", types, arg->c); got_the_message = TRUE; } void service_cp(o2_msg_data_ptr data, const char *types, o2_arg_ptr *argv, int argc, void *user_data) { assert(strcmp(types, "c") == 0); assert(argc == 1); assert(argv[0]->c == 'Q'); printf("service_cp types=%s char=%c\n", types, argv[0]->c); got_the_message = TRUE; } void service_B(o2_msg_data_ptr data, const char *types, o2_arg_ptr *argv, int argc, void *user_data) { o2_extract_start(data); assert(strcmp(types, "B") == 0); o2_arg_ptr arg = o2_get_next('B'); assert(arg->B == TRUE); printf("service_B types=%s bool=%d\n", types, arg->B); got_the_message = TRUE; } void service_Bp(o2_msg_data_ptr data, const char *types, o2_arg_ptr *argv, int argc, void *user_data) { assert(strcmp(types, "B") == 0); assert(argc == 1); assert(argv[0]->B == TRUE); printf("service_Bp types=%s bool=%d\n", types, argv[0]->B); got_the_message = TRUE; } void service_h(o2_msg_data_ptr data, const char *types, o2_arg_ptr *argv, int argc, void *user_data) { o2_extract_start(data); assert(strcmp(types, "h") == 0); o2_arg_ptr arg = o2_get_next('h'); assert(arg->h == 12345); // long long "coercion" to make gcc happy printf("service_h types=%s int64=%lld\n", types, (long long) arg->h); got_the_message = TRUE; } void service_hp(o2_msg_data_ptr data, const char *types, o2_arg_ptr *argv, int argc, void *user_data) { assert(strcmp(types, "h") == 0); assert(argc == 1); assert(argv[0]->h == 12345); // long long "coercion" to make gcc happy printf("service_hp types=%s int64=%lld\n", types, (long long) argv[0]->h); got_the_message = TRUE; } void service_f(o2_msg_data_ptr data, const char *types, o2_arg_ptr *argv, int argc, void *user_data) { o2_extract_start(data); assert(strcmp(types, "f") == 0); o2_arg_ptr arg = o2_get_next('f'); assert(arg->f == 1234.5); printf("service_f types=%s float=%g\n", types, arg->f); got_the_message = TRUE; } void service_fp(o2_msg_data_ptr data, const char *types, o2_arg_ptr *argv, int argc, void *user_data) { assert(strcmp(types, "f") == 0); assert(argc == 1); assert(argv[0]->f == 1234.5); printf("service_fp types=%s float=%g\n", types, argv[0]->f); got_the_message = TRUE; } void service_d(o2_msg_data_ptr data, const char *types, o2_arg_ptr *argv, int argc, void *user_data) { o2_extract_start(data); assert(strcmp(types, "d") == 0); o2_arg_ptr arg = o2_get_next('d'); assert(arg->d == 1234.56); printf("service_d types=%s double=%g\n", types, arg->d); got_the_message = TRUE; } void service_dp(o2_msg_data_ptr data, const char *types, o2_arg_ptr *argv, int argc, void *user_data) { assert(strcmp(types, "d") == 0); assert(argc == 1); assert(argv[0]->d == 1234.56); printf("service_dp types=%s double=%g\n", types, argv[0]->d); got_the_message = TRUE; } void service_t(o2_msg_data_ptr data, const char *types, o2_arg_ptr *argv, int argc, void *user_data) { o2_extract_start(data); assert(strcmp(types, "t") == 0); o2_arg_ptr arg = o2_get_next('t'); assert(arg->t == 1234.567); printf("service_t types=%s time=%g\n", types, arg->t); got_the_message = TRUE; } void service_tp(o2_msg_data_ptr data, const char *types, o2_arg_ptr *argv, int argc, void *user_data) { assert(strcmp(types, "t") == 0); assert(argc == 1); assert(argv[0]->t == 1234.567); printf("service_tp types=%s time=%g\n", types, argv[0]->t); got_the_message = TRUE; } void service_s(o2_msg_data_ptr data, const char *types, o2_arg_ptr *argv, int argc, void *user_data) { o2_extract_start(data); assert(strcmp(types, "s") == 0); o2_arg_ptr arg = o2_get_next('s'); assert(strcmp(arg->s, "1234") == 0); printf("service_s types=%s string=%s\n", types, arg->s); got_the_message = TRUE; } void service_sp(o2_msg_data_ptr data, const char *types, o2_arg_ptr *argv, int argc, void *user_data) { assert(strcmp(types, "s") == 0); assert(argc == 1); assert(strcmp(argv[0]->s, "1234") == 0); printf("service_sp types=%s string=%s\n", types, argv[0]->s); got_the_message = TRUE; } void service_S(o2_msg_data_ptr data, const char *types, o2_arg_ptr *argv, int argc, void *user_data) { o2_extract_start(data); assert(strcmp(types, "S") == 0); o2_arg_ptr arg = o2_get_next('S'); assert(strcmp(arg->S, "123456") == 0); printf("service_S types=%s symbol=%s\n", types, arg->S); got_the_message = TRUE; } void service_Sp(o2_msg_data_ptr data, const char *types, o2_arg_ptr *argv, int argc, void *user_data) { assert(strcmp(types, "S") == 0); assert(argc == 1); assert(strcmp(argv[0]->S, "123456") == 0); printf("service_Sp types=%s symbol=%s\n", types, argv[0]->S); got_the_message = TRUE; } void service_b(o2_msg_data_ptr data, const char *types, o2_arg_ptr *argv, int argc, void *user_data) { o2_extract_start(data); assert(strcmp(types, "b") == 0); o2_arg_ptr arg = o2_get_next('b'); assert(arg->b.size = a_blob->size && memcmp(arg->b.data, a_blob->data, 15) == 0); printf("service_b types=%s blob=%p\n", types, &(arg->b)); got_the_message = TRUE; } void service_bp(o2_msg_data_ptr data, const char *types, o2_arg_ptr *argv, int argc, void *user_data) { assert(strcmp(types, "b") == 0); assert(argc == 1); assert(argv[0]->b.size = a_blob->size && memcmp(argv[0]->b.data, a_blob->data, 15) == 0); printf("service_bp types=%s blob=%p\n", types, &(argv[0]->b)); got_the_message = TRUE; } void service_m(o2_msg_data_ptr data, const char *types, o2_arg_ptr *argv, int argc, void *user_data) { o2_extract_start(data); assert(strcmp(types, "m") == 0); o2_arg_ptr arg = o2_get_next('m'); assert(arg->m == a_midi_msg); printf("service_m types=%s midi = %2x %2x %2x\n", types, (arg->m >> 16) & 0xff, (arg->m >> 8) & 0xff, arg->m & 0xff); got_the_message = TRUE; } void service_mp(o2_msg_data_ptr data, const char *types, o2_arg_ptr *argv, int argc, void *user_data) { assert(strcmp(types, "m") == 0); assert(argc == 1); o2_arg_ptr arg = argv[0]; assert(arg->m == a_midi_msg); printf("service_mp types=%s midi = %2x %2x %2x\n", types, (arg->m >> 16) & 0xff, (arg->m >> 8) & 0xff, arg->m & 0xff); got_the_message = TRUE; } void service_T(o2_msg_data_ptr data, const char *types, o2_arg_ptr *argv, int argc, void *user_data) { o2_extract_start(data); assert(strcmp(types, "T") == 0); printf("service_T types=%s\n", types); got_the_message = TRUE; } void service_Tp(o2_msg_data_ptr data, const char *types, o2_arg_ptr *argv, int argc, void *user_data) { assert(strcmp(types, "T") == 0); assert(argc == 1); printf("service_Tp types=%s\n", types); got_the_message = TRUE; } void service_F(o2_msg_data_ptr data, const char *types, o2_arg_ptr *argv, int argc, void *user_data) { o2_extract_start(data); assert(strcmp(types, "F") == 0); printf("service_F types=%s\n", types); got_the_message = TRUE; } void service_Fp(o2_msg_data_ptr data, const char *types, o2_arg_ptr *argv, int argc, void *user_data) { assert(strcmp(types, "F") == 0); assert(argc == 1); printf("service_Fp types=%s\n", types); got_the_message = TRUE; } void service_I(o2_msg_data_ptr data, const char *types, o2_arg_ptr *argv, int argc, void *user_data) { o2_extract_start(data); assert(strcmp(types, "I") == 0); printf("service_I types=%s\n", types); got_the_message = TRUE; } void service_Ip(o2_msg_data_ptr data, const char *types, o2_arg_ptr *argv, int argc, void *user_data) { assert(strcmp(types, "I") == 0); assert(argc == 1); printf("service_Ip types=%s\n", types); got_the_message = TRUE; } void service_N(o2_msg_data_ptr data, const char *types, o2_arg_ptr *argv, int argc, void *user_data) { o2_extract_start(data); assert(strcmp(types, "N") == 0); printf("service_N types=%s\n", types); got_the_message = TRUE; } void service_Np(o2_msg_data_ptr data, const char *types, o2_arg_ptr *argv, int argc, void *user_data) { assert(strcmp(types, "N") == 0); assert(argc == 1); printf("service_Np types=%s\n", types); got_the_message = TRUE; } void service_many(o2_msg_data_ptr data, const char *types, o2_arg_ptr *argv, int argc, void *user_data) { o2_extract_start(data); o2_arg_ptr arg = o2_get_next('i'); assert(arg->i == 1234); arg = o2_get_next('c'); assert(arg->c == 'Q'); arg = o2_get_next('B'); assert(arg->B == TRUE); arg = o2_get_next('h'); assert(arg->h == 12345LL); arg = o2_get_next('f'); assert(arg->f == 1234.5); arg = o2_get_next('d'); assert(arg->d == 1234.56); arg = o2_get_next('t'); assert(arg->t == 1234.567); arg = o2_get_next('s'); assert(strcmp(arg->s, "1234") == 0); arg = o2_get_next('S'); assert(strcmp(arg->S, "123456") == 0); arg = o2_get_next('b'); assert(arg->b.size = a_blob->size && memcmp(arg->b.data, a_blob->data, 15) == 0); arg = o2_get_next('m'); assert(arg->m == a_midi_msg); arg = o2_get_next('T'); assert(arg); arg = o2_get_next('F'); assert(arg); arg = o2_get_next('I'); assert(arg); arg = o2_get_next('N'); assert(arg); arg = o2_get_next('i'); assert(arg->i == 1234); assert(strcmp(types, "icBhfdtsSbmTFINi") == 0); printf("service_many types=%s\n", types); got_the_message = TRUE; } void service_manyp(o2_msg_data_ptr data, const char *types, o2_arg_ptr *argv, int argc, void *user_data) { assert(argc == 16); assert(argv[0]->i == 1234); assert(argv[1]->c == 'Q'); assert(argv[2]->B == TRUE); assert(argv[3]->h == 12345LL); assert(argv[4]->f == 1234.5); assert(argv[5]->d == 1234.56); assert(argv[6]->t == 1234.567); assert(strcmp(argv[7]->s, "1234") == 0); assert(strcmp(argv[8]->S, "123456") == 0); assert(argv[9]->b.size = a_blob->size && memcmp(argv[9]->b.data, a_blob->data, 15) == 0); assert(argv[10]->m == a_midi_msg); assert(argv[15]->i == 1234); assert(strcmp(types, "icBhfdtsSbmTFINi") == 0); printf("service_manyp types=%s\n", types); got_the_message = TRUE; } // this handles every message to service_two // we'll support two things: /two/i and /two/id void service_two(o2_msg_data_ptr msg, const char *types, o2_arg_ptr *argv, int argc, void *user_data) { o2_extract_start(msg); if (strcmp(msg->address + 1, "two/i") == 0) { o2_arg_ptr arg = o2_get_next('i'); assert(arg && arg->i == 1234); printf("service_two types=%s arg=%d\n", types, arg->i); } else if (strcmp(msg->address + 1, "two/id") == 0) { o2_arg_ptr arg = o2_get_next('i'); int i; assert(arg && arg->i == 1234); i = arg->i; arg = o2_get_next('d'); assert(arg->d == 1234.56); printf("service_two types=%s args=%d %g\n", types, i, arg->d); } else { assert(FALSE); } got_the_message = TRUE; } // this handles every message to service_two // we'll support two things: /two/i and /two/id void service_three(o2_msg_data_ptr msg, const char *types, o2_arg_ptr *argv, int argc, void *user_data) { o2_extract_start(msg); if (strcmp(msg->address + 1, "three/i") == 0) { o2_arg_ptr arg = o2_get_next('i'); assert(arg->i == 1234); printf("service_three types=%s arg=%d\n", types, arg->i); } else if (strcmp(msg->address + 1, "three/id") == 0) { o2_arg_ptr arg = o2_get_next('i'); int i; assert(arg && arg->i == 1234); i = arg->i; arg = o2_get_next('d'); assert(arg->d == 1234.56); printf("service_three types=%s args=%d %g\n", types, i, arg->d); } else { assert(FALSE); } got_the_message = TRUE; } // this handles every message to service_two // we'll support two things: /two/i and /two/id void service_four(o2_msg_data_ptr msg, const char *types, o2_arg_ptr *argv, int argc, void *user_data) { o2_extract_start(msg); if (strcmp(msg->address + 1, "four/i") == 0) { o2_arg_ptr arg = o2_get_next('i'); assert(arg->i == 1234); printf("service_four types=%s arg=%d\n", types, arg->i); } else if (strcmp(msg->address + 1, "four/id") == 0) { o2_arg_ptr arg = o2_get_next('i'); int i; assert(arg && arg->i == 1234); i = arg->i; arg = o2_get_next('d'); assert(arg->d == 1234.56); printf("service_four types=%s args=%d %g\n", types, i, arg->d); } else { assert(FALSE); } got_the_message = TRUE; } void send_the_message() { while (!got_the_message) { o2_poll(); } got_the_message = FALSE; } int main(int argc, const char * argv[]) { a_blob = malloc(20); a_blob->size = 15; memcpy(a_blob->data, "This is a blob", 15); a_midi_msg = (0x90 << 16) + (60 << 8) + 100; o2_initialize("test"); o2_service_new("one"); o2_service_new("two"); o2_service_new("three"); o2_service_new("four"); o2_method_new("/one/none", "", &service_none, NULL, FALSE, FALSE); o2_method_new("/one/nonep", "", &service_nonep, NULL, FALSE, TRUE); o2_method_new("/one/i", "i", &service_i, NULL, FALSE, FALSE); o2_method_new("/one/ip", "i", &service_ip, NULL, FALSE, TRUE); o2_method_new("/one/c", "c", &service_c, NULL, FALSE, FALSE); o2_method_new("/one/cp", "c", &service_cp, NULL, FALSE, TRUE); o2_method_new("/one/B", "B", &service_B, NULL, FALSE, FALSE); o2_method_new("/one/Bp", "B", &service_Bp, NULL, FALSE, TRUE); o2_method_new("/one/h", "h", &service_h, NULL, FALSE, FALSE); o2_method_new("/one/hp", "h", &service_hp, NULL, FALSE, TRUE); o2_method_new("/one/f", "f", &service_f, NULL, FALSE, FALSE); o2_method_new("/one/fp", "f", &service_fp, NULL, FALSE, TRUE); o2_method_new("/one/d", "d", &service_d, NULL, FALSE, FALSE); o2_method_new("/one/dp", "d", &service_dp, NULL, FALSE, TRUE); o2_method_new("/one/t", "t", &service_t, NULL, FALSE, FALSE); o2_method_new("/one/tp", "t", &service_tp, NULL, FALSE, TRUE); o2_method_new("/one/s", "s", &service_s, NULL, FALSE, FALSE); o2_method_new("/one/sp", "s", &service_sp, NULL, FALSE, TRUE); o2_method_new("/one/S", "S", &service_S, NULL, FALSE, FALSE); o2_method_new("/one/Sp", "S", &service_Sp, NULL, FALSE, TRUE); o2_method_new("/one/b", "b", &service_b, NULL, FALSE, FALSE); o2_method_new("/one/bp", "b", &service_bp, NULL, FALSE, TRUE); o2_method_new("/one/m", "m", &service_m, NULL, FALSE, FALSE); o2_method_new("/one/mp", "m", &service_mp, NULL, FALSE, TRUE); o2_method_new("/one/T", "T", &service_T, NULL, FALSE, FALSE); o2_method_new("/one/Tp", "T", &service_Tp, NULL, FALSE, TRUE); o2_method_new("/one/F", "F", &service_F, NULL, FALSE, FALSE); o2_method_new("/one/Fp", "F", &service_Fp, NULL, FALSE, TRUE); o2_method_new("/one/I", "I", &service_I, NULL, FALSE, FALSE); o2_method_new("/one/Ip", "I", &service_Ip, NULL, FALSE, TRUE); o2_method_new("/one/N", "N", &service_N, NULL, FALSE, FALSE); o2_method_new("/one/Np", "N", &service_Np, NULL, FALSE, TRUE); o2_method_new("/one/many", "icBhfdtsSbmTFINi", &service_many, NULL, FALSE, FALSE); o2_method_new("/one/manyp", "icBhfdtsSbmTFINi", &service_manyp, NULL, FALSE, TRUE); o2_method_new("/two", NULL, &service_two, NULL, FALSE, FALSE); o2_method_new("/three", "i", &service_three, NULL, FALSE, TRUE); o2_method_new("/four", "i", &service_four, NULL, TRUE, TRUE); o2_send("/one/i", 0, "i", 1234); send_the_message(); o2_send("/one/ip", 0, "i", 1234); send_the_message(); o2_send("/one/c", 0, "c", 'Q'); send_the_message(); o2_send("/one/cp", 0, "c", 'Q'); send_the_message(); o2_send("/one/B", 0, "B", TRUE); send_the_message(); o2_send("/one/Bp", 0, "B", TRUE); send_the_message(); o2_send("/one/h", 0, "h", 12345LL); send_the_message(); o2_send("/one/hp", 0, "h", 12345LL); send_the_message(); o2_send("/one/f", 0, "f", 1234.5); send_the_message(); o2_send("/one/fp", 0, "f", 1234.5); send_the_message(); o2_send("/one/d", 0, "d", 1234.56); send_the_message(); o2_send("/one/dp", 0, "d", 1234.56); send_the_message(); o2_send("/one/t", 0, "t", 1234.567); send_the_message(); o2_send("/one/tp", 0, "t", 1234.567); send_the_message(); o2_send("/one/s", 0, "s", "1234"); send_the_message(); o2_send("/one/sp", 0, "s", "1234"); send_the_message(); o2_send("/one/S", 0, "S", "123456"); send_the_message(); o2_send("/one/Sp", 0, "S", "123456"); send_the_message(); o2_send("/one/b", 0, "b", a_blob); send_the_message(); o2_send("/one/bp", 0, "b", a_blob); send_the_message(); o2_send("/one/m", 0, "m", a_midi_msg); send_the_message(); o2_send("/one/mp", 0, "m", a_midi_msg); send_the_message(); o2_send("/one/T", 0, "T"); send_the_message(); o2_send("/one/Tp", 0, "T"); send_the_message(); o2_send("/one/F", 0, "F"); send_the_message(); o2_send("/one/Fp", 0, "F"); send_the_message(); o2_send("/one/I", 0, "I"); send_the_message(); o2_send("/one/Ip", 0, "I"); send_the_message(); o2_send("/one/N", 0, "N"); send_the_message(); o2_send("/one/Np", 0, "N"); send_the_message(); o2_send("/one/many", 0, "icBhfdtsSbmTFINi", 1234, 'Q', TRUE, 12345LL, 1234.5, 1234.56, 1234.567, "1234", "123456", a_blob, a_midi_msg, 1234); send_the_message(); o2_send("/one/manyp", 0, "icBhfdtsSbmTFINi", 1234, 'Q', TRUE, 12345LL, 1234.5, 1234.56, 1234.567, "1234", "123456", a_blob, a_midi_msg, 1234); send_the_message(); o2_send("/two/i", 0, "i", 1234); send_the_message(); o2_send("!two/i", 0, "i", 1234); send_the_message(); o2_send("/two/id", 0, "id", 1234, 1234.56); send_the_message(); o2_send("/three/i", 0, "i", 1234); send_the_message(); o2_send("/four/i", 0, "d", 1234.0); send_the_message(); printf("DONE\n"); o2_finish(); return 0; } o2-1.0/test/lo_oscrecv.c0000644000175000017500000000417013072261166015413 0ustar zmoelnigzmoelnig// lo_oscrecv.c - test program to receive simple OSC messages // // this test is designed to run with oscsendtest.c #include "stdio.h" #include "assert.h" #include "lo/lo.h" #include "string.h" #ifdef WIN32 #include #include "usleep.h" // special windows implementation of sleep/usleep #else #include #endif int message_count = 0; int timed_count = 0; double timed_start = 0; int small(double x) { return (x > -0.02) && (x < 0.02); } #define JAN_1970 0x83aa7e80 /* 2208988800 1970 - 1900 in seconds */ // we'll use secs since 1970 for a little more precision double timetag_to_secs(lo_timetag tt) { return (tt.sec - JAN_1970) + tt.frac * 0.00000000023283064365; } int osc_i_handler(const char *path, const char *types, lo_arg **argv, int argc, void *msg, void *user_data) { assert(argv); assert(argc == 1); int i = argv[0]->i; if (i == 1234) { printf("osc_i_handler received 1234 at /osc/i\n"); message_count++; } else if (i == 2000) { lo_timetag tt; lo_timetag_now(&tt); timed_start = timetag_to_secs(tt); timed_count = 1; } else if (2000 < i && i < 2010) { lo_timetag tt; lo_timetag_now(&tt); double now = timetag_to_secs(tt); printf("osc_i_handler received %d at elapsed %g\n", i, now - timed_start); i -= 2000; assert(i == timed_count); assert(small(timed_start + i * 0.1 - now)); timed_count++; } else { assert(0); // unexpected message } return 0; } int main(int argc, const char * argv[]) { int tcpflag = 1; printf("Usage: lo_oscrecv [u] (u means use UDP)\n"); if (argc == 2) { tcpflag = (strchr(argv[1], 'u') == NULL); } printf("tcpflag %d\n", tcpflag); lo_server server = lo_server_new_with_proto("8100", tcpflag ? LO_TCP : LO_UDP, NULL); lo_server_add_method(server, "/i", "i", &osc_i_handler, NULL); while (message_count < 10 || timed_count < 10) { lo_server_recv_noblock(server, 0); usleep(10000); // 10ms } printf("OSCRECV DONE\n"); return 0; } o2-1.0/test/statusserver.c0000644000175000017500000000320613072261166016026 0ustar zmoelnigzmoelnig// statusserver.c - O2 status/discovery test, client side // // This program works with statusclient.c. It checks for // discovery of statusclient's service, sends it a message // to exit, then checks that the status of the service // reverts to "does not exist". #include "o2.h" #include "stdio.h" #include "string.h" #include "assert.h" #ifdef WIN32 #include "usleep.h" // special windows implementation of sleep/usleep #else #include #endif int running = TRUE; int main(int argc, const char * argv[]) { printf("Usage: tcpserver\n"); if (argc > 1) { printf("WARNING: o2server ignoring extra command line argments\n"); } o2_initialize("test"); // we are the master clock o2_clock_set(NULL, NULL); // wait for client service to be discovered while (o2_status("client") < O2_LOCAL) { o2_poll(); usleep(2000); // 2ms } printf("We discovered the client at time %g.\n", o2_time_get()); // delay 1 second double now = o2_time_get(); while (o2_time_get() < now + 1) { o2_poll(); usleep(2000); } double start_time = o2_time_get(); printf("Here we go! ...\ntime is %g.\n", start_time); o2_send_cmd("!client/stop", 0.0, ""); // allow 3s for client to shut down and detect it while (running && o2_time_get() < start_time + 3 && o2_status("client") >= 0) { o2_poll(); } if (o2_status("client") < 0) { printf("SERVER DONE\n"); } else { printf("FAIL: client service status is %d\n", o2_status("client")); } o2_finish(); sleep(1); // clean up sockets return 0; } o2-1.0/test/dispatchtest.c0000644000175000017500000000303513072261166015753 0ustar zmoelnigzmoelnig// dispatchtest.c -- dispatch messages between local services // #include #include "o2.h" #define N_ADDRS 20 #define MAX_MESSAGES 50000 int s = 0; int w = 1; void service_one(o2_msg_data_ptr data, const char *types, o2_arg_ptr *argv, int argc, void *user_data) { char p[100]; sprintf(p, "/two/benchmark/%d", s % N_ADDRS); if (s < MAX_MESSAGES) { o2_send(p, 0, "i", s); } if (s % 10000 == 0) { printf("Service one received %d messages\n", s); } s++; } void service_two(o2_msg_data_ptr data, const char *types, o2_arg_ptr *argv, int argc, void *user_data) { char p[100]; sprintf(p, "/one/benchmark/%d", w % N_ADDRS); if (w < MAX_MESSAGES) { o2_send(p, 0, "i", w); } if (w % 10000 == 0) { printf("Service two received %d messages\n", w); } w++; } int main(int argc, const char * argv[]) { // o2_debug_flags("a"); o2_initialize("test"); o2_service_new("one"); for (int i = 0; i < N_ADDRS; i++) { char path[100]; sprintf(path, "/one/benchmark/%d", i); o2_method_new(path, "i", &service_one, NULL, FALSE, FALSE); } o2_service_new("two"); for (int i = 0; i < N_ADDRS; i++) { char path[100]; sprintf(path, "/two/benchmark/%d", i); o2_method_new(path, "i", &service_two, NULL, FALSE, FALSE); } o2_send("/one/benchmark/0", 0, "i", 0); while (s < 50000) { o2_poll(); } o2_finish(); printf("DONE\n"); return 0; } o2-1.0/test/broadcastclient2.c0000644000175000017500000000523713072261166016505 0ustar zmoelnigzmoelnig/* udp-broadcast-client.c * udp datagram client * Get datagram stock market quotes from UDP broadcast: * see below the step by step explanation */ #include #include #include #include #include #include #include #include #include #include #include #include #ifndef TRUE #define TRUE 1 #define FALSE 0 #endif /* * This function reports the error and * exits back to the shell: */ static void displayError(const char *on_what) { fputs(strerror(errno),stderr); fputs(": ",stderr); fputs(on_what,stderr); fputc('\n',stderr); exit(1); } int main(int argc,char **argv) { int z; socklen_t x; struct sockaddr_in adr; /* AF_INET */ int len_inet; /* length */ int s; /* Socket */ char dgram[512]; /* Recv buffer */ /* * Form the broadcast address: */ len_inet = sizeof adr; adr.sin_family = AF_INET; if (inet_pton(AF_INET, "127.0.0.1", &adr.sin_addr.s_addr) != 1) { displayError("inet_pton"); } adr.sin_port = htons(8124); struct addrinfo hints; memset(&hints, 0, sizeof(hints)); hints.ai_family = AF_INET; hints.ai_socktype = SOCK_DGRAM; hints.ai_protocol = 0; hints.ai_flags = AI_PASSIVE; /* WORKS struct addrinfo *res; if (getaddrinfo(NULL, "8124", &hints, &res) < 0) { displayError("getaddrinfo"); } */ /* TEST */ adr.sin_family = AF_INET; adr.sin_addr.s_addr = htonl(INADDR_ANY); adr.sin_port = htons(8124); /* * Create a UDP socket to use: */ s = socket(AF_INET,SOCK_DGRAM,0); if (s == -1) displayError("socket()"); /* * Bind our socket to the broadcast address: */ z = bind(s, /*TEST*/ (struct sockaddr *) &adr, sizeof(adr)); /* WORKS res->ai_addr, res->ai_addrlen); */ if (z == -1) displayError("bind(2)"); for (;;) { /* * Wait for a broadcast message: */ x = sizeof(adr); z = recvfrom(s, /* Socket */ dgram, /* Receiving buffer */ sizeof dgram,/* Max rcv buf size */ 0, /* Flags: no options */ (struct sockaddr *)&adr, /* Addr */ &x); /* Addr len, in & out */ if (z < 0) displayError("recvfrom(2)"); /* else err */ fwrite(dgram, z, 1, stdout); putchar('\n'); fflush(stdout); } return 0; } o2-1.0/test/tcpserver.c0000644000175000017500000000556713072261166015305 0ustar zmoelnigzmoelnig// tcpserver.c - O2 over TCP check and benchmark for message passing // // This program works with tcpclient.c. It is a performance test // that sends a message back and forth between a client and server. // #include "o2.h" #include "stdio.h" #include "string.h" #include "assert.h" #ifdef WIN32 #include "usleep.h" // special windows implementation of sleep/usleep #else #include #endif // To put some weight on fast address lookup, we create N_ADDRS // different addresses to use. // #define N_ADDRS 20 char *client_addresses[N_ADDRS]; int msg_count = 0; int running = TRUE; // this is a handler for incoming messages. It simply sends a message // back to one of the client addresses // void server_test(o2_msg_data_ptr msg, const char *types, o2_arg ** argv, int argc, void *user_data) { assert(argc == 1); msg_count++; o2_send_cmd(client_addresses[msg_count % N_ADDRS], 0, "i", msg_count); if (msg_count % 10000 == 0) { printf("server received %d messages\n", msg_count); } if (msg_count < 100) { printf("server message %d is %d\n", msg_count, argv[0]->i32); } if (argv[0]->i32 == -1) { running = FALSE; } else { assert(msg_count == argv[0]->i32); } } int main(int argc, const char * argv[]) { printf("Usage: tcpserver [debugflags] " "(see o2.h for flags, use a for all)\n"); if (argc == 2) { o2_debug_flags(argv[1]); printf("debug flags are: %s\n", argv[1]); } if (argc > 2) { printf("WARNING: o2server ignoring extra command line argments\n"); } o2_initialize("test"); o2_service_new("server"); // add our handler for incoming messages to each server address for (int i = 0; i < N_ADDRS; i++) { char path[100]; sprintf(path, "/server/benchmark/%d", i); o2_method_new(path, "i", &server_test, NULL, FALSE, TRUE); } // create an address for each destination so we do not have to // do string manipulation to send a message for (int i = 0; i < N_ADDRS; i++) { char path[100]; sprintf(path, "!client/benchmark/%d", i); client_addresses[i] = (char *) (O2_MALLOC(strlen(path))); strcpy(client_addresses[i], path); } // we are the master clock o2_clock_set(NULL, NULL); // wait for client service to be discovered while (o2_status("client") < O2_LOCAL) { o2_poll(); usleep(2000); // 2ms } printf("We discovered the client at time %g.\n", o2_time_get()); // delay 1 second double now = o2_time_get(); while (o2_time_get() < now + 1) { o2_poll(); usleep(2000); } printf("Here we go! ...\ntime is %g.\n", o2_time_get()); while (running) { o2_poll(); } o2_finish(); sleep(1); // clean up sockets printf("SERVER DONE\n"); return 0; } o2-1.0/test/lo_benchmk_server.c0000644000175000017500000000217513072261166016747 0ustar zmoelnigzmoelnig#include #include #include #include "lo/lo.h" #define N_ADDRS 20 lo_address client; char *addresses[N_ADDRS]; int msg_count = 0; int handler(const char *path, const char *types, lo_arg **argv, int argc, lo_message msg, void *user_data) { // keep count and send a reply msg_count++; lo_send(client, addresses[msg_count % N_ADDRS], "i", msg_count); if (msg_count % 10000 == 0) { printf("server received %d messages\n", msg_count); } return 1; } int main() { // create address for client client = lo_address_new("localhost", "8001"); // create server lo_server server = lo_server_new("8000", NULL); // make addresses and register them with server for (int i = 0; i < N_ADDRS; i++) { char path[100]; sprintf(path, "/benchmark/%d", i); addresses[i] = (char *) malloc(strlen(path)); strcpy(addresses[i], path); lo_server_add_method(server, path, "i", &handler, NULL); } // serve port while (1) { lo_server_recv_noblock(server, 0); } } o2-1.0/test/lo_bndlsend.c0000644000175000017500000001034413072261166015540 0ustar zmoelnigzmoelnig// lo_bndlsend.c - test to send OSC bundles // // this test is designed to run with oscbndlrecv.c // We'll send 5 bundles: // at NOW+2.9: [/xyz/msg1 1009 "an arbitrary string at 2.9"], // [/abcdefg/msg2 2009 "another arbitrary string at 2.9"] // at NOW+2.8: [/xyz/msg1 1008 "an arbitrary string at 2.8"], // [/abcdefg/msg2 2008 "another arbitrary string at 2.8"] // at NOW+2.7: [/xyz/msg1 1007 "an arbitrary string at 2.7"], // [/abcdefg/msg2 2007 "another arbitrary string at 2.7"] // at NOW+2.6: [/xyz/msg1 1006 "an arbitrary string at 2.6"], // [/abcdefg/msg2 2006 "another arbitrary string at 2.6"] // at NOW+2.5: [/xyz/msg1 1005 "an arbitrary string at 2.5"], // [/abcdefg/msg2 2005 "another arbitrary string at 2.5"] // Then we'll send a nested bundle: // at NOW+3: [/first 1111 "an arbitrary string at 3.0"], // [#bundle NOW+3.1 // [/xyz/msg1 1011 "an arbitrary string at 3.1"], // [/abcdefg/msg2 2011 "another arbitrary string at 3.1"]] #include "stdio.h" #include "assert.h" #include "lo/lo.h" #include "string.h" #ifdef WIN32 #include "usleep.h" // special windows implementation of sleep/usleep #else #include #endif lo_address client; #define TWO32 4294967296.0 void timetag_add(lo_timetag *timetag, lo_timetag x, double y) { double secs = x.sec + (x.frac / TWO32); secs += y; timetag->sec = (uint32_t) secs; timetag->frac = (uint32_t) ((secs - timetag->sec) * TWO32); } lo_message make_message(int i, char *s) { lo_message msg = lo_message_new(); lo_message_add_int32(msg, i); lo_message_add_string(msg, s); return msg; } void send_nested(lo_timetag now, double touter, double tinner, int base) { lo_timetag timetag; char s[128]; // send nested bundle timetag_add(&timetag, now, touter); lo_bundle outer = lo_bundle_new(timetag); // make first message sprintf(s, "first string at %g", touter); lo_message out1 = make_message(base + 1, s); // add the message to the bundle lo_bundle_add_message(outer, "/first", out1); // make inner bundle timetag_add(&timetag, now, tinner); lo_bundle inner = lo_bundle_new(timetag); // make first inner message sprintf(s, "msg1 string at %g", tinner); lo_message in1 = make_message(base + 2, s); // add the message to the bundle lo_bundle_add_message(inner, "/xyz/msg1", in1); // make second inner message sprintf(s, "msg2 string at %g", tinner); lo_message in2 = make_message(base + 3, s); // add the message to the bundle lo_bundle_add_message(inner, "/abcdefg/msg2", in2); // add the inner bundle lo_bundle_add_bundle(outer, inner); // send it lo_send_bundle(client, outer); } int main(int argc, const char * argv[]) { int tcpflag = 1; printf("Usage: lo_bndlsend [u] (u means use UDP)\n"); if (argc == 2) { tcpflag = (strchr(argv[1], 'u') == NULL); } printf("tcpflag %d\n", tcpflag); sleep(2); // allow some time for server to start client = lo_address_new_with_proto(tcpflag ? LO_TCP : LO_UDP, "localhost", "8100"); printf("client: %p\n", client); char s[128]; lo_timetag now; lo_timetag_now(&now); lo_timetag timetag; for (int i = 9; i >= 5; i--) { // make the bundle timetag_add(&timetag, now, 2 + i * 0.1); lo_bundle bndl = lo_bundle_new(timetag); // make first message sprintf(s, "an arbitrary string at 2.%d", i); lo_message msg1 = make_message(1000 + i, s); // add the message to the bundle lo_bundle_add_message(bndl, "/xyz/msg1", msg1); // make second message sprintf(s, "another arbitrary string at 2.%d", i); lo_message msg2 = make_message(2000 + i, s); // add the message to the bundle lo_bundle_add_message(bndl, "/abcdefg/msg2", msg2); // send it lo_send_bundle(client, bndl); } send_nested(now, 3.0, 0.0, 3000); send_nested(now, 3.1, 3.2, 4000); sleep(1); // make sure messages go out lo_address_free(client); sleep(1); // time to clean up socketsa printf("OSCSEND DONE\n"); return 0; } o2-1.0/test/regression_tests.sh0000644000175000017500000001033213072261166017044 0ustar zmoelnigzmoelnig#!/bin/sh if [ -d "../Debug" ]; then BIN="../Debug" else BIN=".." fi # for linux, ../Debug may exist to hold a debug copy of the # O2 library, but we need to indicate BIN is .. to find tests if [ `uname` == 'Linux' ]; then BIN=".." fi # runtest testname - runs testname, saving output in output.txt, # searches output.txt for single full line containing "DONE", # returns status=0 if DONE was found (indicating success), or # else status=-1 (indicating failure). runtest(){ printf "%30s: " "$1" $BIN/$1 > output.txt if grep -Fxq "DONE" output.txt then echo "PASS" status=0 else status=-1 fi } rundouble(){ printf "%30s: " "$1+$3" ./regression_run_two.sh "$BIN/$1" "$BIN/$3" &>misc.txt 2>&1 if grep -Fxq "$2" output.txt then if grep -Fxq "$4" output2.txt then echo "PASS" status=0 else echo "FAIL" status=-1 fi else echo "FAIL" status=-1 fi } # the while loop never iterates, it is here to make "break" # into a kind of "goto error" when an error is encountered while true; do errorflag=1 echo "Running regression tests for O2 ..." runtest "dispatchtest" if [ $status == -1 ]; then break; fi runtest "typestest" if [ $status == -1 ]; then break; fi runtest "coercetest" if [ $status == -1 ]; then break; fi runtest "longtest" if [ $status == -1 ]; then break; fi runtest "arraytest" if [ $status == -1 ]; then break; fi runtest "bundletest" if [ $status == -1 ]; then break; fi rundouble "statusserver" "SERVER DONE" "statusclient" "CLIENT DONE" if [ $status == -1 ]; then break; fi rundouble "clockmaster" "CLOCKMASTER DONE" "clockslave" "CLOCKSLAVE DONE" if [ $status == -1 ]; then break; fi rundouble "o2client" "CLIENT DONE" "o2server" "SERVER DONE" if [ $status == -1 ]; then break; fi rundouble "oscsendtest u" "OSCSEND DONE" "oscrecvtest u" "OSCRECV DONE" if [ $status == -1 ]; then break; fi rundouble "oscsendtest u" "OSCSEND DONE" "oscanytest u" "OSCANY DONE" if [ $status == -1 ]; then break; fi rundouble "oscsendtest" "OSCSEND DONE" "oscrecvtest" "OSCRECV DONE" if [ $status == -1 ]; then break; fi rundouble "tcpclient" "CLIENT DONE" "tcpserver" "SERVER DONE" if [ $status == -1 ]; then break; fi rundouble "oscbndlsend u" "OSCSEND DONE" "oscbndlrecv u" "OSCRECV DONE" if [ $status == -1 ]; then break; fi rundouble "oscbndlsend" "OSCSEND DONE" "oscbndlrecv" "OSCRECV DONE" if [ $status == -1 ]; then break; fi # tests for compatibility with liblo are run only if the binaries were built # In CMake, set BUILD_TESTS_WITH_LIBLO to create the binaries if [ -f "$BIN/lo_oscrecv" ]; then rundouble "oscsendtest Mu" "OSCSEND DONE" "lo_oscrecv u" "OSCRECV DONE" if [ $status == -1 ]; then break; fi rundouble "oscsendtest M" "OSCSEND DONE" "lo_oscrecv" "OSCRECV DONE" if [ $status == -1 ]; then break; fi fi if [ -f "$BIN/lo_oscsend" ]; then rundouble "lo_oscsend u" "OSCSEND DONE" "oscrecvtest u" "OSCRECV DONE" if [ $status == -1 ]; then break; fi rundouble "lo_oscsend" "OSCSEND DONE" "oscrecvtest" "OSCRECV DONE" if [ $status == -1 ]; then break; fi fi if [ -f "$BIN/lo_bndlsend" ]; then rundouble "lo_bndlsend u" "OSCSEND DONE" "oscbndlrecv u" "OSCRECV DONE" if [ $status == -1 ]; then break; fi rundouble "lo_bndlsend" "OSCSEND DONE" "oscbndlrecv" "OSCRECV DONE" if [ $status == -1 ]; then break; fi fi if [ -f "$BIN/lo_bndlrecv" ]; then rundouble "oscbndlsend Mu" "OSCSEND DONE" "lo_bndlrecv u" "OSCRECV DONE" if [ $status == -1 ]; then break; fi rundouble "oscbndlsend M" "OSCSEND DONE" "lo_bndlrecv" "OSCRECV DONE" if [ $status == -1 ]; then break; fi fi # exit with no errors errorflag=0 break done if [ "$errorflag" == 1 ] then echo "ERROR: exiting regression tests because a test failed." echo " See output.txt (and possibly output2.txt) for " echo " output from the failing test(s)." else echo "**** All O2 regression tests PASSED." fi o2-1.0/test/tcppollclient.c0000644000175000017500000000411613072261166016131 0ustar zmoelnigzmoelnig/* udp-broadcast-client.c * udp datagram client * Get datagram stock market quotes from UDP broadcast: * see below the step by step explanation */ #ifdef WIN32 #include #include #else #include #include #include #include #include #endif #include #include #include #include #include #include #ifndef TRUE #define TRUE 1 #define FALSE 0 #endif /* * This function reports the error and * exits back to the shell: */ static void displayError(const char *on_what) { fputs(strerror(errno),stderr); fputs(": ",stderr); fputs(on_what,stderr); fputc('\n',stderr); exit(1); } int main(int argc,char **argv) { FILE *inf = fopen("port.dat", "r"); if (!inf) displayError("Could not find port.dat"); char ip[100]; int tcp_port; if (fscanf(inf, "%s %d", ip, &tcp_port) != 2) { displayError("could not scanf"); } // strcpy(ip, "localhost"); // printf("ip %s tcp %d\n", ip, tcp_port); fclose(inf); int sock = socket(AF_INET, SOCK_STREAM, 0); if (sock == -1) { displayError("tcp socket set up error"); } struct sockaddr_in remote_addr; // set up the connection memset(&remote_addr, 0, sizeof(struct sockaddr_in)); remote_addr.sin_family = AF_INET; //AF_INET means using IPv4 // inet_pton(AF_INET, ip, &(remote_addr.sin_addr)); struct hostent *hostp = gethostbyname(ip); if (!hostp) displayError("gethostbyname failed"); memcpy(&remote_addr.sin_addr, hostp->h_addr, sizeof(remote_addr.sin_addr)); // remote_addr.sin_port = htons(tcp_port); printf("*** connecting to %s:%d\n", ip, tcp_port); if (connect(sock, (struct sockaddr *) &remote_addr, sizeof(remote_addr)) < 0) { displayError("Connect Error!"); } while (TRUE) { char *msg = "This is a test\n"; if (write(sock, msg, strlen(msg) + 1) < 0) { displayError("send"); } #ifdef WIN32 Sleep(4); #else sleep(4) #endif } return 0; } o2-1.0/test/oscrecvtest.c0000644000175000017500000000416213072261166015622 0ustar zmoelnigzmoelnig// oscrecvtest.c - test o2_osc_port_new() // // this test is designed to run with oscsendtest.c #include "stdio.h" #include "o2.h" #include "string.h" #include "assert.h" #ifdef WIN32 #include "usleep.h" // special windows implementation of sleep/usleep #else #include #endif int message_count = 0; o2_time timed_start = 0; int timed_count = 0; int approx(double x) { return (x > -0.02) && (x < 0.02); } void osc_i_handler(o2_msg_data_ptr data, const char *types, o2_arg_ptr *argv, int argc, void *user_data) { assert(argv); assert(argc == 1); int i = argv[0]->i; if (i == 1234) { printf("osc_i_handler received 1234 at /oscrecv/i\n"); message_count++; } else if (i == 2000) { timed_start = o2_time_get(); timed_count = 1; } else if (2000 < i && i < 2010) { printf("osc_i_handler received %d at elapsed %g\n", i, o2_time_get() - timed_start); i -= 2000; assert(i == timed_count); #ifndef NDEBUG o2_time now = o2_time_get(); // only needed in assert() #endif assert(approx(timed_start + i * 0.1 - now)); timed_count++; } else { assert(FALSE); // unexpected message } } int main(int argc, const char * argv[]) { printf("Usage: oscrecvtest [flags] " "(see o2.h for flags, use a for all, also u for UDP)\n"); int tcpflag = TRUE; if (argc == 2) { o2_debug_flags(argv[1]); tcpflag = (strchr(argv[1], 'u') == NULL); } if (argc > 2) { printf("WARNING: o2server ignoring extra command line argments\n"); } o2_initialize("test"); printf("tcpflag %d\n", tcpflag); int err = o2_osc_port_new("oscrecv", 8100, tcpflag); assert(err == O2_SUCCESS); o2_clock_set(NULL, NULL); o2_service_new("oscrecv"); o2_method_new("/oscrecv/i", "i", osc_i_handler, NULL, FALSE, TRUE); while (message_count < 10 || timed_count < 10) { o2_poll(); usleep(2000); // 2ms } o2_osc_port_free(8100); o2_finish(); printf("OSCRECV DONE\n"); sleep(1); // allow TCP to finish up return 0; } o2-1.0/test/midiclient.c0000644000175000017500000000270213072261166015375 0ustar zmoelnigzmoelnig// midiclient.c - an example program, send to midiserver // // This program works with midiserver.c. #include "o2.h" #include "stdio.h" #include "string.h" #include "cmtio.h" #ifdef WIN32 #include "usleep.h" // special windows implementation of sleep/usleep #else #include #endif #define N_ADDRS 20 char *server_addresses[N_ADDRS]; int msg_count = 0; int main(int argc, const char * argv[]) { o2_debug_flags("*"); /* establish non-blocking input so we can "type" some notes */ IOsetup(0); // inputfd: 0 means stdin o2_initialize("miditest"); while (o2_status("midi") < O2_LOCAL) { o2_poll(); usleep(2000); // 2ms } printf("We discovered the midi service.\ntime is %g.\n", o2_time_get()); printf("Here we go! ...\ntime is %g.\n", o2_time_get()); while (1) { o2_poll(); int input = IOgetchar(); if (input > 0) { char *keys = "qwertyuiopasdfghjklzxcvbnm"; char *keyloc = strchr(keys, input); if (keyloc) { input = (int) ((keyloc - keys) + 48); // index of found key to pitch double now = o2_time_get(); o2_send_cmd("/midi/midi", 0.0, "iii", 0x90, input, 127); o2_send_cmd("/midi/midi", now + 1.0, "iii", 0x90, input, 0); printf("sent key number %d at %g\n", input, now); } } usleep(2000); // 2ms } return 0; } o2-1.0/test/o2client.c0000644000175000017500000000520613072261166014775 0ustar zmoelnigzmoelnig// o2client.c - part of performance benchmark // // see o2server.c for details #include "o2.h" #include "stdio.h" #include "string.h" #include "assert.h" #ifdef WIN32 #include "usleep.h" // special windows implementation of sleep/usleep #else #include #endif #define N_ADDRS 20 int max_msg_count = 50000; char *server_addresses[N_ADDRS]; int msg_count = 0; int running = TRUE; void client_test(o2_msg_data_ptr data, const char *types, o2_arg_ptr *argv, int argc, void *user_data) { msg_count++; // the value we send is arbitrary, but we've already sent // 1 message with value 1, so the 2nd message will have 2, etc... int32_t i = msg_count + 1; // server will shut down when it gets data == -1 if (msg_count >= max_msg_count) { i = -1; running = FALSE; } o2_send(server_addresses[msg_count % N_ADDRS], 0, "i", i); if (msg_count % 10000 == 0) { printf("client received %d messages\n", msg_count); } if (msg_count < 100) { printf("client message %d is %d\n", msg_count, argv[0]->i32); } assert(msg_count == argv[0]->i32); } int main(int argc, const char *argv[]) { printf("Usage: o2client maxmsgs debugflags " "(see o2.h for flags, use a for all)\n"); if (argc >= 2) { max_msg_count = atoi(argv[1]); printf("max_msg_count set to %d\n", max_msg_count); } if (argc >= 3) { o2_debug_flags(argv[2]); printf("debug flags are: %s\n", argv[2]); } if (argc > 3) { printf("WARNING: o2client ignoring extra command line argments\n"); } o2_initialize("test"); o2_service_new("client"); for (int i = 0; i < N_ADDRS; i++) { char path[100]; sprintf(path, "/client/benchmark/%d", i); o2_method_new(path, "i", &client_test, NULL, FALSE, TRUE); } for (int i = 0; i < N_ADDRS; i++) { char path[100]; sprintf(path, "!server/benchmark/%d", i); server_addresses[i] = (char *) (O2_MALLOC(strlen(path))); strcpy(server_addresses[i], path); } while (o2_status("server") < O2_REMOTE) { o2_poll(); usleep(2000); // 2ms } printf("We discovered the server.\ntime is %g.\n", o2_time_get()); double now = o2_time_get(); while (o2_time_get() < now + 1) { o2_poll(); usleep(2000); } printf("Here we go! ...\ntime is %g.\n", o2_time_get()); o2_send("!server/benchmark/0", 0, "i", 1); while (running) { o2_poll(); //usleep(2000); // 2ms // as fast as possible } o2_finish(); printf("CLIENT DONE\n"); return 0; } o2-1.0/test/oscbndlrecv.c0000644000175000017500000001241513072261166015562 0ustar zmoelnigzmoelnig// oscrecvtest.c - test o2_osc_port_new() // // this test is designed to run with oscsendtest.c #include "o2.h" #include "stdio.h" #include "string.h" #include "assert.h" #ifdef WIN32 #include "usleep.h" // special windows implementation of sleep/usleep #else #include #endif // Here's what is sent // at NOW+2.9: [/xyz/msg1 1009 "an arbitrary string at 2.9"], // [/abcdefg/msg2 2009 "another arbitrary string at 2.9"] // at NOW+2.8: [/xyz/msg1 1008 "an arbitrary string at 2.8"], // [/abcdefg/msg2 2008 "another arbitrary string at 2.8"] // at NOW+2.7: [/xyz/msg1 1007 "an arbitrary string at 2.7"], // [/abcdefg/msg2 2007 "another arbitrary string at 2.7"] // at NOW+2.6: [/xyz/msg1 1006 "an arbitrary string at 2.6"], // [/abcdefg/msg2 2006 "another arbitrary string at 2.6"] // at NOW+2.5: [/xyz/msg1 1005 "an arbitrary string at 2.5"], // [/abcdefg/msg2 2005 "another arbitrary string at 2.5"] // Then we'll send a nested bundle: // at NOW+3: [/first 1111 "an arbitrary string at 3.0"], // [#bundle NOW+3.1 // [/xyz/msg1 1011 "an arbitrary string at 3.1"], // [/abcdefg/msg2 2011 "another arbitrary string at 3.1"]] // // Putting that in delivery order, we expect: // at NOW+2.5: /xyz/msg1 1005 "an arbitrary string at 2.5" // /abcdefg/msg2 2005 "another arbitrary string at 2.5" // at NOW+2.6: /xyz/msg1 1006 "an arbitrary string at 2.6" // /abcdefg/msg2 2006 "another arbitrary string at 2.6" // at NOW+2.7: /xyz/msg1 1007 "an arbitrary string at 2.7" // /abcdefg/msg2 2007 "another arbitrary string at 2.7" // at NOW+2.8: /xyz/msg1 1008 "an arbitrary string at 2.8" // /abcdefg/msg2 2008 "another arbitrary string at 2.8" // at NOW+2.9: /xyz/msg1 1009 "an arbitrary string at 2.9" // /abcdefg/msg2 2009 "another arbitrary string at 2.9" // at NOW+3: /first 1111 "an arbitrary string at 3.0" // at NOW+3.1: /xyz/msg1 1011 "an arbitrary string at 3.1" // /abcdefg/msg2 2011 "another arbitrary string at 3.1" int ints[] = {1005, 2005, 1006, 2006, 1007, 2007, 1008, 2008, 1009, 2009, 3001, 3002, 3003, 4001, 4002, 4003, 999}; char *strings[] = { "an arbitrary string at 2.5", "another arbitrary string at 2.5", "an arbitrary string at 2.6", "another arbitrary string at 2.6", "an arbitrary string at 2.7", "another arbitrary string at 2.7", "an arbitrary string at 2.8", "another arbitrary string at 2.8", "an arbitrary string at 2.9", "another arbitrary string at 2.9", "first string at 3", "msg1 string at 0", "msg2 string at 0", "first string at 3.1", "msg1 string at 3.2", "msg2 string at 3.2", "not a valid string"}; o2_time times[] = {2.5, 2.5, 2.6, 2.6, 2.7, 2.7, 2.8, 2.8, 2.9, 2.9, 3.0, 3.0, 3.0, 3.1, 3.2, 3.2, 999}; int msg_count = 0; o2_time start_time = 0.0; // test if x and y are within 30ms (Note: 10ms was too tight under // Windows, but I'm not sure why. int approximate(o2_time x, o2_time y) { return (x < y + 0.03) && (y < x + 0.03); } void meta_handler(char *name, o2_arg_ptr *argv, int argc, o2_msg_data_ptr msg) { if (msg_count == 0) { // assume first message is delivered at the right time start_time = o2_time_get() - 2.5; // timestamp was "now + 2.5" } printf("%s receieved %d, \"%s\"\n", name, argv[0]->i, argv[1]->s); printf(" elapsed %g timestamp %g o2 time %g last_time %g\n", o2_time_get() - start_time, msg->timestamp, o2_time_get(), o2_gtsched.last_time); assert(argv); assert(argc == 2); assert(argv[0]->i == ints[msg_count]); assert(strcmp(argv[1]->s, strings[msg_count]) == 0); assert(approximate(o2_time_get() - start_time, times[msg_count])); msg_count++; } #define ARGS o2_msg_data_ptr msg, const char *types, \ o2_arg_ptr *argv, int argc, void *user_data void first_handler(ARGS) { meta_handler("first_handler", argv, argc, msg); } void msg1_handler (ARGS) { meta_handler("msg1_handler", argv, argc, msg); } void msg2_handler (ARGS) { meta_handler("msg2_handler", argv, argc, msg); } int main(int argc, const char * argv[]) { printf("Usage: oscbndlrecv flags " "(see o2.h for flags, use a for all, also u for UDP)\n"); int tcpflag = TRUE; if (argc == 2) { o2_debug_flags(argv[1]); tcpflag = (strchr(argv[1], 'u') == NULL); } if (argc > 2) { printf("WARNING: o2server ignoring extra command line argments\n"); } o2_initialize("test"); printf("tcpflag %d\n", tcpflag); o2_service_new("oscrecv"); int err = o2_osc_port_new("oscrecv", 8100, tcpflag); assert(err == O2_SUCCESS); printf("created osc server port 8100\n"); o2_clock_set(NULL, NULL); o2_method_new("/oscrecv/xyz/msg1", "is", msg1_handler, NULL, FALSE, TRUE); o2_method_new("/oscrecv/abcdefg/msg2", "is", msg2_handler, NULL, FALSE, TRUE); o2_method_new("/oscrecv/first", "is", first_handler, NULL, FALSE, TRUE); while (msg_count < 16) { o2_poll(); usleep(1000); // 1ms } o2_osc_port_free(8100); o2_finish(); sleep(1); printf("OSCRECV DONE\n"); return 0; } o2-1.0/test/tcppollserver.c0000644000175000017500000001717013072261166016165 0ustar zmoelnigzmoelnig/* udp-broadcast-server.c: * udp broadcast server example * Example Stock Index Broadcast: */ #include #include #include #include #include #ifdef WIN32 #include #include #include #else #include #include #include #include #include #include #include #endif #ifndef TRUE #define TRUE 1 #define FALSE 0 #endif #ifdef WIN32 typedef struct ifaddrs { struct ifaddrs *ifa_next; /* Next item in list */ char *ifa_name; /* Name of interface */ unsigned int ifa_flags; /* Flags from SIOCGIFFLAGS */ struct sockaddr *ifa_addr; /* Address of interface */ struct sockaddr *ifa_netmask; /* Netmask of interface */ union { struct sockaddr *ifu_broadaddr; /* Broadcast address of interface */ struct sockaddr *ifu_dstaddr; /* Point-to-point destination address */ } ifa_ifu; #define ifa_broadaddr ifa_ifu.ifu_broadaddr #define ifa_dstaddr ifa_ifu.ifu_dstaddr void *ifa_data; /* Address-specific data */ } ifaddrs; int getifaddrs(struct ifaddrs **ifpp); void freeifaddrs(struct ifaddrs *ifp); #endif static struct sockaddr_in o2_serv_addr; char o2_local_ip[24]; struct pollfd pfd[2]; int pfd_len = 0; /* * This function reports the error and * exits back to the shell: */ static void displayError(const char *on_what) { fputs(strerror(errno),stderr); fputs(": ",stderr); fputs(on_what,stderr); fputc('\n',stderr); exit(1); } int main(int argc, char **argv) { int server_sock; /* Socket */ /* * Create a TCP server socket: */ server_sock = socket(AF_INET, SOCK_STREAM, 0); if (server_sock == -1) displayError("socket()"); /* * bind it */ memset((char *) &o2_serv_addr, 0, sizeof(o2_serv_addr)); o2_serv_addr.sin_family = AF_INET; o2_serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); // local IP address o2_serv_addr.sin_port = 0; if (bind(server_sock, (struct sockaddr *) &o2_serv_addr, sizeof(o2_serv_addr))) { displayError("Bind receive socket"); } // find the port that was (possibly) allocated #ifdef WIN32 int addr_len = sizeof(o2_serv_addr); #else socklen_t addr_len = sizeof(o2_serv_addr); #endif if (getsockname(server_sock, (struct sockaddr *) &o2_serv_addr, &addr_len)) { displayError("getsockname call to get port number"); } int server_port = ntohs(o2_serv_addr.sin_port); // set actual port used printf("server_port %d\n", server_port); printf("server ip? %x\n", o2_serv_addr.sin_addr.s_addr); o2_local_ip[0] = 0; struct ifaddrs *ifap, *ifa; struct sockaddr_in *sa; if (getifaddrs (&ifap)) { displayError("getting IP address"); } for (ifa = ifap; ifa; ifa = ifa->ifa_next) { if (ifa->ifa_addr->sa_family==AF_INET) { sa = (struct sockaddr_in *) ifa->ifa_addr; if (!inet_ntop(AF_INET, &sa->sin_addr, o2_local_ip, sizeof(o2_local_ip))) { displayError("converting local ip to string"); } if (strcmp(o2_local_ip, "127.0.0.1")) { printf("%s %d\n", o2_local_ip, server_port); break; } } } freeifaddrs(ifap); if (!o2_local_ip[0]) { printf("NO IP!\n"); return -1; } FILE *outf = fopen("port.dat", "w"); fprintf(outf, "%s %d\n", o2_local_ip, server_port); fclose(outf); /* listen */ if (listen(server_sock, 10) < 0) { displayError("listen failed"); } #ifdef WIN32 pfd[0].fd = server_sock; pfd[0].events = POLLIN; pfd_len = 1; FD_SET o2_read_set; struct timeval o2_no_timeout; int total; FD_ZERO(&o2_read_set); for (int i = 0; i < pfd_len; i++) { struct pollfd *d = pfd + i; FD_SET(d->fd, &o2_read_set); } o2_no_timeout.tv_sec = 0; o2_no_timeout.tv_usec = 0; while (TRUE) { if ((total = select(0, &o2_read_set, NULL, NULL, &o2_no_timeout)) == SOCKET_ERROR) { printf("read_set error\n"); } else if (total == 0) { printf("No message is waiting!"); } else { for (int i = 0; i < pfd_len; i++) { struct pollfd *d = pfd + i; if (FD_ISSET(d->fd, &o2_read_set)) { if (i == 0) { // connection request int connection = accept(d->fd, NULL, NULL); if (connection <= 0) { displayError("accept failed"); } pfd[pfd_len].events = POLLIN; pfd[pfd_len++].fd = connection; } else { char buffer[1001]; int len; if ((len = recvfrom(d->fd, buffer, 1000, 0, NULL, NULL)) <= 0) { displayError("recvfrom tcp failed"); } buffer[len] = 0; printf("GOT %d: %s\n", len, buffer); } } } } Sleep(1); #else /* poll */ pfd[0].fd = server_sock; pfd[0].events = POLLIN; pfd_len = 1; while (TRUE) { poll(pfd, pfd_len, 0); for (int i = 0; i < pfd_len; i++) { if ((pfd[i].revents & POLLERR) || (pfd[i].revents & POLLHUP)) { printf("pfd error\n"); } else if (pfd[i].revents) { printf("poll got %d on %d\n", pfd[i].revents, i); if (i == 0) { // connection request int connection = accept(pfd[i].fd, NULL, NULL); if (connection <= 0) { displayError("accept failed"); } pfd[pfd_len].events = POLLIN; pfd[pfd_len++].fd = connection; } else { char buffer[1001]; int len; if ((len = recvfrom(pfd[i].fd, buffer, 1000, 0, NULL, NULL)) <= 0) { displayError("recvfrom tcp failed"); } buffer[len] = 0; printf("GOT %d: %s\n", len, buffer); } } } sleep(1) #endif } return 0; } #ifdef _WIN32 static struct sockaddr * dupaddr(const sockaddr_gen * src) { sockaddr_gen * d = malloc(sizeof(*d)); if (d) { memcpy(d, src, sizeof(*d)); } return (struct sockaddr *) d; } int getifaddrs(struct ifaddrs **ifpp) { SOCKET s = INVALID_SOCKET; size_t il_len = 8192; int ret = -1; INTERFACE_INFO *il = NULL; *ifpp = NULL; s = socket(AF_INET, SOCK_DGRAM, 0); if (s == INVALID_SOCKET) return -1; for (;;) { DWORD cbret = 0; il = malloc(il_len); if (!il) break; ZeroMemory(il, il_len); if (WSAIoctl(s, SIO_GET_INTERFACE_LIST, NULL, 0, (LPVOID)il, (DWORD)il_len, &cbret, NULL, NULL) == 0) { il_len = cbret; break; } free(il); il = NULL; if (WSAGetLastError() == WSAEFAULT && cbret > il_len) { il_len = cbret; } else { break; } } if (!il) goto _exit; /* il is an array of INTERFACE_INFO structures. il_len has the actual size of the buffer. The number of elements is il_len/sizeof(INTERFACE_INFO) */ { size_t n = il_len / sizeof(INTERFACE_INFO); size_t i; for (i = 0; i < n; i++) { struct ifaddrs *ifp; ifp = malloc(sizeof(*ifp)); if (ifp == NULL) break; ZeroMemory(ifp, sizeof(*ifp)); ifp->ifa_next = NULL; ifp->ifa_name = NULL; ifp->ifa_flags = il[i].iiFlags; ifp->ifa_addr = dupaddr(&il[i].iiAddress); ifp->ifa_netmask = dupaddr(&il[i].iiNetmask); ifp->ifa_broadaddr = dupaddr(&il[i].iiBroadcastAddress); ifp->ifa_data = NULL; *ifpp = ifp; ifpp = &ifp->ifa_next; } if (i == n) ret = 0; } _exit: if (s != INVALID_SOCKET) closesocket(s); if (il) free(il); return ret; } void freeifaddrs(struct ifaddrs *ifp) { free(ifp); } #endif o2-1.0/test/coercetest.c0000644000175000017500000005100513072261166015414 0ustar zmoelnigzmoelnig// coercetest.c -- test coercion of O2 parameters // // send from all coercible types: i, h, f, d, t to // handlers that ask for i, h, f, d, t, B, T, F #include #include "o2.h" #include "assert.h" #include "string.h" #ifdef WIN32 #define snprintf _snprintf #endif int got_the_message = FALSE; // we do not declare a different handler for each send type, but // we check that the message has the expected type string. To // enable the test, we put the sender's type string in this // global: // char *send_types = ""; // receive send type as int void service_i(o2_msg_data_ptr data, const char *types, o2_arg_ptr *argv, int argc, void *user_data) { o2_extract_start(data); assert(strcmp(types, send_types) == 0); o2_arg_ptr arg = o2_get_next('i'); assert(arg); printf("service_i types=%s int=%d\n", types, arg->i); assert(arg->i == ((strcmp(send_types, "T") == 0) || strcmp(send_types, "B") == 0) ? 1 : ((strcmp(send_types, "F") == 0) ? 0 : 12345)); got_the_message = TRUE; } void service_ip(o2_msg_data_ptr data, const char *types, o2_arg_ptr *argv, int argc, void *user_data) { assert(strcmp(types, "i") == 0); assert(argc == 1); assert(argv[0]); printf("service_ip types=%s int=%d\n", types, argv[0]->i); assert(argv[0]->i == ((strcmp(send_types, "T") == 0 || strcmp(send_types, "B") == 0) ? 1 : ((strcmp(send_types, "F") == 0) ? 0 : 12345))); got_the_message = TRUE; } // receive int as bool void service_B(o2_msg_data_ptr data, const char *types, o2_arg_ptr *argv, int argc, void *user_data) { o2_extract_start(data); assert(strcmp(types, send_types) == 0); o2_arg_ptr arg = o2_get_next('B'); assert(arg); printf("service_B types=%s bool=%d\n", types, arg->B); assert(arg->B == (strcmp(send_types, "F") != 0)); got_the_message = TRUE; } void service_Bp(o2_msg_data_ptr data, const char *types, o2_arg_ptr *argv, int argc, void *user_data) { assert(strcmp(types, "B") == 0); assert(argc == 1); assert(argv[0]); printf("service_Bp types=%s bool=%d\n", types, argv[0]->B); assert(argv[0]->B == (strcmp(send_types, "F") != 0)); got_the_message = TRUE; } // receive int32 as int64 void service_h(o2_msg_data_ptr data, const char *types, o2_arg_ptr *argv, int argc, void *user_data) { o2_extract_start(data); assert(strcmp(types, send_types) == 0); o2_arg_ptr arg = o2_get_next('h'); assert(arg); // long long "coercion" is to make gcc happy printf("service_h types=%s int64=%lld\n", types, (long long) arg->h); assert(arg->h == ((strcmp(send_types, "T") == 0) || strcmp(send_types, "B") == 0) ? 1 : ((strcmp(send_types, "F") == 0) ? 0 : 12345LL)); got_the_message = TRUE; } void service_hp(o2_msg_data_ptr data, const char *types, o2_arg_ptr *argv, int argc, void *user_data) { assert(strcmp(types, "h") == 0); assert(argc == 1); assert(argv[0]); // long long "coercion" is to make gcc happy printf("service_hp types=%s int64=%lld\n", types, (long long) argv[0]->h); assert(argv[0]->h == ((strcmp(send_types, "T") == 0 || strcmp(send_types, "B") == 0) ? 1 : ((strcmp(send_types, "F") == 0) ? 0 : 12345))); got_the_message = TRUE; } // receive int32 as float void service_f(o2_msg_data_ptr data, const char *types, o2_arg_ptr *argv, int argc, void *user_data) { o2_extract_start(data); assert(strcmp(types, send_types) == 0); o2_arg_ptr arg = o2_get_next('f'); assert(arg); printf("service_f types=%s float=%g\n", types, arg->f); assert(arg->i == ((strcmp(send_types, "T") == 0) || strcmp(send_types, "B") == 0) ? 1 : ((strcmp(send_types, "F") == 0) ? 0 : 1234.0)); got_the_message = TRUE; } void service_fp(o2_msg_data_ptr data, const char *types, o2_arg_ptr *argv, int argc, void *user_data) { assert(strcmp(types, "f") == 0); assert(argc == 1); assert(argv[0]); printf("service_fp types=%s float=%g\n", types, argv[0]->f); assert(argv[0]->f == ((strcmp(send_types, "T") == 0 || strcmp(send_types, "B") == 0) ? 1 : ((strcmp(send_types, "F") == 0) ? 0 : 1234.0))); got_the_message = TRUE; } // receive int32 as double void service_d(o2_msg_data_ptr data, const char *types, o2_arg_ptr *argv, int argc, void *user_data) { o2_extract_start(data); assert(strcmp(types, send_types) == 0); o2_arg_ptr arg = o2_get_next('d'); assert(arg); printf("service_d types=%s double=%g\n", types, arg->d); assert(arg->i == ((strcmp(send_types, "T") == 0) || strcmp(send_types, "B") == 0) ? 1 : ((strcmp(send_types, "F") == 0) ? 0 : 1234.0)); got_the_message = TRUE; } void service_dp(o2_msg_data_ptr data, const char *types, o2_arg_ptr *argv, int argc, void *user_data) { assert(strcmp(types, "d") == 0); assert(argc == 1); assert(argv[0]); printf("service_dp types=%s double=%g\n", types, argv[0]->d); assert(argv[0]->d == ((strcmp(send_types, "T") == 0 || strcmp(send_types, "B") == 0) ? 1 : ((strcmp(send_types, "F") == 0) ? 0 : 1234.0))); got_the_message = TRUE; } // receive int32 as time void service_t(o2_msg_data_ptr data, const char *types, o2_arg_ptr *argv, int argc, void *user_data) { o2_extract_start(data); assert(strcmp(types, send_types) == 0); o2_arg_ptr arg = o2_get_next('t'); assert(arg); printf("service_t types=%s time=%g\n", types, arg->t); assert(arg->t == 1234.0); got_the_message = TRUE; } // receive int32 as time void service_tp(o2_msg_data_ptr data, const char *types, o2_arg_ptr *argv, int argc, void *user_data) { assert(strcmp(types, "t") == 0); assert(argc == 1); assert(argv[0]); printf("service_tp types=%s time=%g\n", types, argv[0]->t); assert(argv[0]->t == 1234.0); got_the_message = TRUE; } // receive a string as a Symbol void service_S(o2_msg_data_ptr data, const char *types, o2_arg_ptr *argv, int argc, void *user_data) { o2_extract_start(data); assert(strcmp(types, send_types) == 0); o2_arg_ptr arg = o2_get_next('S'); assert(arg); printf("service_S types=%s symbol=%s\n", types, arg->S); assert(strcmp(arg->S, "aString") == 0); got_the_message = TRUE; } // receive string as "S" coerced into argv void service_Sp(o2_msg_data_ptr data, const char *types, o2_arg_ptr *argv, int argc, void *user_data) { assert(strcmp(types, "S") == 0); assert(argc == 1); assert(argv[0]); printf("service_Sp types=%s symbol=%s\n", types, argv[0]->S); assert(strcmp(argv[0]->S, "aString") == 0); got_the_message = TRUE; } // receive sent Symbol as s void service_s(o2_msg_data_ptr data, const char *types, o2_arg_ptr *argv, int argc, void *user_data) { o2_extract_start(data); assert(strcmp(types, send_types) == 0); o2_arg_ptr arg = o2_get_next('s'); assert(arg); printf("service_S types=%s symbol=%s\n", types, arg->s); assert(strcmp(arg->s, "aSymbol") == 0); got_the_message = TRUE; } // receive Symbol coerced to "s" string in argv void service_sp(o2_msg_data_ptr data, const char *types, o2_arg_ptr *argv, int argc, void *user_data) { assert(strcmp(types, "s") == 0); assert(argc == 1); assert(argv[0]); printf("service_Sp types=%s string=%s\n", types, argv[0]->s); assert(strcmp(argv[0]->s, "aSymbol") == 0); got_the_message = TRUE; } // receive different sent types as True void service_T(o2_msg_data_ptr data, const char *types, o2_arg_ptr *argv, int argc, void *user_data) { o2_extract_start(data); assert(strcmp(types, send_types) == 0); #ifndef NDEBUG o2_arg_ptr arg = // only needed for assert() #endif o2_get_next('T'); printf("service_T types=%s\n", types); assert(arg); got_the_message = TRUE; } void service_Tp(o2_msg_data_ptr data, const char *types, o2_arg_ptr *argv, int argc, void *user_data) { assert(strcmp(types, "T") == 0); assert(argc == 1); printf("service_Tp types=%s\n", types); got_the_message = TRUE; } // receive int32 as FALSE void service_F(o2_msg_data_ptr data, const char *types, o2_arg_ptr *argv, int argc, void *user_data) { o2_extract_start(data); assert(strcmp(types, send_types) == 0); #ifndef NDEBUG o2_arg_ptr arg = // only needed for assert() #endif o2_get_next('F'); assert(arg); printf("service_F types=%s\n", types); got_the_message = TRUE; } void service_Fp(o2_msg_data_ptr data, const char *types, o2_arg_ptr *argv, int argc, void *user_data) { assert(strcmp(types, "F") == 0); assert(argc == 1); printf("service_Fp types=%s\n", types); got_the_message = TRUE; } // expects hifdt, but receives them as ihdff // void service_many(o2_msg_data_ptr data, const char *types, o2_arg_ptr *argv, int argc, void *user_data) { o2_extract_start(data); o2_arg_ptr arg = o2_get_next('i'); assert(arg->i == 12345); arg = o2_get_next('h'); assert(arg->h == 1234LL); arg = o2_get_next('d'); // note that we must convert the double back to a float // and compare to a float, because if you assign 123.456 // to a float, the stored value is approximately 123.45600128173828 assert(((float) arg->d) == 123.456F); arg = o2_get_next('f'); assert(arg->f == 123.456F); arg = o2_get_next('f'); assert(arg->f == 123.456F); assert(strcmp(types, "hifdt") == 0); printf("service_many types=%s\n", types); got_the_message = TRUE; } void service_manyp(o2_msg_data_ptr data, const char *types, o2_arg_ptr *argv, int argc, void *user_data) { assert(argc == 5); assert(argv[0]->i == 12345); assert(argv[1]->h == 1234LL); // note that we must convert the double back to a float // and compare to a float, because if you assign 123.456 // to a float, the stored value is approximately 123.45600128173828 assert(((float) argv[2]->d) == 123.456F); assert(argv[3]->f == 123.456F); assert(argv[4]->f == 123.456F); assert(strcmp(types, "ihdff") == 0); printf("service_manyp types=%s\n", types); got_the_message = TRUE; } void send_the_message() { while (!got_the_message) { o2_poll(); } got_the_message = FALSE; } int main(int argc, const char * argv[]) { o2_initialize("test"); o2_service_new("one"); char address[32]; for (int i = 0; i < 8; i++) { char send_type = ("ihfdtTFB")[i]; char send_types[4]; send_types[0] = send_type; send_types[1] = 0; snprintf(address, 32, "/one/%ci", send_type); o2_method_new(address, send_types, &service_i, NULL, TRUE, FALSE); snprintf(address, 32, "/one/%cB", send_type); o2_method_new(address, send_types, service_B, NULL, TRUE, FALSE); snprintf(address, 32, "/one/%ch", send_type); o2_method_new(address, send_types, service_h, NULL, TRUE, FALSE); snprintf(address, 32, "/one/%cf", send_type); o2_method_new(address, send_types, service_f, NULL, TRUE, FALSE); snprintf(address, 32, "/one/%cd", send_type); o2_method_new(address, send_types, service_d, NULL, TRUE, FALSE); snprintf(address, 32, "/one/%ct", send_type); o2_method_new(address, send_types, service_t, NULL, TRUE, FALSE); snprintf(address, 32, "/one/%cT", send_type); o2_method_new(address, send_types, service_T, NULL, TRUE, FALSE); snprintf(address, 32, "/one/%cF", send_type); o2_method_new(address, send_types, service_F, NULL, TRUE, FALSE); } o2_method_new("/one/sS", "S", service_S, NULL, TRUE, FALSE); o2_method_new("/one/Ss", "s", service_s, NULL, TRUE, FALSE); o2_method_new("/one/ip", "i", &service_ip, NULL, TRUE, TRUE); o2_method_new("/one/Bp", "B", &service_Bp, NULL, TRUE, TRUE); o2_method_new("/one/hp", "h", &service_hp, NULL, TRUE, TRUE); o2_method_new("/one/fp", "f", &service_fp, NULL, TRUE, TRUE); o2_method_new("/one/dp", "d", &service_dp, NULL, TRUE, TRUE); o2_method_new("/one/tp", "t", &service_tp, NULL, TRUE, TRUE); o2_method_new("/one/Tp", "T", &service_Tp, NULL, TRUE, TRUE); o2_method_new("/one/Fp", "F", &service_Fp, NULL, TRUE, TRUE); o2_method_new("/one/sp", "s", &service_sp, NULL, TRUE, TRUE); o2_method_new("/one/Sp", "S", &service_Sp, NULL, TRUE, TRUE); o2_method_new("/one/many", "hifdt", &service_many, NULL, TRUE, FALSE); o2_method_new("/one/manyp", "ihdff", &service_manyp, NULL, TRUE, TRUE); o2_send("/one/many", 0, "hifdt", 12345LL, 1234, 123.456, 123.456, 123.456); send_the_message(); o2_send("/one/manyp", 0, "hifdt", 12345LL, 1234, 123.456, 123.456, 123.456); send_the_message(); send_types = "i"; o2_send("/one/ii", 0, "i", 12345); send_the_message(); o2_send("/one/ip", 0, "i", 12345); send_the_message(); o2_send("/one/iB", 0, "i", 1234); send_the_message(); o2_send("/one/Bp", 0, "i", 1234); send_the_message(); o2_send("/one/ih", 0, "i", 12345); send_the_message(); o2_send("/one/hp", 0, "i", 12345); send_the_message(); o2_send("/one/if", 0, "i", 1234); send_the_message(); o2_send("/one/fp", 0, "i", 1234); send_the_message(); o2_send("/one/id", 0, "i", 1234); send_the_message(); o2_send("/one/dp", 0, "i", 1234); send_the_message(); o2_send("/one/it", 0, "i", 1234); send_the_message(); o2_send("/one/tp", 0, "i", 1234); send_the_message(); o2_send("/one/iT", 0, "i", 1111); send_the_message(); o2_send("/one/Tp", 0, "i", 1111); send_the_message(); o2_send("/one/iF", 0, "i", 0); send_the_message(); o2_send("/one/Fp", 0, "i", 0); send_the_message(); send_types = "h"; o2_send("/one/hi", 0, "h", 12345LL); send_the_message(); o2_send("/one/ip", 0, "h", 12345LL); send_the_message(); o2_send("/one/hB", 0, "h", 12345LL); send_the_message(); o2_send("/one/Bp", 0, "h", 12345LL); send_the_message(); o2_send("/one/hh", 0, "h", 12345LL); send_the_message(); o2_send("/one/hp", 0, "h", 12345LL); send_the_message(); o2_send("/one/hf", 0, "h", 1234LL); send_the_message(); o2_send("/one/fp", 0, "h", 1234LL); send_the_message(); o2_send("/one/hd", 0, "h", 1234LL); send_the_message(); o2_send("/one/dp", 0, "h", 1234LL); send_the_message(); o2_send("/one/ht", 0, "h", 1234LL); send_the_message(); o2_send("/one/tp", 0, "h", 1234LL); send_the_message(); o2_send("/one/hT", 0, "h", 1111LL); send_the_message(); o2_send("/one/Tp", 0, "h", 1111LL); send_the_message(); o2_send("/one/hF", 0, "h", 0LL); send_the_message(); o2_send("/one/Fp", 0, "h", 0LL); send_the_message(); send_types = "f"; o2_send("/one/fi", 0, "f", 12345.0); send_the_message(); o2_send("/one/ip", 0, "f", 12345.0); send_the_message(); o2_send("/one/fB", 0, "f", 1234.0); send_the_message(); o2_send("/one/Bp", 0, "f", 1234.0); send_the_message(); o2_send("/one/fh", 0, "f", 12345.0); send_the_message(); o2_send("/one/hp", 0, "f", 12345.0); send_the_message(); o2_send("/one/ff", 0, "f", 1234.0); send_the_message(); o2_send("/one/fp", 0, "f", 1234.0); send_the_message(); o2_send("/one/fd", 0, "f", 1234.0); send_the_message(); o2_send("/one/dp", 0, "f", 1234.0); send_the_message(); o2_send("/one/ft", 0, "f", 1234.0); send_the_message(); o2_send("/one/tp", 0, "f", 1234.0); send_the_message(); o2_send("/one/fT", 0, "f", 1111.0); send_the_message(); o2_send("/one/Tp", 0, "f", 1111.0); send_the_message(); o2_send("/one/fF", 0, "f", 0.0); send_the_message(); o2_send("/one/Fp", 0, "f", 0.0); send_the_message(); send_types = "d"; o2_send("/one/di", 0, "d", 12345.0); send_the_message(); o2_send("/one/ip", 0, "d", 12345.0); send_the_message(); o2_send("/one/dB", 0, "d", 1234.0); send_the_message(); o2_send("/one/Bp", 0, "d", 1234.0); send_the_message(); o2_send("/one/dh", 0, "d", 12345.0); send_the_message(); o2_send("/one/hp", 0, "d", 12345.0); send_the_message(); o2_send("/one/df", 0, "d", 1234.0); send_the_message(); o2_send("/one/fp", 0, "d", 1234.0); send_the_message(); o2_send("/one/dd", 0, "d", 1234.0); send_the_message(); o2_send("/one/dp", 0, "d", 1234.0); send_the_message(); o2_send("/one/dt", 0, "d", 1234.0); send_the_message(); o2_send("/one/tp", 0, "d", 1234.0); send_the_message(); o2_send("/one/dT", 0, "d", 1111.0); send_the_message(); o2_send("/one/Tp", 0, "d", 1111.0); send_the_message(); o2_send("/one/dF", 0, "d", 0.0); send_the_message(); o2_send("/one/Fp", 0, "d", 0.0); send_the_message(); send_types = "t"; o2_send("/one/ti", 0, "t", 12345.0); send_the_message(); o2_send("/one/ip", 0, "t", 12345.0); send_the_message(); o2_send("/one/th", 0, "t", 12345.0); send_the_message(); o2_send("/one/hp", 0, "t", 12345.0); send_the_message(); o2_send("/one/tf", 0, "t", 1234.0); send_the_message(); o2_send("/one/fp", 0, "t", 1234.0); send_the_message(); o2_send("/one/td", 0, "t", 1234.0); send_the_message(); o2_send("/one/dp", 0, "t", 1234.0); send_the_message(); o2_send("/one/tt", 0, "t", 1234.0); send_the_message(); o2_send("/one/tp", 0, "t", 1234.0); send_the_message(); send_types = "T"; o2_send("/one/Ti", 0, "T"); send_the_message(); o2_send("/one/ip", 0, "T"); send_the_message(); o2_send("/one/TB", 0, "T"); send_the_message(); o2_send("/one/Bp", 0, "T"); send_the_message(); o2_send("/one/Th", 0, "T"); send_the_message(); o2_send("/one/hp", 0, "T"); send_the_message(); o2_send("/one/Tf", 0, "T"); send_the_message(); o2_send("/one/fp", 0, "T"); send_the_message(); o2_send("/one/Td", 0, "T"); send_the_message(); o2_send("/one/dp", 0, "T"); send_the_message(); o2_send("/one/TT", 0, "T"); send_the_message(); o2_send("/one/Tp", 0, "T"); send_the_message(); send_types = "F"; o2_send("/one/Fi", 0, "F"); send_the_message(); o2_send("/one/ip", 0, "F"); send_the_message(); o2_send("/one/FB", 0, "F"); send_the_message(); o2_send("/one/Bp", 0, "F"); send_the_message(); o2_send("/one/Fh", 0, "F"); send_the_message(); o2_send("/one/hp", 0, "F"); send_the_message(); o2_send("/one/Ff", 0, "F"); send_the_message(); o2_send("/one/fp", 0, "F"); send_the_message(); o2_send("/one/Fd", 0, "F"); send_the_message(); o2_send("/one/dp", 0, "F"); send_the_message(); o2_send("/one/FF", 0, "F"); send_the_message(); o2_send("/one/Fp", 0, "F"); send_the_message(); send_types = "B"; o2_send("/one/Bi", 0, "B", TRUE); send_the_message(); o2_send("/one/ip", 0, "B", TRUE); send_the_message(); o2_send("/one/BB", 0, "B", TRUE); send_the_message(); o2_send("/one/Bp", 0, "B", TRUE); send_the_message(); o2_send("/one/Bh", 0, "B", TRUE); send_the_message(); o2_send("/one/hp", 0, "B", TRUE); send_the_message(); o2_send("/one/Bf", 0, "B", TRUE); send_the_message(); o2_send("/one/fp", 0, "B", TRUE); send_the_message(); o2_send("/one/Bd", 0, "B", TRUE); send_the_message(); o2_send("/one/dp", 0, "B", TRUE); send_the_message(); o2_send("/one/BB", 0, "B", TRUE); send_the_message(); o2_send("/one/Bp", 0, "B", TRUE); send_the_message(); o2_send("/one/BF", 0, "B", FALSE); send_the_message(); o2_send("/one/Fp", 0, "B", FALSE); send_the_message(); o2_send("/one/BT", 0, "B", TRUE); send_the_message(); o2_send("/one/Tp", 0, "B", TRUE); send_the_message(); send_types = "S"; o2_send("/one/Ss", 0, "S", "aSymbol"); send_the_message(); o2_send("/one/sp", 0, "S", "aSymbol"); send_the_message(); send_types = "s"; o2_send("/one/sS", 0, "s", "aString"); send_the_message(); o2_send("/one/Sp", 0, "s", "aString"); send_the_message(); printf("DONE\n"); o2_finish(); return 0; } o2-1.0/test/statusclient.c0000644000175000017500000000214013072261166015772 0ustar zmoelnigzmoelnig// statusclient.c - O2 status/discovery test, client side // // see statusserver.c for details #include "o2.h" #include "stdio.h" #include "string.h" #include "assert.h" #ifdef WIN32 #include "usleep.h" // special windows implementation of sleep/usleep #else #include #endif int running = TRUE; void stop_handler(o2_msg_data_ptr data, const char *types, o2_arg_ptr *argv, int argc, void *user_data) { printf("client received stop message. Bye.\n"); running = FALSE; } int main(int argc, const char * argv[]) { printf("Usage: statusclient\n"); if (argc > 1) { printf("WARNING: tcpclient ignoring extra command line arguments\n"); } o2_initialize("test"); o2_service_new("client"); o2_method_new("/client/stop", "", &stop_handler, NULL, FALSE, TRUE); while (running) { o2_poll(); usleep(2000); // 2ms } // exit without calling o2_finish() -- this is a test for behavior when // the client crashes. Will the server still remove the service? // o2_finish(); printf("CLIENT DONE\n"); return 0; } o2-1.0/test/regression_run_two.sh0000755000175000017500000000063013072261166017402 0ustar zmoelnigzmoelnig#!/bin/sh # regression_run_two program1 program2 # runs the two programs in parallel, saves output of each, # and waits for them to terminate. We use this script so # that the shell output generated when a process finishes # can be captured as output and redirected so as not to # mess up the formatted output of regression_tests.sh $1 > output.txt & PID1=$! $2 > output2.txt PID2=$! wait $PID1 wait $PID2 o2-1.0/test/usleep.h0000644000175000017500000000246513072261166014564 0ustar zmoelnigzmoelnig/* usleep.h -- implement a substitute for usleep and sleep on windows * * Roger Dannenberg Jan 2017 * * I tried #define usleep(x) Sleep((x)/1000) * thinking that Sleep() would give ms delays and that would be good * enough, but in a loop that calls usleep(2000) 500 times, which * should nominally delay 1s, the delay could be 7s or more. I just * want to call o2_poll() while delaying. What I will try is to * keep track of cummulative *intended* delay and return immediately * if we're seeing that delay already. * * This file can just be included -- it's used in a lot of test * programs which all have one source file that links to O2. It * seems unnecessary to create a separate object file to link and * then have system dependent link instructions. */ #include #include long last_time = 0; long long implied_wakeup_us = 0; void usleep(long usec) { long now = timeGetTime(); if (now - last_time < 50) { // assume the intention is a sequence of short delays implied_wakeup_us += usec; } else { // a long time has elapsed implied_wakeup_us = now * 1000LL + usec; } long wake_ms = (int) (implied_wakeup_us / 1000); if (wake_ms > now + 1) Sleep(wake_ms - now); last_time = wake_ms; } void sleep(int secs) { Sleep(secs * 1000); } o2-1.0/test/broadcastclient.c0000644000175000017500000000537413072261166016425 0ustar zmoelnigzmoelnig/* udp-broadcast-client.c * udp datagram client * Get datagram stock market quotes from UDP broadcast: * see below the step by step explanation */ #ifdef WIN32 #include #include #else #include #include #include #include #include #endif #include #include #include #include #include #include #ifndef TRUE #define TRUE 1 #define FALSE 0 #endif /* * This function reports the error and * exits back to the shell: */ static void displayError(const char *on_what) { fputs(strerror(errno),stderr); fputs(": ",stderr); fputs(on_what,stderr); fputc('\n',stderr); exit(1); } int main(int argc,char **argv) { int z; #ifdef WIN32 int x; #else socklen_t x; #endif struct sockaddr_in adr; /* AF_INET */ int len_inet; /* length */ int s; /* Socket */ char dgram[512]; /* Recv buffer */ /* * Form the broadcast address: */ len_inet = sizeof adr; adr.sin_family = AF_INET; if (inet_pton(AF_INET, "127.0.0.1", &adr.sin_addr.s_addr) != 1) { displayError("inet_pton"); } adr.sin_port = htons(8124); struct addrinfo hints; memset(&hints, 0, sizeof(hints)); hints.ai_family = AF_INET; hints.ai_socktype = SOCK_DGRAM; hints.ai_protocol = 0; hints.ai_flags = AI_PASSIVE; /* WORKS struct addrinfo *res; if (getaddrinfo(NULL, "8124", &hints, &res) < 0) { displayError("getaddrinfo"); } */ /* TEST */ adr.sin_family = AF_INET; adr.sin_addr.s_addr = htonl(INADDR_ANY); adr.sin_port = htons(8124); /* * Create a UDP socket to use: */ s = socket(AF_INET,SOCK_DGRAM,0); if (s == -1) displayError("socket()"); /* * Bind our socket to the broadcast address: */ z = bind(s, /*TEST*/ (struct sockaddr *) &adr, sizeof(adr)); /* WORKS res->ai_addr, res->ai_addrlen); */ if (z == -1) displayError("bind(2)"); for (;;) { /* * Wait for a broadcast message: */ x = sizeof(adr); z = recvfrom(s, /* Socket */ dgram, /* Receiving buffer */ sizeof dgram,/* Max rcv buf size */ 0, /* Flags: no options */ (struct sockaddr *)&adr, /* Addr */ &x); /* Addr len, in & out */ if (z < 0) displayError("recvfrom(2)"); /* else err */ fwrite(dgram, z, 1, stdout); putchar('\n'); fflush(stdout); } return 0; } o2-1.0/test/README.txt0000644000175000017500000000545413072261166014615 0ustar zmoelnigzmoelnigREADME.txt These are test programs for o2, benchmarking and development. broadcastclient.c - development code; see if broadcasting works broadcastserver.c clockmaster.c - test of O2 clock synchronization (there are no clockslave.c provisions here to test accuracy, only if it works). To test, run both processes on the same host or on the same local network. They should discover each other, run the clock sync protocol, and print messages indicating success. lo_benchmark_client.c - a performance test similar to o2client/o2server lo_benchmark_server.c but using liblo (you will have to get liblo and build these yourself if you want to run them. o2client.c - performance test; send messages back and forth between o2server.c client and server. Only expected to work on localhost. To test, run both processes on the same host. After about 10s, they should start sending messages back and forth, printing how many messages have been sent. They run only a short time unless you pass a message count to o2client: o2client 10000000 tcpclient.c - o2client/o2server will eventually drop a message if tcpserver.c run on an unreliable network. These programs do the same test as o2client/o2server but use tcp rather than udp so that they should work on a wireless connection as well as on a single host or over local (wired) ethernet. tcppollclient.c - development code exercising poll() to get messages tcppollserver.c midiclient.c - read keys from console and send MIDI via O2 to a server midiserver.c that relays the messages to MIDI using PortMIDI. This was used for an O2 demo. Requires portmidi library. dispatchtest.c - test for simple message construction and dispatch. Runs forever, so kill it by hand. typestest.c - send short messages of all types except vectors and arrays. Prints DONE near the end if every test passes; otherwise, it will be terminated by a failed assert(). coercetest.c - send short messages of all types and try all type coercions. Prints DONE near the end if every test passes; otherwise, it will be terminated by a failed assert(). arraytest.c - send arrays and vectors, receive them with all possible coercions. Prints DONE near the end if every test passes; otherwise, it will be terminated by a failed assert(). longtest.c - send long messages to force special memory allocation for big messages. Prints DONE near the end if every test passes; otherwise, it will be terminated by a failed assert(). o2-1.0/test/oscbndlsend.c0000644000175000017500000001273013072261166015554 0ustar zmoelnigzmoelnig// oscbndlsend.c - test program to send OSC bundles // // this test is designed to run with either oscbndlrecv.c or lo_bndlrecv.c // We'll send 5 bundles: // at NOW+2.9: [/xyz/msg1 1009 "an arbitrary string at 2.9"], // [/abcdefg/msg2 2009 "another arbitrary string at 2.9"] // at NOW+2.8: [/xyz/msg1 1008 "an arbitrary string at 2.8"], // [/abcdefg/msg2 2008 "another arbitrary string at 2.8"] // at NOW+2.7: [/xyz/msg1 1007 "an arbitrary string at 2.7"], // [/abcdefg/msg2 2007 "another arbitrary string at 2.7"] // at NOW+2.6: [/xyz/msg1 1006 "an arbitrary string at 2.6"], // [/abcdefg/msg2 2006 "another arbitrary string at 2.6"] // at NOW+2.5: [/xyz/msg1 1005 "an arbitrary string at 2.5"], // [/abcdefg/msg2 2005 "another arbitrary string at 2.5"] // Then we'll send a nested bundle: // at NOW+3: [/first 1111 "an arbitrary string at 3.0"], // [#bundle NOW+3.1 // [/xyz/msg1 1011 "an arbitrary string at 3.1"], // [/abcdefg/msg2 2011 "another arbitrary string at 3.1"]] #include "o2.h" #include "stdio.h" #include "string.h" #include "assert.h" #ifdef WIN32 #include "usleep.h" // special windows implementation of sleep/usleep #else #include #endif o2_message_ptr make_message(o2_time time, char *address, int i, char *s) { o2_send_start(); o2_add_int32(i); o2_add_string(s); // add the message to the bundle return o2_message_finish(time, address, FALSE); } o2_message_ptr bundle2(o2_time time, o2_message_ptr m1, o2_message_ptr m2) { o2_send_start(); o2_add_message(m1); o2_message_free(m1); o2_add_message(m2); o2_message_free(m2); return o2_service_message_finish(time, "oscsend", "", FALSE); } void send_nested(o2_time now, o2_time touter, o2_time tinner, int base) { char s[128]; // make first message sprintf(s, "first string at %g", touter); o2_message_ptr out1 = make_message(now + touter, "/oscsend/first", base + 1, s); // make first inner message sprintf(s, "msg1 string at %g", tinner); o2_message_ptr in1 = make_message(now + tinner, "/oscsend/xyz/msg1", base + 2, s); // make second inner message // use timestamp of 0, should deliver at max(touter, tinner) because // of containing bundle sprintf(s, "msg2 string at %g", tinner); o2_message_ptr in2 = make_message(0.0, "/oscsend/abcdefg/msg2", base + 3, s); // make inner bundle o2_message_ptr inner = bundle2(now + tinner, in1, in2); // make outer bundle o2_message_ptr outer = bundle2(now + touter, out1, inner); // send it o2_message_send(outer); } int main(int argc, const char * argv[]) { printf("Usage: oscbndlrecv flags (see o2.h for flags, " "use a for all, also u for UDP, M for master)\n"); int tcpflag = TRUE; int master = FALSE; if (argc == 2) { o2_debug_flags(argv[1]); tcpflag = (strchr(argv[1], 'u') == NULL); master = (strchr(argv[1], 'M') != NULL); } if (argc > 2) { printf("WARNING: o2server ignoring extra command line argments\n"); } printf("tcpflag %d master %d\n", tcpflag, master); o2_initialize("test"); // you can make this run without an O2 server by passing "master" if (master) o2_clock_set(NULL, NULL); if (master) sleep(2); // wait for liblo server to come up if we are master char s[128]; printf("Waiting for clock sync\n"); while (!o2_clock_is_synchronized) { usleep(2000); o2_poll(); } int err = o2_osc_delegate("oscsend", "localhost", 8100, tcpflag); assert(err == O2_SUCCESS); printf("connected to port 8100\n"); o2_time now = o2_time_get(); printf("Sending simple message\n"); o2_send("/oscsend/test", 0.0, NULL); printf("Sending messages\n"); for (int i = 9; i >= 5; i--) { // make the bundle o2_send_start(); // make first message sprintf(s, "an arbitrary string at 2.%d", i); o2_message_ptr msg1 = make_message(0.0, "/oscsend/xyz/msg1", 1000 + i, s); // make second message sprintf(s, "another arbitrary string at 2.%d", i); o2_message_ptr msg2 = make_message(0.0, "/oscsend/abcdefg/msg2", 2000 + i, s); // add the messages to the bundle o2_send_start(); o2_add_message(msg1); o2_message_free(msg1); o2_add_message(msg2); o2_message_free(msg2); o2_message_ptr msg = o2_service_message_finish(now + 2 + i * 0.1, "oscsend", "", FALSE); // send it o2_message_send(msg); } // now send nested bundles // this tests timestamps on inner bundles, trying both 0 and a time: // [@3.0 /first [@0 /msg1 /msg2]] -- should deliver all at 3.0 // [@3.1 /first [@3.2 /msg1 /msg2]] -- should dliever msg1, msg2 at 3.2 send_nested(now, 3.0, 0.0, 3000); send_nested(now, 3.1, 3.2, 4000); printf("after sending\n"); sleep(1); // if you exit() after send(), data might be lost printf("removing oscsend\n"); o2_service_free("oscsend"); printf("calling o2_finish()\n"); o2_finish(); printf("sleep(1)\n"); sleep(1); // clean up sockets printf("OSCSEND DONE\n"); return 0; } o2-1.0/test/longtest.c0000644000175000017500000001204313072261166015112 0ustar zmoelnigzmoelnig// longtest.c -- test long messages that require allocation // #include #include "o2.h" #include "assert.h" #include "string.h" #include "o2_message.h" int got_the_message = FALSE; o2_blob_ptr a_blob; char a_midi_msg[4]; int arg_count = 0; // receive arg_count floats void service_f(o2_msg_data_ptr data, const char *types, o2_arg_ptr *argv, int argc, void *user_data) { o2_extract_start(data); for (int i = 0; i < arg_count; i++) { assert(*types == 'f'); #ifndef NDEBUG o2_arg_ptr arg = // only needed for assert() #endif o2_get_next('f'); assert(arg); assert(arg->f == i + 123); types++; } assert(*types == 0); // end of string, got arg_count floats got_the_message = TRUE; } // receive arg_count doubles void service_d(o2_msg_data_ptr data, const char *types, o2_arg_ptr *argv, int argc, void *user_data) { o2_extract_start(data); for (int i = 0; i < arg_count; i++) { assert(*types == 'd'); #ifndef NDEBUG o2_arg_ptr arg = // only needed for assert() #endif o2_get_next('d'); assert(arg); assert(arg->d == i + 1234); types++; } assert(*types == 0); // end of string, got arg_count floats got_the_message = TRUE; } // receive arg_count floats, coerced to ints, with parsing void service_fc(o2_msg_data_ptr data, const char *types, o2_arg_ptr *argv, int argc, void *user_data) { assert(argc == arg_count); o2_extract_start(data); for (int i = 0; i < arg_count; i++) { assert(*types == 'i'); assert(argv[i]); #ifndef NDEBUG int actual = // only needed for assert #endif argv[i]->i; assert(actual == i + 123); types++; } assert(*types == 0); // end of string, got arg_count floats got_the_message = TRUE; } // receive arg_count doubles, coerced to ints, with parsing void service_dc(o2_msg_data_ptr data, const char *types, o2_arg_ptr *argv, int argc, void *user_data) { assert(argc == arg_count); o2_extract_start(data); for (int i = 0; i < arg_count; i++) { assert(*types == 'h'); assert(argv[i]); #ifndef NDEBUG int64_t actual = argv[i]->h; // only needed for assert() #endif assert(actual == i + 1234); types++; } assert(*types == 0); // end of string, got arg_count floats got_the_message = TRUE; } void send_the_message() { while (!got_the_message) { o2_poll(); } got_the_message = FALSE; } int main(int argc, const char * argv[]) { const int N = 100; char address[32]; char types[200]; o2_initialize("test"); o2_service_new("one"); // send from 0 to N-1 floats, without coercion for (int i = 0; i < N; i++) { sprintf(address, "/one/f%d", i); for (int j = 0; j < i; j++) { types[j] = 'f'; } types[i] = 0; o2_method_new(address, types, &service_f, NULL, FALSE, FALSE); o2_send_start(); for (int j = 0; j < i; j++) { o2_add_float(j + 123.0F); } arg_count = i; o2_send_finish(0, address, TRUE); send_the_message(); } printf("DONE sending 0 to %d floats\n", N - 1); // send from 0 to N-1 doubles, without coercion for (int i = 0; i < N; i++) { sprintf(address, "/one/d%d", i); for (int j = 0; j < i; j++) { types[j] = 'd'; } types[i] = 0; o2_method_new(address, types, &service_d, NULL, FALSE, FALSE); o2_send_start(); for (int j = 0; j < i; j++) { o2_add_double(j + 1234); } arg_count = i; o2_send_finish(0, address, TRUE); send_the_message(); } printf("DONE sending 0 to %d doubles\n", N - 1); // send from 0 to N-1 floats, with coercion to int and parsing for (int i = 0; i < N; i++) { sprintf(address, "/one/fc%d", i); for (int j = 0; j < i; j++) { types[j] = 'i'; } types[i] = 0; o2_method_new(address, types, &service_fc, NULL, TRUE, TRUE); o2_send_start(); for (int j = 0; j < i; j++) { o2_add_float(j + 123.0F); } arg_count = i; o2_send_finish(0, address, TRUE); send_the_message(); } printf("DONE sending 0 to %d floats coerced to ints with parsing\n", N - 1); // send from 0 to N-1 doubles, with coercion to int64_t and parsing for (int i = 0; i < N; i++) { sprintf(address, "/one/dc%d", i); for (int j = 0; j < i; j++) { types[j] = 'h'; } types[i] = 0; o2_method_new(address, types, &service_dc, NULL, TRUE, TRUE); o2_send_start(); for (int j = 0; j < i; j++) { o2_add_double(j + 1234); } arg_count = i; o2_send_finish(0, address, TRUE); send_the_message(); } printf("DONE sending 0 to %d doubles coerced to int64_t with parsing\n", N - 1); printf("DONE\n"); o2_finish(); return 0; } o2-1.0/test/lo_benchmk_client.c0000644000175000017500000000225613072261166016717 0ustar zmoelnigzmoelnig#include #include #include #include "lo/lo.h" #define N_ADDRS 20 lo_address server; char *addresses[N_ADDRS]; int msg_count = 0; int handler(const char *path, const char *types, lo_arg **argv, int argc, lo_message msg, void *user_data) { // keep count and send a reply msg_count++; lo_send(server, addresses[msg_count % N_ADDRS], "i", msg_count); if (msg_count % 10000 == 0) { printf("client received %d messages\n", msg_count); } return 1; } int main() { // create address for server server = lo_address_new("localhost", "8000"); // create client lo_server client = lo_server_new("8001", NULL); // make addresses and register them with server for (int i = 0; i < N_ADDRS; i++) { char path[100]; sprintf(path, "/benchmark/%d", i); addresses[i] = (char *) malloc(strlen(path)); strcpy(addresses[i], path); lo_server_add_method(client, path, "i", &handler, NULL); } lo_send(server, addresses[msg_count % N_ADDRS], "i", msg_count); while (1) { lo_server_recv_noblock(client, 0); } } o2-1.0/test/srp/0000755000175000017500000000000013072261166013713 5ustar zmoelnigzmoelnigo2-1.0/test/srp/oscrecvtest.srp0000644000175000017500000000100413072261166017000 0ustar zmoelnigzmoelnig# oscrecvtest.srp - receive OSC messages (for testing O2) message_count = 0 def osc_handler(path, rest parameters) display "osc_handler", path, parameters message_count = message_count + 1 if parameters[0] != 1234 or len(parameters) != 1 print "ERROR: received incorrect value (expected 1234)" exit() def main() osc_server_init("8100", t) osc_server_method("/i", "i", nil, 'osc_handler') while message_count < 10 osc_server_poll() time_sleep(0.01) main() o2-1.0/test/srp/oscsendtest.srp0000644000175000017500000000041713072261166017001 0ustar zmoelnigzmoelnig# oscsendtest.srp - send OSC messages (for testing O2) require "debug" def main() var addr = osc_create_address("", "8100", false) for n = 0 to 20 osc_send_start() osc_add_int32(1234) osc_send(addr, "/i") time_sleep(2.0) main() o2-1.0/test/srp/README.txt0000644000175000017500000000037613072261166015417 0ustar zmoelnigzmoelnigThis directory contains test programs for Serpent, a real-time scripting language. oscrecvtest.srp - similar to ../oscrecvtest.c, but uses Serpent's OSC implementation oscsendtest.srp - similar to ../oscsendtest.c, but uses Serpent's OSC implementation o2-1.0/static.cmake0000644000175000017500000000115713072261166014425 0ustar zmoelnigzmoelnig# static.cmake -- change flags to link with static runtime libraries if(MSVC) foreach(flag_var CMAKE_CXX_FLAGS CMAKE_CXX_FLAGS_DEBUG CMAKE_CXX_FLAGS_RELEASE CMAKE_CXX_FLAGS_MINSIZEREL CMAKE_CXX_FLAGS_RELWITHDEBINFO CMAKE_C_FLAGS CMAKE_C_FLAGS_DEBUG CMAKE_C_FLAGS_RELEASE CMAKE_C_FLAGS_MINSIZEREL CMAKE_C_FLAGS_RELWITHDEBINFO) if(${flag_var} MATCHES "/MD") string(REGEX REPLACE "/MD" "/MT" ${flag_var} "${${flag_var}}") endif(${flag_var} MATCHES "/MD") endforeach(flag_var) message(STATUS "Note: overriding CMAKE_*_FLAGS_* to use Visual C static multithread library") endif(MSVC) o2-1.0/src/0000755000175000017500000000000013072261166012717 5ustar zmoelnigzmoelnigo2-1.0/src/o2_internal.h0000644000175000017500000001303013072261166015301 0ustar zmoelnigzmoelnig// // o2_internal.h // o2 // // Created by 弛张 on 1/24/16. // Copyright © 2016 弛张. All rights reserved. // /// \cond INTERNAL #ifndef o2_INTERNAL_H #define o2_INTERNAL_H #include "o2.h" #include "o2_dynamic.h" typedef const char *o2string; // string padded to 4-byte boundary #include "o2_socket.h" #include "o2_search.h" // Configuration: #define IP_ADDRESS_LEN 32 /** Note: No struct literals in MSVC. */ #ifdef _MSC_VER #ifndef USE_ANSI_C #define USE_ANSI_C #endif #ifndef _CRT_SECURE_NO_WARNINGS // Preclude warnings for string functions #define _CRT_SECURE_NO_WARNINGS #endif // OS X and Linux call it "snprintf": // snprintf seems to be defined Visual Studio now, // Visual Studio 2015 is the first version which defined snprintf, and its _MSC_VER is 1900. #if _MSC_VER < 1900 #define snprintf _snprintf #endif #else // Linux or OS X #define ioctlsocket ioctl #define closesocket close #endif // _MSC_VER #ifdef O2_NO_DEBUGGING #define o2_debug_flags(x) #define O2_DBc(x) #define O2_DBr(x) #define O2_DBs(x) #define O2_DBR(x) #define O2_DBS(x) #define O2_DBk(x) #define O2_DBd(x) #define O2_DBt(x) #define O2_DBT(x) #define O2_DBm(x) #define O2_DBo(x) #define O2_DBO(x) #define O2_DBg(x) // speical multiple category tests: #define O2_DBoO(x) #else extern int o2_debug; void o2_dbg_msg(const char *src, o2_msg_data_ptr msg, const char *extra_label, const char *extra_data); // macro to surround debug print statements: #define O2_DBc_FLAG 1 #define O2_DBr_FLAG 2 #define O2_DBs_FLAG 4 #define O2_DBR_FLAG 8 #define O2_DBS_FLAG 0x10 #define O2_DBk_FLAG 0x20 #define O2_DBd_FLAG 0x40 #define O2_DBt_FLAG 0x80 #define O2_DBT_FLAG 0x100 #define O2_DBm_FLAG 0x200 #define O2_DBo_FLAG 0x400 #define O2_DBO_FLAG 0x800 // All flags but DBM (malloc/free) enabled by "a" #define O2_DBa_FLAGS (0xFFF-0x200) #define O2_DB(flags, x) if (o2_debug & (flags)) { x; } #define O2_DBc(x) O2_DB(O2_DBc_FLAG, x) #define O2_DBr(x) O2_DB(O2_DBr_FLAG, x) #define O2_DBs(x) O2_DB(O2_DBs_FLAG, x) #define O2_DBR(x) O2_DB(O2_DBR_FLAG, x) #define O2_DBS(x) O2_DB(O2_DBS_FLAG, x) #define O2_DBk(x) O2_DB(O2_DBk_FLAG, x) #define O2_DBd(x) O2_DB(O2_DBd_FLAG, x) #define O2_DBt(x) O2_DB(O2_DBt_FLAG, x) #define O2_DBT(x) O2_DB(O2_DBT_FLAG, x) #define O2_DBm(x) O2_DB(O2_DBm_FLAG, x) #define O2_DBo(x) O2_DB(O2_DBo_FLAG, x) #define O2_DBO(x) O2_DB(O2_DBO_FLAG, x) // general debug msgs ('g') are printed if ANY other debugging enabled #define O2_DBg_FLAGS (O2_DBa_FLAGS | O2_DBm_FLAG) #define O2_DBg(x) O2_DB(O2_DBg_FLAGS, x) // special multiple category tests: #define O2_DBoO(x) O2_DB(O2_DBo_FLAG | O2_DBO_FLAG, x) #define O2_DBdo(x) O2_DB(O2_DBd_FLAG | O2_DBo_FLAG, x) #endif #define RETURN_IF_ERROR(expr) { int err = (expr); if (err) return err; } // define IS_BIG_ENDIAN, IS_LITTLE_ENDIAN, and swap64(i), // swap32(i), and swap16(i) #if WIN32 // WIN32 requires predefinition of IS_BIG_ENDIAN=1 or IS_BIG_ENDIAN=0 #else #ifdef __APPLE__ #include "machine/endian.h" // OS X endian.h is in MacOSX10.8.sdk/usr/include/machine/endian.h #define LITTLE_ENDIAN __DARWIN_LITTLE_ENDIAN #else #include #define LITTLE_ENDIAN __LITTLE_ENDIAN #define BYTE_ORDER __BYTE_ORDER #endif #define IS_BIG_ENDIAN (BYTE_ORDER != LITTLE_ENDIAN) #endif #define IS_LITTLE_ENDIAN (!(IS_BIG_ENDIAN)) #define swap16(i) ((((i) >> 8) & 0xff) | (((i) & 0xff) << 8)) #define swap32(i) ((((i) >> 24) & 0xff) | (((i) & 0xff0000) >> 8) | \ (((i) & 0xff00) << 8) | (((i) & 0xff) << 24)) #define swap64(i) ((((uint64_t) swap32(i)) << 32) | swap32((i) >> 32)) #define O2_DEF_TYPE_SIZE 8 #define O2_DEF_DATA_SIZE 8 #define WORD_OFFSET(i) ((i) & ~3) #define streql(a, b) (strcmp(a, b) == 0) /** * Common head for both Windows and Unix. */ #include #include #include #include #include extern char *o2_debug_prefix; extern SOCKET local_send_sock; // socket for sending all UDP msgs extern o2_time o2_local_now; extern o2_time o2_global_now; extern int o2_gtsched_started; #define DEFAULT_DISCOVERY_PERIOD 4.0 extern o2_time o2_discovery_period; #define O2_ARGS_END O2_MARKER_A, O2_MARKER_B /** Default max send and recieve buffer. */ #define MAX_BUFFER 1024 /** \brief Maximum length of address node names */ #define O2_MAX_NODE_NAME_LEN 1020 #define NAME_BUF_LEN ((O2_MAX_NODE_NAME_LEN) + 4) /* \brief Maximum length of UDP messages in bytes */ #define O2_MAX_MSG_SIZE 32768 // macro to make a byte pointer #define PTR(addr) ((char *) (addr)) /// how many bytes are used by next and length fields before data and by /// 4 bytes of zero pad after the data? #define MESSAGE_EXTRA ((PTR(&((o2_message_ptr) 0)->data.timestamp) - \ PTR(&((o2_message_ptr) 0)->next)) + 4) /// how big should whole o2_message be to leave len bytes for the data part? #define MESSAGE_SIZE_FROM_ALLOCATED(len) ((len) + MESSAGE_EXTRA) /// how many bytes of data are left if the whole o2_message is size bytes? #define MESSAGE_ALLOCATED_FROM_SIZE(size) ((size) - MESSAGE_EXTRA) #define MESSAGE_DEFAULT_SIZE 240 #define GET_SERVICE(list, i) (*DA_GET((list), o2_info_ptr, (i))) // global variables extern process_info_ptr o2_process; extern o2_arg_ptr *o2_argv; // arg vector extracted by calls to o2_get_next() extern int o2_argc; // length of argv // shared internal functions void o2_notify_others(const char *service_name, int added); o2_info_ptr o2_proc_service_find(process_info_ptr proc, services_entry_ptr *services); int o2_service_provider_new(o2string key, o2_info_ptr service, process_info_ptr process); #endif /* O2_INTERNAL_H */ /// \endcond o2-1.0/src/o2_interoperation.h0000644000175000017500000000023713072261166016534 0ustar zmoelnigzmoelnig/* o2_interoperation.h -- header for OSC functions */ int o2_deliver_osc(process_info_ptr info); int o2_send_osc(osc_info_ptr service, o2_msg_data_ptr msg); o2-1.0/src/o2_clock.c0000644000175000017500000004120213072261166014555 0ustar zmoelnigzmoelnig// o2_clock.c -- clock synchronization // // Roger Dannenberg, 2016 #include "o2_internal.h" #include "o2_sched.h" #include "o2_send.h" // get the master clock - clock time is estimated as // global_time_base + elapsed_time * clock_rate, where // elapsed_time is local_time - local_time_base // #define LOCAL_TO_GLOBAL(t) \ (global_time_base + ((t) - local_time_base) * clock_rate) static o2_time local_time_base; static o2_time global_time_base; static double clock_rate; int o2_clock_is_synchronized = FALSE; // can we read the time? static int is_master; // initially FALSE, set true by o2_clock_set() static int found_clock_service = FALSE; // set when service appears static o2_time start_sync_time; // local time when we start syncing static int clock_sync_id = 0; static o2_time clock_sync_send_time; static o2string clock_sync_reply_to; static o2_time_callback time_callback = NULL; static void *time_callback_data = NULL; static int clock_rate_id = 0; // data for clock sync. Each reply results in the computation of the // round-trip time and the master-vs-local offset. These results are // stored at ping_reply_count % CLOCK_SYNC_HISTORY_LEN #define CLOCK_SYNC_HISTORY_LEN 5 static int ping_reply_count = 0; static o2_time round_trip_time[CLOCK_SYNC_HISTORY_LEN]; static o2_time master_minus_local[CLOCK_SYNC_HISTORY_LEN]; static o2_time time_offset = 0.0; // added to time_callback() #ifdef __APPLE__ #include "sys/time.h" #include "CoreAudio/HostTime.h" static uint64_t start_time; #elif __linux__ #include "sys/time.h" static long start_time; #elif WIN32 static long start_time; #endif void o2_time_initialize() { #ifdef __APPLE__ start_time = AudioGetCurrentHostTime(); #elif __linux__ struct timeval tv; gettimeofday(&tv, NULL); start_time = tv.tv_sec; #elif WIN32 start_time = timeGetTime(); #else #error o2_clock has no implementation for this system #endif // until local clock is synchronized, LOCAL_TO_GLOBAL will return -1: local_time_base = 0; global_time_base = -1; clock_rate = 0; } // call this with the local and master time when clock sync is first obtained // static void o2_clock_synchronized(o2_time local_time, o2_time master_time) { if (o2_clock_is_synchronized) { return; } o2_clock_is_synchronized = TRUE; o2_sched_start(&o2_gtsched, master_time); if (!is_master) { // do not set local_now or global_now because we could be inside // o2_sched_poll() and we don't want "now" to change, but we can // set up the mapping from local to global time: local_time_base = local_time; global_time_base = master_time; clock_rate = 1.0; } } // catch_up_handler -- handler for "/_o2/cu" // called when we are slowing down or speeding up to return // the clock rate to 1.0 because we should be synchronized // static void catch_up_handler(o2_msg_data_ptr msg, const char *types, o2_arg_ptr *argv, int argc, void *user_data) { int rate_id = argv[0]->i32; if (rate_id != clock_rate_id) return; // this task is cancelled // assume the scheduler sets local_now and global_now global_time_base = LOCAL_TO_GLOBAL(msg->timestamp); local_time_base = msg->timestamp; clock_rate = 1.0; } static void will_catch_up_after(double delay) { // build a message that will call catch_up_handler(rate_id) at local_time if (o2_send_start() || o2_add_int32(clock_rate_id)) return; o2_message_ptr msg = o2_message_finish(local_time_base + delay, "!_o2/cu", FALSE); o2_schedule(&o2_ltsched, msg); } static void set_clock(double local_time, double new_master) { global_time_base = LOCAL_TO_GLOBAL(local_time); // current estimate local_time_base = local_time; O2_DBk(printf("%s set_clock: using %g, should be %g\n", o2_debug_prefix, global_time_base, new_master)); double clock_advance = new_master - global_time_base; // how far to catch up clock_rate_id++; // cancel any previous calls to catch_up_handler() // compute when we will catch up: estimate will increase at clock_rate // while (we assume) master increases at rate 1, so at what t will // global_time_base + (t - local_time_base) * clock_rate == // new_master + (t - local_time_base) // => // new_master - global_time_base == // (t - local_time_base) * clock_rate - (t - local_time_base) // => // clock_advance == (clock_rate - 1) * (t - local_time_base) // => // t == local_time_base + clock_advance / (clock_rate - 1) if (clock_advance > 1) { clock_rate = 1.0; global_time_base = new_master; // we are way behind: jump ahead } else if (clock_advance > 0) { // we are a little behind, clock_rate = 1.1; // go faster to catch up will_catch_up_after(clock_advance * 10); } else if (clock_advance > -1) { // we are a little ahead clock_rate = 0.9; // go slower until the master clock catches up will_catch_up_after(clock_advance * -10); } else { clock_rate = 0; // we're way ahead: stop until next clock sync // TODO: maybe we should try to run clock sync soon since we are // way out of sync and do not know if master time is running } O2_DBk(printf("%s adjust clock to %g, rate %g\n", o2_debug_prefix, LOCAL_TO_GLOBAL(local_time), clock_rate)); } static int o2_send_clocksync(process_info_ptr info) { if (!o2_clock_is_synchronized) return O2_SUCCESS; char address[32]; snprintf(address, 32, "!%s/cs/cs", info->proc.name); return o2_send_cmd(address, 0.0, "s", o2_process->proc.name); } static void compute_osc_time_offset(o2_time now) { // osc_time_offset is initialized using system clock, but you // can call o2_osc_time_offset() to change it, e.g. periodically // using a different time source #ifdef WIN32 // this code comes from liblo /* * FILETIME is the time in units of 100 nsecs from 1601-Jan-01 * 1601 and 1900 are 9435484800 seconds apart. */ int64_t osc_time; FILETIME ftime; double dtime; GetSystemTimeAsFileTime(&ftime); dtime = ((ftime.dwHighDateTime * 4294967296.e-7) - 9435484800.) + (ftime.dwLowDateTime * 1.e-7); osc_time = (uint64_t) dtime; osc_time = (osc_time << 32) + (uint32_t) ((dtime - osc_time) * 4294967296.); #else #define JAN_1970 0x83aa7e80 /* 2208988800 1970 - 1900 in seconds */ struct timeval tv; gettimeofday(&tv, NULL); uint64_t osc_time = (uint64_t) (tv.tv_sec + JAN_1970); osc_time = (osc_time << 32) + (uint64_t) (tv.tv_usec * 4294.967295); #endif osc_time -= (uint64_t) (now * 4294967296.0); o2_osc_time_offset(osc_time); O2_DBk(printf("%s osc_time_offset (in sec) %g\n", o2_debug_prefix, osc_time / 4294967296.0)); } static void announce_synchronized(o2_time now) { // when clock becomes synchronized, we must tell all other // processes about it. To find all other processes, use the o2_fds_info // table since all but a few of the entries are connections to processes for (int i = 0; i < o2_fds_info.length; i++) { process_info_ptr info = GET_PROCESS(i); if (info->tag == TCP_SOCKET) { o2_send_clocksync(info); } } // in addition, compute the offset to absolute time in case we need an // OSC timestamp compute_osc_time_offset(now); O2_DBg(printf("%s obtained clock sync at %g\n", o2_debug_prefix, o2_time_get())); } void o2_clocksynced_handler(o2_msg_data_ptr msg, const char *types, o2_arg_ptr *argv, int argc, void *user_data) { o2_extract_start(msg); o2_arg_ptr arg = o2_get_next('s'); if (!arg) return; char *name = arg->s; o2_info_ptr entry = o2_service_find(name); if (entry) { assert(entry->tag == TCP_SOCKET); process_info_ptr info = (process_info_ptr) entry; info->proc.status = PROCESS_OK; return; } O2_DBg(printf("%s ### ERROR in o2_clocksynced_handler, bad service %s\n", o2_debug_prefix, name)); } static double mean_rtt = 0; static double min_rtt = 0; static void cs_ping_reply_handler(o2_msg_data_ptr msg, const char *types, o2_arg_ptr *argv, int argc, void *user_data) { o2_arg_ptr arg; o2_extract_start(msg); if (!(arg = o2_get_next('i'))) return; // if this is not a reply to the most recent message, ignore it if (arg->i32 != clock_sync_id) return; if (!(arg = o2_get_next('t'))) return; o2_time master_time = arg->t; o2_time now = o2_local_time(); o2_time rtt = now - clock_sync_send_time; // estimate current master time by adding 1/2 round trip time: master_time += rtt * 0.5; int i = ping_reply_count % CLOCK_SYNC_HISTORY_LEN; round_trip_time[i] = rtt; master_minus_local[i] = master_time - now; ping_reply_count++; O2_DBk(printf("%s got clock reply, master_time %g, rtt %g, count %d\n", o2_debug_prefix, master_time, rtt, ping_reply_count)); if (o2_debug & O2_DBk_FLAG) { int start, count; if (ping_reply_count < CLOCK_SYNC_HISTORY_LEN) { start = 0; count = ping_reply_count; } else { start = ping_reply_count % CLOCK_SYNC_HISTORY_LEN; count = CLOCK_SYNC_HISTORY_LEN; } printf("%s master minus local:", o2_debug_prefix); int k = start; for (int j = 0; j < count; j++) { printf(" %g", master_minus_local[k]); k = (k + 1) % CLOCK_SYNC_HISTORY_LEN; } printf("\n%s round trip:", o2_debug_prefix); for (int j = 0; j < count; j++) { printf(" %g", round_trip_time[start]); start = (start + 1) % CLOCK_SYNC_HISTORY_LEN; } printf("\n"); } if (ping_reply_count >= CLOCK_SYNC_HISTORY_LEN) { // find minimum round trip time min_rtt = 9999.0; mean_rtt = 0; int best_i; for (i = 0; i < CLOCK_SYNC_HISTORY_LEN; i++) { mean_rtt += round_trip_time[i]; if (round_trip_time[i] < min_rtt) { min_rtt = round_trip_time[i]; best_i = i; } } // best estimate of master_minus_local is stored at i //printf("* %s: time adjust %g\n", o2_debug_prefix, // now + master_minus_local[best_i] - o2_time_get()); o2_time new_master = now + master_minus_local[best_i]; if (!o2_clock_is_synchronized) { o2_clock_synchronized(now, new_master); announce_synchronized(new_master); } else { set_clock(now, new_master); } } } int o2_roundtrip(double *mean, double *min) { if (!o2_clock_is_synchronized) return O2_FAIL; *mean = mean_rtt; *min = min_rtt; return O2_SUCCESS; } // o2_ping_send_handler -- handler for /_o2/ps (short for "ping send") // wait for clock sync service to be established, // then send ping every 0.1s CLICK_SYNC_HISTORY_LEN times, // then every 0.5s for 5s, then every 10s // void o2_ping_send_handler(o2_msg_data_ptr msg, const char *types, o2_arg_ptr *argv, int argc, void *user_data) { // this function gets called periodically to drive the clock sync // protocol, but if the application calls o2_clock_set(), then we // become the master, at which time we stop polling and announce // to all other processes that we know what time it is, and we // return without scheduling another callback. if (is_master) { o2_clock_is_synchronized = TRUE; return; // no clock sync; we're the master } clock_sync_send_time = o2_local_time(); if (!found_clock_service) { int status = o2_status("_cs"); found_clock_service = (status >= 0); if (found_clock_service) { O2_DBc(printf("%s ** found clock service, is_master=%d\n", o2_debug_prefix, is_master)); if (status == O2_LOCAL || status == O2_LOCAL_NOTIME) { assert(is_master); } else { // record when we started to send clock sync messages start_sync_time = clock_sync_send_time; char path[48]; // enough room for !IP:PORT/cs/get-reply snprintf(path, 48, "!%s/cs/get-reply", o2_process->proc.name); o2_method_new(path, "it", &cs_ping_reply_handler, NULL, FALSE, FALSE); snprintf(path, 32, "!%s/cs", o2_process->proc.name); clock_sync_reply_to = o2_heapify(path); } } } // earliest time to call this action again is clock_sync_send_time + 0.1s: o2_time when = clock_sync_send_time + 0.1; if (found_clock_service) { // found service, but it's non-local clock_sync_id++; o2_send("!_cs/get", 0, "is", clock_sync_id, clock_sync_reply_to); // TODO: test return? // run every 0.1 second until at least CLOCK_SYNC_HISTORY_LEN pings // have been sent to get a fast start, then ping every 0.5s until 5s, // then every 10s. o2_time t1 = CLOCK_SYNC_HISTORY_LEN * 0.1 - 0.01; if (clock_sync_send_time - start_sync_time > t1) when += 0.4; if (clock_sync_send_time - start_sync_time > 5.0) when += 9.5; O2_DBk(printf("%s clock request sent at %g\n", o2_debug_prefix, clock_sync_send_time)); } // schedule another call to o2_ping_send_handler o2_send_start(); o2_message_ptr m = o2_message_finish(when, "!_o2/ps", FALSE); // printf("* schedule ping_send at %g, now is %g\n", when, o2_local_time()); o2_schedule(&o2_ltsched, m); } void o2_clock_initialize() { is_master = FALSE; o2_method_new("/_o2/ps", "", &o2_ping_send_handler, NULL, FALSE, TRUE); o2_method_new("/_o2/cu", "i", &catch_up_handler, NULL, FALSE, TRUE); } // cs_ping_handler -- handler for /_cs/get // return the master clock time static void cs_ping_handler(o2_msg_data_ptr msg, const char *types, o2_arg_ptr *argv, int argc, void *user_data) { o2_arg_ptr serial_no_arg, reply_to_arg; o2_extract_start(msg); if (!(serial_no_arg = o2_get_next('i')) || !(reply_to_arg = o2_get_next('s'))) { return; } int serial_no = serial_no_arg->i32; char *replyto = reply_to_arg->s; int len = (int) strlen(replyto); if (len > 1000) { fprintf(stderr, "cs_ping_handler ignoring /_cs/get message with long reply_to argument\n"); return; // address too long - ignore it } char address[1024]; memcpy(address, replyto, len); memcpy(address + len, "/get-reply", 11); // include EOS o2_send(address, 0, "it", serial_no, o2_time_get()); } int o2_clock_set(o2_time_callback callback, void *data) { if (!o2_application_name) { O2_DBk(printf("%s o2_clock_set cannot be called before o2_initialize.\n", o2_debug_prefix)); return O2_FAIL; } // adjust local_start_time to ensure continuity of time: // new_local_time - new_time_offset == old_local_time - old_time_offset // new_time_offset = new_local_time - (old_local_time - old_time_offset) o2_time old_local_time = o2_local_time(); // (includes -old_time_offset) time_callback = callback; time_callback_data = data; time_offset = 0.0; // get the time without any offset o2_time new_local_time = o2_local_time(); time_offset = new_local_time - old_local_time; if (!is_master) { o2_clock_synchronized(new_local_time, new_local_time); o2_service_new("_cs"); o2_method_new("/_cs/get", "is", &cs_ping_handler, NULL, FALSE, FALSE); O2_DBg(printf("%s ** master clock established, time is now %g\n", o2_debug_prefix, o2_local_time())); is_master = TRUE; announce_synchronized(new_local_time); } return O2_SUCCESS; } o2_time o2_local_time() { if (time_callback) { return (*time_callback)(time_callback_data) - time_offset; } #ifdef __APPLE__ uint64_t clock_time, nsec_time; clock_time = AudioGetCurrentHostTime() - start_time; nsec_time = AudioConvertHostTimeToNanos(clock_time); return ((o2_time) (nsec_time * 1.0E-9)) - time_offset; #elif __linux__ struct timeval tv; gettimeofday(&tv, NULL); return ((tv.tv_sec - start_time) + (tv.tv_usec * 0.000001)) - time_offset; #elif WIN32 return ((timeGetTime() - start_time) * 0.001) - time_offset; #else #error o2_clock has no implementation for this system #endif } o2_time o2_local_to_global(double lt) { return (is_master ? lt : LOCAL_TO_GLOBAL(lt)); } o2_time o2_time_get() { o2_time t = o2_local_time(); return (is_master ? t : LOCAL_TO_GLOBAL(t)); } o2-1.0/src/o2_search.h0000644000175000017500000001352113072261166014737 0ustar zmoelnigzmoelnig// // o2_search.h // o2 // // Created by 弛张 on 3/14/16. // // #ifndef o2_search_h #define o2_search_h #define PATTERN_NODE 0 #define PATTERN_HANDLER 1 #define SERVICES 2 #define O2_BRIDGE_SERVICE 3 #define OSC_REMOTE_SERVICE 4 /** * Structures for hash look up. */ // The structure of an entry in hash table. Subclasses o2_info // subclasses are node_entry, handler_entry, and services_entry typedef struct o2_entry { // "subclass" of o2_info int tag; o2string key; // key is "owned" by this generic entry struct struct o2_entry *next; } o2_entry, *o2_entry_ptr; // Hash table's entry for node, another hash table typedef struct node_entry { // "subclass" of o2_entry int tag; // must be PATTERN_NODE o2string key; // key is "owned" by this node_entry struct o2_entry_ptr next; int num_children; dyn_array children; // children is a dynamic array of o2_entry_ptr // a o2_entry_ptr can point to a node_entry, a handler_entry, a // remote_service_entry, or an osc_entry (are there more?) } node_entry, *node_entry_ptr; // Hash table's entry for handler typedef struct handler_entry { // "subclass" of o2_entry int tag; // must be PATTERN_HANDLER o2string key; // key is "owned" by this handler_entry struct o2_entry_ptr next; o2_method_handler handler; void *user_data; char *full_path; // this is the key for this entry in the o2_full_path_table // it is a copy of the key in the master table entry, so you should never // free this pointer -- it will be freed when the master table entry is // freed. o2string type_string; ///< types expected by handler, or NULL to ignore int types_len; ///< the length of type_string int coerce_flag; ///< boolean - coerce types to match type_string? ///< The message is not altered, but args will point ///< to copies of type-coerced data as needed ///< (coerce_flag is only set if parse_args is true.) int parse_args; ///< boolean - send argc and argv to handler? } handler_entry, *handler_entry_ptr; typedef struct services_entry { // "subclass" of o2_entry int tag; // must be SERVICES o2string key; // key (service name) is "owned" by this struct o2_entry_ptr next; dyn_array services; // links to offers of this service. First in list // is the service to send to. Here "offers" means a node_entry // (local service), handler_entry (local service with just one // handler for all messages), process_info (for remote // service), osc_info (for service delegated to OSC server) } services_entry, *services_entry_ptr; /* // Hash table's entry for a remote service typedef struct remote_service_info { int tag; // must be O2_REMOTE_SERVICE // char *key; // key is "owned" by this remote_service_entry struct // o2_entry_ptr next; process_info_ptr process; // points to its host process for the service, // the remote service might be discovered but not connected } remote_service_info, *remote_service_info_ptr; */ // Hash table entry for o2_osc_delegate: this service // is provided by an OSC server typedef struct osc_info { int tag; // must be OSC_REMOTE_SERVICE o2string service_name; struct sockaddr_in udp_sa; // address for sending UDP messages int port; process_info_ptr tcp_socket_info; // points to "process" that has // info about the TCP socket to the OSC server, if NULL, then use UDP } osc_info, *osc_info_ptr; // Enumerate structure is hash table typedef struct enumerate { dyn_array_ptr dict; int index; o2_entry_ptr entry; } enumerate, *enumerate_ptr; // typedef struct enumerate enumerate, *enumerate_ptr; extern node_entry o2_path_tree; extern node_entry o2_full_path_table; #ifndef O2_NO_DEBUGGING const char *o2_tag_to_string(int tag); #define SEARCH_DEBUG #ifdef SEARCH_DEBUG void o2_info_show(o2_info_ptr info, int indent); #endif #endif void o2_string_pad(char *dst, const char *src); int o2_add_entry_at(node_entry_ptr node, o2_entry_ptr *loc, o2_entry_ptr entry); /** * add an entry to a hash table */ int o2_entry_add(node_entry_ptr node, o2_entry_ptr entry); /** * make a new node */ node_entry_ptr o2_node_new(); int o2_service_provider_replace(process_info_ptr proc, const char *service_name, o2_info_ptr new_service); int o2_embedded_msgs_deliver(o2_msg_data_ptr msg, int tcp_flag); /** * Deliver a message immediately and locally. If service is given, * the caller must check to see if this is a bundle and call * deliver_embedded_msgs() if so. * * @param msg the message data to deliver * @param tcp_flag tells whether to send via tcp; this is only used * when the message is a bundle and elements of the * bundle are addressed to remote services * @param service is the service to which the message is addressed. * If the service is unknown, pass NULL. */ void o2_msg_data_deliver(o2_msg_data_ptr msg, int tcp_flag, o2_info_ptr service); void o2_node_finish(node_entry_ptr node); o2string o2_heapify(const char *path); /** * When we want to initialize or want to create a table. We need to call this * function. * * @param locations The locations you want in the table. * * @return O2_SUCCESS or O2_FAIL */ node_entry_ptr o2_node_initialize(node_entry_ptr node, const char *key); /** * Look up the key in certain table and return the pointer to the entry. * * @param dict The table that the entry is supposed to be in. * @param key The key. * @param index The position in the hash table. * * @return The address of the pointer to the entry. */ o2_entry_ptr *o2_lookup(node_entry_ptr dict, o2string key); int o2_remove_remote_process(process_info_ptr info); #endif /* o2_search_h */ o2-1.0/src/o2_debug.h0000644000175000017500000000016413072261166014557 0ustar zmoelnigzmoelnig/* o2_debug.h -- some debugging code, see o2_debug.c */ void *dbg_malloc(size_t size); void dbg_free(void *ptr); o2-1.0/src/o2_message.h0000644000175000017500000000471013072261166015116 0ustar zmoelnigzmoelnig// // O2_message.h // O2 // // Created by 弛张 on 1/26/16. // Copyright © 2016 弛张. All rights reserved. // #ifndef o2_message_h #define o2_message_h extern o2_message_ptr message_freelist; #define MAX_SERVICE_LEN 64 #ifdef WIN32 #define ssize_t long long #endif /* MESSAGE EXTRACTION */ void o2_argv_initialize(); void o2_argv_finish(); /* MESSAGE CONSTRUCTION */ int o2_add_bundle_head(int64_t time); int32_t *o2_msg_len_ptr(); int o2_set_msg_length(int32_t *msg_len_ptr); int o2_add_raw_bytes(int32_t len, char *bytes); char *o2_msg_data_get(int32_t *len_ptr); /* GENERAL MESSAGE FUNCIONS */ #define IS_BUNDLE(msg)((msg)->address[0] == '#') // Iterate over elements of a bundle. msg is an o2_msg_data_ptr, and // code is the code to execute. When code is entered, embedded is an // o2_msg_data_ptr pointing to each element of msg. code MUST assign // the length of embedded to len. (This is not built-in because // embedded may be byte-swapped.) Generally, code will include a // recursive call to process embedded, which may itself be a bundle. // #define FOR_EACH_EMBEDDED(msg, code) \ char *end_of_msg = PTR(msg) + MSG_DATA_LENGTH(msg); \ o2_msg_data_ptr embedded = (o2_msg_data_ptr) \ ((msg)->address + o2_strsize((msg)->address) + sizeof(int32_t)); \ while (PTR(embedded) < end_of_msg) { int32_t len; \ code; \ embedded = (o2_msg_data_ptr) (PTR(embedded) + len + sizeof(int32_t)); } /* allocate message structure with at least size bytes in the data portion */ o2_message_ptr o2_alloc_size_message(int size); /* free a message and all the messages it links to */ void o2_message_list_free(o2_message_ptr msg); /* compute the size of a string including EOS and padding to next word */ int o2_strsize(const char *s); /** * Convert endianness of a message * * @param msg The message * * @return O2_SUCCESS unless the message is malformed */ int o2_msg_swap_endian(o2_msg_data_ptr msg, int is_host_order); int o2_message_build(o2_message_ptr *msg, o2_time timestamp, const char *service_name, const char *path, const char *typestring, int tcp_flag, va_list ap); /** * Print o2_msg_data to stdout * * @param msg The message to print */ void o2_msg_data_print(o2_msg_data_ptr msg); /** * Print an O2 message to stdout * * @param msg The message to print */ void o2_message_print(o2_message_ptr msg); #endif /* O2_message_h */ o2-1.0/src/o2_debug.c0000644000175000017500000000105013072261166014545 0ustar zmoelnigzmoelnig/* o2_debug.c -- some debugging code. This is not used, but if * if you need a quick re-implementation of malloc or free, * you could start here. */ #include "o2.h" #include void *dbg_malloc(size_t size) { void *ptr = malloc(size); /* code used for debug */ if ((long)ptr == 0x0100100d68) { printf("malloc %p\n", ptr); } /* */ return ptr; } void dbg_free(void *ptr) { /* code used for debug */ if ((long)ptr == 0x0100100d68) { printf("free %p\n", ptr); } /* */ free(ptr); } o2-1.0/src/o2_search.c0000644000175000017500000014213213072261166014733 0ustar zmoelnigzmoelnig// // o2_search.c // o2 // // Created by 弛张 on 3/14/16. // // // delivery is recursive due to bundles. Here's an overview of the structure: // // o2_message_send(o2_message_ptr msg, int schedulable) // (Defined in o2_send.c) // Determines local or remote O2 or OSC // If remote, calls o2_send_remote() // If local and future and schedulable, calls o2_schedule() // Otherwise, calls o2_msg_data_deliver() // Caller gives msg to callee. msg is freed eventually. // o2_send_remote(o2_msg_data_ptr msg, int tcp_flag, fds_info_ptr info) // Sends msg_data to remote service // o2_msg_data_send(o2_msg_data_ptr msg, int tcp_flag) // (Defined in o2_send.c) // Determines local or remote O2 or OSC // If remote, calls o2_send_remote() // If OSC, calls sends data as UDP // If local but future, builds a message and calls // o2_schedule(). // If local, calls o2_msg_data_deliver() // o2_schedule(o2_sched_ptr sched, o2_message_ptr msg) // (Defined in o2_sched.c) // msg could be local or for delivery to an OSC server // Caller gives msg to callee. msg is freed eventually. // Calls o2_message_send_sched(msg, FALSE) to send message // (FALSE prevents a locally scheduled message you are trying // to dispatch from being considered for scheduling using the // o2_gtsched, which may not be working yet.) // o2_msg_data_deliver(o2_msg_data_ptr msg, int tcp_flag, // o2_entry_ptr service) // delivers a message or bundle locally. Calls // o2_embedded_msgs_deliver() if this is a bundle. // Otherwise, uses find_and_call_handlers_rec(). // o2_embedded_msgs_deliver(o2_msg_data_ptr msg, int tcp_flag) // Deliver or schedule messages in a bundle (recursively). // Calls o2_msg_data_send() to deliver each embedded message // message, which copies the element into an o2_message if needed. // // Message parsing and forming o2_argv with message parameters is not // reentrant since there is a global buffer used to store coerced // parameters. Therefore, if you call a handler and the handler sends a // message, we cannot deliver it immediately, at least not if it has a // local destination. Therefore, messages sent from handlers may be // saved on a list and dispatched later. #include #include "o2_internal.h" #include "o2_message.h" #include "o2_discovery.h" #include "o2_send.h" #include "o2_sched.h" #ifdef WIN32 #include "malloc.h" #define allloca _alloca #endif static void entry_free(o2_entry_ptr entry); static int o2_pattern_match(const char *str, const char *p); static int entry_remove(node_entry_ptr node, o2_entry_ptr *child, int resize); static int remove_method_from_tree(char *remaining, char *name, node_entry_ptr node); static int remove_node(node_entry_ptr node, o2string key); static int remove_remote_services(process_info_ptr info); static int resize_table(node_entry_ptr node, int new_locs); static node_entry_ptr tree_insert_node(node_entry_ptr node, o2string key); #if IS_LITTLE_ENDIAN // for little endian machines #define STRING_EOS_MASK 0xFF000000 #else #define STRING_EOS_MASK 0x000000FF #endif #define SCRAMBLE 2686453351680 #define MAX_SERVICE_NUM 1024 node_entry o2_full_path_table; node_entry o2_path_tree; static void enumerate_begin(enumerate *enumerator, dyn_array_ptr dict) { enumerator->dict = dict; enumerator->index = 0; enumerator->entry = NULL; } // return next entry from table. Entries can be inserted into // a new table because enumerate_next does not depend upon the // pointers in each entry once the entry is enumerated. // static o2_entry_ptr enumerate_next(enumerate_ptr enumerator) { while (!enumerator->entry) { int i = enumerator->index++; if (i >= enumerator->dict->length) { return NULL; // no more entries } enumerator->entry = *DA_GET(*(enumerator->dict), o2_entry_ptr, i); } o2_entry_ptr ret = enumerator->entry; enumerator->entry = enumerator->entry->next; return ret; } #ifndef O2_NO_DEBUGGING static const char *entry_tags[5] = { "PATTERN_NODE", "PATTERN_HANDLER", "SERVICES", "O2_BRIDGE_SERVICE", "OSC_REMOTE_SERVICE" }; static const char *info_tags[7] = { "UDP_SOCKET", "TCP_SOCKET", "OSC_SOCKET", "DISCOVER_SOCKET", "TCP_SERVER_SOCKET", "OSC_TCP_SERVER_SOCKET", "OSC_TCP_SOCKET" }; const char *o2_tag_to_string(int tag) { if (tag <= OSC_REMOTE_SERVICE) return entry_tags[tag]; if (tag >= UDP_SOCKET && tag <= OSC_TCP_SOCKET) return info_tags[tag - UDP_SOCKET]; static char unknown[32]; snprintf(unknown, 32, "Tag-%d", tag); return unknown; } #endif #ifdef SEARCH_DEBUG // debugging code to print o2_entry and o2_info structures void o2_info_show(o2_info_ptr info, int indent) { for (int i = 0; i < indent; i++) printf(" "); printf("%s@%p", o2_tag_to_string(info->tag), info); if (info->tag == PATTERN_NODE || info->tag == PATTERN_HANDLER || info->tag == SERVICES) { o2_entry_ptr entry = (o2_entry_ptr) info; if (entry->key) printf(" key=%s", entry->key); } if (info->tag == PATTERN_NODE) { printf("\n"); node_entry_ptr node = (node_entry_ptr) info; enumerate en; enumerate_begin(&en, &(node->children)); o2_entry_ptr entry; while ((entry = enumerate_next(&en))) { // see if each entry can be found #ifndef NDEBUG o2_entry_ptr *ptr = // only needed in assert() #endif o2_lookup(node, entry->key); assert(*ptr == entry); o2_info_show((o2_info_ptr) entry, indent + 1); } } else if (info->tag == SERVICES) { services_entry_ptr s = (services_entry_ptr) info; printf("\n"); for (int j = 0; j < s->services.length; j++) { o2_info_show(*DA_GET(s->services, o2_info_ptr, j), indent + 1); } } else if (info->tag == PATTERN_HANDLER) { handler_entry_ptr h = (handler_entry_ptr) info; if (h->full_path) printf(" full_path=%s", h->full_path); printf("\n"); } else if (info->tag == TCP_SOCKET) { process_info_ptr proc = (process_info_ptr) info; printf(" port=%d name=%s\n", proc->port, proc->proc.name); } else { printf("\n"); } } #endif // o2_add_entry_at inserts an entry into the hash table. If the // table becomes too full, a new larger table is created. // This function is called after o2_lookup() has been used to // determine a pointer to the new entry. This pointer is // passed in loc. // int o2_add_entry_at(node_entry_ptr node, o2_entry_ptr *loc, o2_entry_ptr entry) { node->num_children++; entry->next = *loc; *loc = entry; // expand table if it is too small if (node->num_children * 3 > node->children.length * 2) { return resize_table(node, node->num_children * 3); } return O2_SUCCESS; } // call handler for message. Does type coercion, argument vector // construction, and type checking. types points to the type string // after the initial ',' // // Design note: We could find types by scanning over the address in // msg, but since address pattern matching already scans over most // of the address, it's faster (I think) for the caller to compute // types and pass it in. An exception is the case where we do a hash // lookup of the full address. In that case the caller has to scan // over the whole address (4 bytes at a time) to find types in order // to pass it in. // static void call_handler(handler_entry_ptr handler, o2_msg_data_ptr msg, const char *types) { // coerce to avoid compiler warning -- even 2^31 is absurdly big // for the type string length int types_len = (int) strlen(types); // type checking if (handler->type_string && // mismatch detection needs type_string ((handler->types_len != types_len) || // first check if counts are equal !(handler->coerce_flag || // need coercion or exact match (streql(handler->type_string, types))))) { // printf("!!! %s: call_handler skipping %s due to type mismatch\n", // o2_debug_prefix, msg->address); return; // type mismatch } if (handler->parse_args) { o2_extract_start(msg); o2string typ = handler->type_string; if (!typ) { // if handler type_string is NULL, use message types typ = types; } while (*typ) { o2_arg_ptr next = o2_get_next(*typ++); if (!next) { return; // type mismatch, do not deliver the message } } if (handler->type_string) { types = handler->type_string; // so that handler gets coerced types } extern dyn_array o2_argv_data; // these are (mostly) private extern dyn_array o2_arg_data; // to o2_message.c assert(o2_arg_data.allocated >= o2_arg_data.length); assert(o2_argv_data.allocated >= o2_argv_data.length); } else { o2_argv = NULL; o2_argc = 0; } (*(handler->handler))(msg, types, o2_argv, o2_argc, handler->user_data); } // create a node in the path tree // // key is "owned" by caller // node_entry_ptr o2_node_new(const char *key) { node_entry_ptr node = (node_entry_ptr) O2_MALLOC(sizeof(node_entry)); if (!node) return NULL; return o2_node_initialize(node, key); } // This is the main worker for dispatching messages. It determines if a node // name is a pattern (if so, enumerate all nodes in the table and try to match) // or not a pattern (if so, do a faster hash lookup). In either case, when the // address node is internal (not the last part of the address), call this // function recursively to search the tree of tables for matching handlers. // Otherwise, call the handler specified by the/each matching entry. // // remaining is what remains of path to be matched. The base case is the 2nd // character of the whole address (skipping ! or /). // name is a buffer used to copy a node name and pad it with zeros // for the hash function // node is the current node in the tree // msg is message to be dispatched // static void find_and_call_handlers_rec(char *remaining, char *name, o2_entry_ptr node, o2_msg_data_ptr msg, char *types) { char *slash = strchr(remaining, '/'); if (slash) *slash = 0; char *pattern = strpbrk(remaining, "*?[{"); if (slash) *slash = '/'; if (pattern) { // this is a pattern enumerate enumerator; enumerate_begin(&enumerator, &(((node_entry_ptr)node)->children)); o2_entry_ptr entry; while ((entry = enumerate_next(&enumerator))) { if (slash && (entry->tag == PATTERN_NODE) && (o2_pattern_match(entry->key, remaining) == O2_SUCCESS)) { find_and_call_handlers_rec(slash + 1, name, entry, msg, types); } else if (!slash && (entry->tag == PATTERN_HANDLER)) { char *path_end = remaining + strlen(remaining); path_end = WORD_ALIGN_PTR(path_end); call_handler((handler_entry_ptr) entry, msg, path_end + 5); } } } else { // no pattern characters so do hash lookup if (slash) *slash = 0; o2_string_pad(name, remaining); if (slash) *slash = '/'; o2_entry_ptr entry = *o2_lookup((node_entry_ptr) node, name); if (entry) { if (slash && (entry->tag == PATTERN_NODE)) { find_and_call_handlers_rec(slash + 1, name, entry, msg, types); } else if (!slash && (entry->tag == PATTERN_HANDLER)) { char *path_end = remaining + strlen(remaining); path_end = WORD_ALIGN_PTR(path_end); call_handler((handler_entry_ptr) entry, msg, path_end + 5); } } } } void osc_info_free(osc_info_ptr osc) { if (osc->tcp_socket_info) { // TCP: close the TCP socket o2_socket_mark_to_free(osc->tcp_socket_info); osc->tcp_socket_info->osc.service_name = NULL; osc->tcp_socket_info = NULL; // just to be safe osc->service_name = NULL; // shared pointer with services_entry } O2_FREE(osc); } // entry_free - when an entry is inserted into a table, it // may conflict with a previous entry. For example, if you // define a handler for /a/b/1 and /a/b/2, then define a // handler for /a/b, the table representing /a/b/ and // containing entries for 1 and 2 will be replaced by the // new handler for /a/b. This function recursively deletes // and frees subtrees (such as the one rooted at /a/b). // As a side-effect, the full paths (such as /a/b/1 and // /a/b/2) corresponding to leaf nodes in the tree will be // removed from the o2_full_path_table, which hashes full paths. // Note that the o2_full_path_table entries do not have full_path // fields (the .full_path field is NULL), so we know that // an entry is in the o2_path_tree by looking at the // .full_path field. // // The parameter should be an entry to remove -- either an // internal entry (PATTERN_NODE) or a leaf entry (PATTERN_HANDLER) // static void entry_free(o2_entry_ptr entry) { // printf("entry_free: freeing %s %s\n", // o2_tag_to_string(entry->tag), entry->key); if (entry->tag == PATTERN_NODE) { o2_node_finish((node_entry_ptr) entry); O2_FREE(entry); return; } else if (entry->tag == PATTERN_HANDLER) { handler_entry_ptr handler = (handler_entry_ptr) entry; // if we remove a leaf node from the tree, remove the // corresponding full path: if (handler->full_path) { remove_node(&o2_full_path_table, handler->full_path); handler->full_path = NULL; // this string should be freed // in the previous call to remove_node(); remove the // pointer so if anyone tries to reference it, it will // generate a more obvious and immediate runtime error. } if (handler->type_string) O2_FREE((void *) handler->type_string); } else if (entry->tag == SERVICES) { // free the service providers here; a non-empty services_entry will // only be freed if we are shutting down, so we don't have to clean // up the links from process_info_ptr->services lists here. services_entry_ptr s = (services_entry_ptr) entry; for (int i = 0; i < s->services.length; i++) { o2_info_ptr info = GET_SERVICE(s->services, i); if (info->tag == PATTERN_NODE) { entry_free((o2_entry_ptr) info); } else if (info->tag == PATTERN_HANDLER) { entry_free((o2_entry_ptr) info); } else if (info->tag == OSC_REMOTE_SERVICE) { osc_info_free((osc_info_ptr) info); } else assert(info->tag == TCP_SOCKET); } DA_FINISH(s->services); } else assert(FALSE); // nothing else should be freed O2_FREE((void *) entry->key); O2_FREE(entry); } // The hash function processes 4 bytes at a time and is based // on the idea (and I think this is what Java uses) of repeatedly // multiplying the hash by 5 and adding the next character until // all characters are used. The SCRAMBLE number is (5 << 8) + // ((5 * 5) << 16 + ..., so it is similar to doing the multiplies // and adds all in parallel for 4 bytes at a time. // // In O2, o2string means "const char *" with zero padding to a // 32-bit word boundary. // static int64_t get_hash(o2string key) { int32_t *ikey = (int32_t *) key; uint64_t hash = 0; int32_t c; do { c = *ikey++; hash = ((hash + c) * SCRAMBLE) >> 32; } while (c & STRING_EOS_MASK); return hash; } static int initialize_table(dyn_array_ptr table, int locations) { DA_INIT(*table, o2_entry_ptr, locations); if (!table->array) return O2_FAIL; memset(table->array, 0, locations * sizeof(o2_entry_ptr)); table->allocated = locations; table->length = locations; return O2_SUCCESS; } // o2_entry_add inserts an entry into the hash table. If the table becomes // too full, a new larger table is created. // int o2_entry_add(node_entry_ptr node, o2_entry_ptr entry) { o2_entry_ptr *ptr = o2_lookup(node, entry->key); if (*ptr) { // if we found it, this is a replacement entry_remove(node, ptr, FALSE); // splice out existing entry and delete it } return o2_add_entry_at(node, ptr, entry); } // insert whole path into master table, insert path nodes into tree // if this path exists, then first remove all sub-tree paths // // path is "owned" by caller (so it is copied here) // int o2_method_new(const char *path, const char *typespec, o2_method_handler h, void *user_data, int coerce, int parse) { // o2_heapify result is declared as const, but if we don't share it, there's // no reason we can't write into it, so this is a safe cast to (char *): char *key = (char *) o2_heapify(path); *key = '/'; // force key's first character to be '/', not '!' // add path elements as tree nodes -- to get the keys, replace each // "/" with EOS and o2_heapify to copy it, then restore the "/" char *remaining = key + 1; char name[NAME_BUF_LEN]; char *slash = strchr(remaining, '/'); if (slash) *slash = 0; services_entry_ptr *services = o2_services_find(remaining); // note that slash has not been restored (see o2_service_replace below) int ret = O2_NO_SERVICE; if (!services) goto error_return; // cleanup and return node_entry_ptr node = (node_entry_ptr) o2_proc_service_find(o2_process, services); if (!node) goto error_return; // cleanup and return assert(node->tag == PATTERN_NODE || node->tag == PATTERN_HANDLER); ret = O2_FAIL; // set to prepare for failure in O2_MALLOC handler_entry_ptr handler = (handler_entry_ptr) O2_MALLOC(sizeof(handler_entry)); if (!handler) goto error_return; // using default O2_FAIL handler->tag = PATTERN_HANDLER; handler->key = NULL; handler->handler = h; handler->user_data = user_data; handler->full_path = key; o2string types_copy = NULL; int types_len = 0; if (typespec) { types_copy = o2_heapify(typespec); if (!types_copy) goto error_return_2; // coerce to int to avoid compiler warning -- this could overflow but // only in cases where it would be impossible to construct a message types_len = (int) strlen(typespec); } handler->type_string = types_copy; handler->types_len = types_len; handler->coerce_flag = coerce; handler->parse_args = parse; // case 1: method is global handler for entire service replacing a // PATTERN_NODE with specific handlers: remove the PATTERN_NODE // and insert a new PATTERN_HANDLER as local service. // case 2: method is a global handler, replacing an existing global handler: // same as case 1 so we can use o2_service_replace to clean up the // old handler rather than duplicate that code. // case 2: method is a specific handler and a global handler exists: // replace the global handler with a PATTERN_NODE and continue to // case 3 // case 3: method is a specific handler and a PATTERN_NODE exists as the // local service: build the path in the tree according to the // the remaining address string // slash here means path has nodes, e.g. /serv/foo vs. just /serv if (!slash) { // (cases 1 and 2) handler->key = NULL; int rslt = o2_service_provider_replace(o2_process, key + 1, (o2_info_ptr) handler); O2_FREE(key); // do not need full path for global handler return rslt; } if (node->tag == PATTERN_HANDLER) { // change it to an empty node_entry node = o2_node_new(NULL); if (!node) goto error_return_3; if ((ret = o2_service_provider_replace(o2_process, key + 1, (o2_info_ptr) node))) { goto error_return_3; } } // now node is the root of a path tree for all paths for this service if (slash) { *slash = '/'; // restore the full path in key remaining = slash + 1; } while ((slash = strchr(remaining, '/'))) { *slash = 0; // terminate the string at the "/" o2_string_pad(name, remaining); *slash = '/'; // restore the string remaining = slash + 1; // if necessary, allocate a new entry for name node = tree_insert_node(node, name); assert(node); // node is now the node for the path up to name } // node is now where we should put the final path name with the handler; // remaining points to the final segment of the path handler->key = o2_heapify(remaining); if ((ret = o2_entry_add(node, (o2_entry_ptr) handler))) { goto error_return_3; } // make an entry for the master table handler_entry_ptr mhandler = (handler_entry_ptr) O2_MALLOC(sizeof(handler_entry)); if (!mhandler) goto error_return_3; memcpy(mhandler, handler, sizeof(handler_entry)); // copy the handler info mhandler->key = key; // this key has already been copied mhandler->full_path = NULL; // only leaf nodes have full_path pointer if (types_copy) types_copy = o2_heapify(typespec); mhandler->type_string = types_copy; // put the entry in the master table ret = o2_entry_add(&o2_full_path_table, (o2_entry_ptr) mhandler); goto just_return; error_return_3: if (types_copy) O2_FREE((void *) types_copy); error_return_2: O2_FREE(handler); error_return: O2_FREE(key); just_return: return ret; } const char *info_to_ipport(o2_info_ptr info) { return info->tag == TCP_SOCKET ? ((process_info_ptr) info)->proc.name : o2_process->proc.name; } void pick_service_provider(dyn_array_ptr list) { int top_index = 0; if (top_index >= list->length) return; o2_info_ptr top_info = GET_SERVICE(*list, top_index); const char *top_name = info_to_ipport(top_info); for (int i = 1; i < list->length; i++) { o2_info_ptr info = GET_SERVICE(*list, i); const char *name = info_to_ipport(info); if (strcmp(name, top_name) > 0) { top_info = info; top_name = name; top_index = i; } } // swap top_index and 0 DA_SET(*list, o2_info_ptr, top_index, GET_SERVICE(*list, 0)); DA_SET(*list, o2_info_ptr, 0, top_info); } /** replace the service named service_name offered by proc with new_service. * if new_service is NULL, remove the service, and if this is the last service, * remove the whole services entry. * * prereq: service_name does not have '/' */ int o2_service_provider_replace(process_info_ptr proc, const char *service_name, o2_info_ptr new_service) { services_entry_ptr *services = o2_services_find(service_name); if (!*services || (*services)->tag != SERVICES) { O2_DBg(printf("%s o2_service_provider_replace(%s, %s) did not find " "service\n", o2_debug_prefix, proc->proc.name, service_name)); return O2_FAIL; } dyn_array_ptr list = &((*services)->services); // list of services int i; for (i = 0; i < list->length; i++) { o2_info_ptr service = GET_SERVICE(*list, i); int tag = service->tag; if (tag == TCP_SOCKET && (process_info_ptr) service == proc) { break; } else if ((tag == PATTERN_NODE || tag == PATTERN_HANDLER) && proc == o2_process) { entry_free((o2_entry_ptr) service); break; } else if (tag == OSC_REMOTE_SERVICE && proc == o2_process) { // shut down any OSC connection osc_info_free((osc_info_ptr) service); break; } else { assert(tag != O2_BRIDGE_SERVICE); } } // if we did not find what we wanted to replace, stop here if (i >= list->length) { O2_DBg(printf("%s o2_service_provider_replace(%s, %s) did not find " "service offered by this process\n", o2_debug_prefix, proc->proc.name, service_name)); return O2_FAIL; } // we found the service to replace; finalized the info depending on the // type, so now we have a dangling pointer in the services list if (new_service) { // replace and we're finished DA_SET(*list, o2_info_ptr, i, new_service); return O2_SUCCESS; } // "replacement" is NULL, so we have to remove the listing DA_REMOVE(*list, process_info_ptr, i); if (list->length == 0) { entry_remove(&o2_path_tree, (o2_entry_ptr *) services, TRUE); } else if (i == 0) { // move top ip:port provider to top spot pick_service_provider(list); } // if the service was local, tell other processes that it is gone if (proc == o2_process) { o2_notify_others(service_name, FALSE); } // proc also has a list of services it provides; remove service list = &(proc->proc.services); for (int j = 0; j < proc->proc.services.length; j++) { if (streql(*DA_GET(*list, char *, i), service_name)) { DA_REMOVE(*list, char *, i); return O2_SUCCESS; } } O2_DBg(printf("%s o2_service_provider_replace(%s, %s) did not find " "service in process_info's services list\n", o2_debug_prefix, proc->proc.name, service_name)); return O2_FAIL; } // remove a service from o2_path_tree // int o2_service_free(char *service_name) { if (!service_name || strchr(service_name, '/')) return O2_BAD_SERVICE_NAME; return o2_service_provider_replace(o2_process, service_name, NULL); } int o2_embedded_msgs_deliver(o2_msg_data_ptr msg, int tcp_flag) { char *end_of_msg = PTR(msg) + MSG_DATA_LENGTH(msg); o2_msg_data_ptr embedded = (o2_msg_data_ptr) (msg->address + o2_strsize(msg->address) + sizeof(int32_t)); while (PTR(embedded) < end_of_msg) { o2_msg_data_send(embedded, tcp_flag); embedded = (o2_msg_data_ptr) (PTR(embedded) + MSG_DATA_LENGTH(embedded) + sizeof(int32_t)); } return O2_SUCCESS; } // deliver msg locally and immediately void o2_msg_data_deliver(o2_msg_data_ptr msg, int tcp_flag, o2_info_ptr service) { if (IS_BUNDLE(msg)) { o2_embedded_msgs_deliver(msg, tcp_flag); return; } char *address = msg->address; if (!service) { service = o2_msg_service(msg); if (!service) return; // service must have been removed } // we are going to deliver a non-bundle message, so we'll need // to find the type string... char *types = address; while (types[3]) types += 4; // find end of address string // types[3] is the zero marking the end of the address, // types[4] is the "," that starts the type string, so // types + 5 is the first type char types += 5; // now types points to first type char // if you o2_add_message("/service", ...) then the service entry is a // pattern handler used for ALL messages to the service if (service->tag == PATTERN_HANDLER) { call_handler((handler_entry_ptr) service, msg, types); } else if ((address[0]) == '!') { // do full path lookup address[0] = '/'; // must start with '/' to get consistent hash value o2_entry_ptr handler = *o2_lookup(&o2_full_path_table, address); address[0] = '!'; // restore address for no particular reason if (handler && handler->tag == PATTERN_HANDLER) { call_handler((handler_entry_ptr) handler, msg, types); } } else if (service->tag == PATTERN_NODE) { char name[NAME_BUF_LEN]; address = strchr(address + 1, '/'); // search for end of service name if (!address) { // address is "/service", but "/service" is not a PATTERN_HANDLER ; } else { find_and_call_handlers_rec(address + 1, name, (o2_entry_ptr) service, msg, types); } } // the assumption that the service is local fails, drop the message } void o2_node_finish(node_entry_ptr node) { for (int i = 0; i < node->children.length; i++) { o2_entry_ptr e = *DA_GET(node->children, o2_entry_ptr, i); while (e) { o2_entry_ptr next = e->next; entry_free(e); e = next; } } // not all nodes have keys, top-level nodes have key == NULL if (node->key) O2_FREE((void *) node->key); } // copy a string to the heap, result is 32-bit word aligned, has // at least one zero end-of-string byte and is // zero-padded to the next word boundary o2string o2_heapify(const char *path) { long len = o2_strsize(path); char *rslt = (char *) O2_MALLOC(len); if (!rslt) return NULL; // zero fill last 4 bytes int32_t *end_ptr = (int32_t *) WORD_ALIGN_PTR(rslt + len - 1); *end_ptr = 0; strcpy(rslt, path); return rslt; } // set fields for a node in the path tree // // key is "owned" by the caller // node_entry_ptr o2_node_initialize(node_entry_ptr node, const char *key) { node->tag = PATTERN_NODE; node->key = key; if (key) { // note: top level and services don't have keys node->key = o2_heapify(key); if (!node->key) { O2_FREE(node); return NULL; } } node->num_children = 0; initialize_table(&(node->children), 2); return node; } // o2_lookup returns a pointer to a pointer to the entry, if any. // The hash table uses linked lists for collisions to make // deletion simple. key must be aligned on a 32-bit word boundary // and must be padded with zeros to a 32-bit boundary o2_entry_ptr *o2_lookup(node_entry_ptr node, o2string key) { int n = node->children.length; int64_t hash = get_hash(key); int index = hash % n; // printf("o2_lookup %s in %s hash %ld index %d\n", key, node->key, hash, *index); o2_entry_ptr *ptr = DA_GET(node->children, o2_entry_ptr, index); while (*ptr) { if (streql(key, (*ptr)->key)) { break; } ptr = &((*ptr)->next); } return ptr; } #ifndef NEGATE #define NEGATE '!' #endif /** * robust glob pattern matcher * * @param str oringinal string, a node name terminated by zero (eos) * @param p the string with pattern, p can be the remainder of a whole * address pattern, so it is terminated by either zero (eos) * or slash (/) * * @return Iff match, return TRUE. * * glob patterns: * * matches zero or more characters * ? matches any single character * [set] matches any character in the set * [^set] matches any character NOT in the set * where a set is a group of characters or ranges. a range * is written as two characters seperated with a hyphen: a-z denotes * all characters between a to z inclusive. * [-set] set matches a literal hypen and any character in the set * []set] matches a literal close bracket and any character in the set * * char matches itself except where char is '*' or '?' or '[' * \char matches char, including any pattern character * * examples: * a*c ac abc abbc ... * a?c acc abc aXc ... * a[a-z]c aac abc acc ... * a[-a-z]c a-c aac abc ... */ static int o2_pattern_match(const char *str, const char *p) { int negate; // the ! is used before a character or range of characters int match; // a boolean used to exit a loop looking for a match char c; // a character from the pattern // match each character of the pattern p with string str up to // pattern end marked by zero (end of string) or '/' while (*p && *p != '/') { // fast exit: if we have exhausted str and there is more // pattern to match, give up (unless *p is '*', which can // match zero characters) // also, [!...] processing assumes a character to match in str // without checking, so the case of !*str is handled here if (!*str && *p != '*') { return FALSE; } // process the next character(s) of the pattern switch ((c = *p++)) { case '*': // matches 0 or more characters while (*p == '*') p++; // "*...*" is equivalent to "*" so // skip over a run of '*'s // if there are no more pattern characters, we can match // '*' to the rest of str, so we have a match. This is an // optimization that tests for a special case: if (!*p || *p == '/') return TRUE; // if the next pattern character is not a meta character, // we can skip over all the characters in str that // do not match: at least these skipped characters must // match the '*'. This is an optimization: if (*p != '?' && *p != '[' && *p != '{') while (*str && *p != *str) str++; // we do not know if '*' should match more characters or // not, so we have to try every possibility. This is // done recursively. There are more special cases and // possible optimizations, but at this point we give up // looking for special cases and just try everything: while (*str) { if (o2_pattern_match(str, p)) { return TRUE; } str++; } return FALSE; case '?': // matches exactly 1 character in str if (*str) break; // success return FALSE; /* * set specification is inclusive, that is [a-z] is a, z and * everything in between. this means [z-a] may be interpreted * as a set that contains z, a and nothing in between. */ case '[': if (*p != NEGATE) { negate = 1; } else { negate = 0; // note that negate == 0 if '!' found p++; // so in this case, 0 means "true" } match = 0; // no match found yet // search in set for a match until it is found // if/when you exit the loop, p is pointing to ']' or // before it, or c == ']' and p points to the // next character, or there is no matching ']' while (!match && (c = *p++)) { if (!*p || *p == '/') { // no matching ']' in pattern return FALSE; } else if (c == ']') { p--; // because we search forward for ']' below break; } else if (*p == '-') { // expected syntax is c-c p++; if (!*p || *p == '/') return FALSE; // expected to find at least ']' if (*p != ']') { // found end of range match = (*str == c || *str == *p || (*str > c && *str < *p)); } else { // c-] means ok to match c or '-' match = (*str == c || *str == '-'); } } else { // no dash, so see if we match 'c' match = (c == *str); } } if (negate == match) { return FALSE; } // if there is a match, skip past the cset and continue on while ((c = *p++) != ']') { if (!c || c == '/') { // no matching ']' in pattern return FALSE; } } break; /* * {astring,bstring,cstring}: This is tricky because astring * could be a prefix of bstring, so even if astring matches * the beginning of str, we may have to backtrack and match * bstring in order to get an overall match */ case '{': { // *p is now first character in the {brace list} const char *place = str; // to backtrack const char *remainder = p; // to forwardtrack // find the end of the brace list (or end of pattern) c = *remainder; while (c != '}') { if (!c || c == '/') { // unexpected end of pattern return FALSE; } c = *remainder++; } c = *p++; // test each string in the {brace list}. At the top of // the loop: // c is a character of a {brace list} string // p points to the next character after c // str points to the so-far unmatched remainder of // the address // place points to the location in str that must // be matched with this {brace list} while (c && c != '/') { if (c == ',') { // recursively see if we can complete the match if (o2_pattern_match(str, remainder)) { return TRUE; } else { str = place; // backtrack on test string // continue testing, skipping the comma p++; if (!*p || *p == '/') { // unexpected end return FALSE; } } } else if (c == '}') { str--; // str is incremented again below break; } else if (c == *str) { // match a character str++; } else { // skip to next comma str = place; while ((c = *p++) != ',') { if (!c || c == '/' || c == '}') { return FALSE; // no more choices, so no match } } } c = *p++; } break; } default: if (c != *str) { return FALSE; } break; } str++; } // since we have reached the end of the pattern, we match iff we are // also at the end of the string: return (*str == 0); } /** * \brief remove a path -- find the leaf node in the tree and remove it. * * When the method is no longer exist, or the method conflict with a new one. * we need to call this function to delete the old one. The master table entry * will be removed as a side effect. If a parent node becomes empty, the * parent is removed. Thus we use a recursive algorithm so we can examine * parents after visiting the children. * * @param path The path of the method * * @return If succeed, return O2_SUCCESS. If not, return O2_FAIL. */ int o2_remove_method(const char *path) { // this is one of the few times where we need the result of o2_heapify // to be writeable, so coerce from o2string to (char *) char *path_copy = (char *) o2_heapify(path); if (!path_copy) return O2_FAIL; char name[NAME_BUF_LEN]; // search path elements as tree nodes -- to get the keys, replace each // "/" with EOS and o2_heapify to copy it, then restore the "/" char *remaining = path_copy + 1; // skip the initial "/" return remove_method_from_tree(remaining, name, &o2_path_tree); } // Called when TCP_SOCKET gets a TCP_HUP (hang-up) error; // Delete the socket and data associated with it: // for TCP_SOCKET: // remove all services for this process, these all point to a single // process_info_ptr // if the services_entry becomes empty (and it will for the ip:port // service), remove the services_entry // delete this process_info_ptr contents, including: // proc.name // array of service names (names themselves are keys to // services_entry, so they are not freed (or maybe they are // already freed by the time we free this array) // any message // mark the socket to be freed, and in a deferred action, the // socket is closed and removed from the o2_fds array. The // corresponding o2_fds_info entry is freed then too // for UDP_SOCKET, OSC_DISCOVER_SOCKET, OSC_TCP_SOCKET, // TCP_SERVER_SOCKET, TCP_SERVER_SOCKET, OSC_TCP_SOCKET (name is // "owned" by OSC_TCP_SERVER_SOCKET): // free any message // mark the socket to be freed later // for OSC_SOCKET, OSC_TCP_SERVER_SOCKET, OSC_TCP_CLIENT: // (it must be the case that we're shutting down: when we free the // osc.service_name, all the OSC_TCP_SOCKETS accepted from this // socket will have freed osc.service_name fields) // free the osc.service_name (it's a copy) // free any message // mark the socket to be freed later // int o2_remove_remote_process(process_info_ptr info) { if (info->tag == TCP_SOCKET) { // remove the remote services provided by the proc remove_remote_services(info); // proc.name may be NULL if we have not received an init (/_o2/dy) // message if (info->proc.name) { O2_DBd(printf("%s removing remote process %s\n", o2_debug_prefix, info->proc.name)); O2_FREE((void *) info->proc.name); info->proc.name = NULL; } } else if (info->tag == OSC_SOCKET || info->tag == OSC_TCP_SERVER_SOCKET || info->tag == OSC_TCP_CLIENT) { O2_FREE((void *) info->osc.service_name); } if (info->message) O2_FREE(info->message); o2_socket_mark_to_free(info); // close the TCP socket return O2_SUCCESS; } // tree_insert_node -- insert a node for pattern matching. // on entry, table points to a tree node pointer, initially it is the // address of o2_path_tree. If key is already in the table and the // entry is another node, then just return a pointer to the node address. // Otherwise, if key is a handler, remove it, and then create a new node // to represent this key. // // key is "owned" by caller and must be aligned to 4-byte word boundary // static node_entry_ptr tree_insert_node(node_entry_ptr node, o2string key) { assert(node->children.length > 0); o2_entry_ptr *entry_ptr = o2_lookup(node, key); // 3 outcomes: entry exists and is a PATTERN_NODE: return location // entry exists but is something else: delete old and create one // entry does not exist: create one if (*entry_ptr) { if ((*entry_ptr)->tag == PATTERN_NODE) { return (node_entry_ptr) *entry_ptr; } else { // this node cannot be a handler (leaf) and a (non-leaf) node entry_remove(node, entry_ptr, FALSE); } } // entry is a valid location. Insert a new node: node_entry_ptr new_entry = o2_node_new(key); o2_add_entry_at(node, entry_ptr, (o2_entry_ptr) new_entry); return new_entry; } // remove a child from a node. Then free the child // (deleting its entire subtree, or if it is a leaf, removing the // entry from the o2_full_path_table). // ptr is the address of the pointer to the table entry to be removed. // This ptr must be a value returned by o2_lookup or o2_service_find // Often, we remove an entry to make room for an insertion, so // we do not want to resize the table. The resize parameter must // be true to enable resizing. // static int entry_remove(node_entry_ptr node, o2_entry_ptr *child, int resize) { node->num_children--; o2_entry_ptr entry = *child; *child = entry->next; entry_free(entry); // if the table is too big, rehash to smaller table if (resize && (node->num_children * 3 < node->children.length) && (node->num_children > 3)) { // suppose we jumped to 12 locations when we got up to 8 entries. // when we go back down to 4 entries, we still allow 12, but when // we are one less, 3 entries, we want to cut the size in half. // Therefore the math is (3 + 1) * 3 / 2 = 6, which is half of 12. // We do not make table size less than 3. return resize_table(node, ((node->num_children + 1) * 3) / 2); } return O2_SUCCESS; } // recursive function to remove path from tree. Follow links to the leaf // node, remove it, then as the stack unwinds, remove empty nodes. // remaining is the full path, which is manipulated to isolate node names. // name is storage to copy and pad node names. // table is the current node. // // returns O2_FAIL if path is not found in tree (should not happen) // static int remove_method_from_tree(char *remaining, char *name, node_entry_ptr node) { char *slash = strchr(remaining, '/'); o2_entry_ptr *entry_ptr; // another return value from o2_lookup if (slash) { // we have an internal node name *slash = 0; // terminate the string at the "/" o2_string_pad(name, remaining); *slash = '/'; // restore the string entry_ptr = o2_lookup(node, name); if ((!*entry_ptr) || ((*entry_ptr)->tag != PATTERN_NODE)) { printf("could not find method\n"); return O2_FAIL; } // *entry addresses a node entry node = (node_entry_ptr) *entry_ptr; remove_method_from_tree(slash + 1, name, node); if (node->num_children == 0) { // remove the empty table return entry_remove(node, entry_ptr, TRUE); } return O2_SUCCESS; } // now table is where we find the final path name with the handler // remaining points to the final segment of the path o2_string_pad(name, remaining); entry_ptr = o2_lookup(node, name); // there should be an entry, remove it if (*entry_ptr) { entry_remove(node, entry_ptr, TRUE); return O2_SUCCESS; } return O2_FAIL; } // remove a dictionary entry by name // when we remove an entry, we may resize the table to be smaller // in which case *node is written with a pointer to the new table // and the old table is freed // static int remove_node(node_entry_ptr node, o2string key) { o2_entry_ptr *ptr = o2_lookup(node, key); if (*ptr) { return entry_remove(node, ptr, TRUE); } return O2_FAIL; } // for each service named in info (a process_info_ptr), // find the service offered by this process and remove it: // since info has tag TCP_SOCKET, each service will be a // pointer back to this process_info_ptr, so do not delete info // if a service is the last service in services, remove the // services_entry as well // deallocate the dynamic array holding service names // static int remove_remote_services(process_info_ptr info) { assert(info->tag == TCP_SOCKET); while (info->proc.services.length > 0) { o2string service_name = *DA_GET(info->proc.services, o2string, 0); o2_service_provider_replace(info, service_name, NULL); // note: o2_service_provider_replace will, as a side effect, remove // service_name from info->proc.services. In fact, it will search // the array for a matching string, which is a bit silly, but since // we're always deleting the first element, the search will always // find service_name immediately (at the cost of 1 string compare), // so the operation is pretty efficient. } DA_FINISH(info->proc.services); return O2_SUCCESS; } static int resize_table(node_entry_ptr node, int new_locs) { dyn_array old = node->children; // copy whole dynamic array if (initialize_table(&(node->children), new_locs)) return O2_FAIL; // now, old array is in old, node->children is newly allocated // copy all entries from old to nde->children assert(node->children.array != NULL); enumerate enumerator; enumerate_begin(&enumerator, &old); o2_entry_ptr entry; while ((entry = enumerate_next(&enumerator))) { o2_entry_add(node, entry); } // now we have moved all entries into the new table and we can free the // old one DA_FINISH(old); return O2_SUCCESS; } // o2_string_pad -- copy src to dst, adding zero padding to word boundary // // dst MUST point to a buffer of size NAME_BUF_LEN or bigger // void o2_string_pad(char *dst, const char *src) { size_t len = strlen(src); if (len >= NAME_BUF_LEN) { len = NAME_BUF_LEN - 1; } // first fill last 32-bit word with zeros so the final word will be zero-padded int32_t *last_32 = (int32_t *)(dst + WORD_OFFSET(len)); // round down to word boundary *last_32 = 0; // now copy the string; this may overwrite some zero-pad bytes: strncpy(dst, src, len); } o2-1.0/src/o2_discovery.h0000644000175000017500000000304313072261166015477 0ustar zmoelnigzmoelnig// // o2_discovery.h // O2 // // Created by 弛张 on 1/26/16. // Copyright © 2016 弛张. All rights reserved. // #ifndef o2_discovery_h #define o2_discovery_h #define PORT_MAX 16 extern SOCKET o2_discovery_socket; extern int o2_port_map[16]; /** * Initialize for discovery * * @return O2_SUCCESS (0) if succeed, O2_FAIL (-1) if not. */ int o2_discovery_initialize(); int o2_discovery_finish(); int o2_discovery_msg_initialize(); /** * Discover function will send the discover messages and deal with all the discover * messages sent to the discover socket. Record all the information in the * remote_process arrays and periodly check for update. If there exists a new * remote_process, the o2_discover() will automatically set up a new tcp * connection with the remote_process. * * @return 0 if succeed, 1 if there is some error. */ void o2_discovery_send_handler(o2_msg_data_ptr msg, const char *types, o2_arg_ptr *argv, int argc, void *user_data); int o2_send_initialize(process_info_ptr process); int o2_send_services(process_info_ptr process); void o2_discovery_handler(o2_msg_data_ptr msg, const char *types, o2_arg_ptr *argv, int argc, void *user_data); void o2_discovery_init_handler(o2_msg_data_ptr msg, const char *types, o2_arg_ptr *argv, int argc, void *user_data); void o2_services_handler(o2_msg_data_ptr msg, const char *types, o2_arg_ptr *argv, int argc, void *user_data); #endif /* O2_discovery_h */ o2-1.0/src/o2_socket.h0000644000175000017500000001461513072261166014767 0ustar zmoelnigzmoelnig// // o2_socket.h // O2 // // Created by 弛张 on 2/4/16. // Copyright © 2016 弛张. All rights reserved. // #ifndef o2_socket_h #define o2_socket_h /** * TCP and UDP head for different system */ #ifdef WIN32 #include #include // Header for tcp connections #define socklen_t int #define sleep Sleep #define strdup _strdup /* Define pollfd for Windows */ struct poll_fd { __int64 fd; /* the windows socket number */ int events; /* not used, but needed for compatibility */ }; #else #include #include #include #include #include "arpa/inet.h" // Headers for all inet functions //#ifdef ENABLE_THREADS //#include //#endif #include #define closesocket close #include // Header for socket transportations. Included "stdint.h" typedef int SOCKET; // In O2, we'll use SOCKET to denote the type of a socket #define INVALID_SOCKET -1 #include #endif /** * The process_info structure tells us info about each socket. For Unix, there * is a parallel structure, fds, that contains an fds parameter for poll(). * * In process_info, we have the socket, a handler for the socket, and buffer * info to store incoming data, and the service the socket is attached to. * This structure is also used to represent a remote service if the * tag is TCP_SOCKET. * */ #define UDP_SOCKET 100 #define TCP_SOCKET 101 #define OSC_SOCKET 102 #define DISCOVER_SOCKET 103 #define TCP_SERVER_SOCKET 104 #define OSC_TCP_SERVER_SOCKET 105 #define OSC_TCP_SOCKET 106 #define OSC_TCP_CLIENT 107 struct process_info; struct fds_info; // recursive declarations o2_socket_handler and fds_info typedef int (*o2_socket_handler)(SOCKET sock, struct process_info *info); /* info->proc.status values */ #define PROCESS_LOCAL 0 // process is the local process #define PROCESS_CONNECTED 1 // connect call returned or accepted connection #define PROCESS_NO_CLOCK 2 // process initial message received, not clock synced #define PROCESS_OK 3 // process is clock synced // anything with a tag is of the "abstract superclass" o2_info // subclasses are process_info, osc_info, and o2_entry typedef struct o2_info { int tag; } o2_info, *o2_info_ptr; typedef struct process_info { // "subclass" of o2_info int tag; // UDP_SOCKET, TCP_SOCKET, DISCOVER_SOCKET, TCP_SERVER_SOCKET // OSC_SOCKET, OSC_TCP_SERVER_SOCKET, // OSC_TCP_SOCKET, OSC_TCP_CLIENT int fds_index; // index of socket in o2_fds and o2_fds_info // -1 if process known but not connected int delete_me; // set to TRUE when socket should be removed int32_t length; // message length o2_message_ptr message; // message data from TCP stream goes here int length_got; // how many bytes of length have been read? int message_got; // how many bytes of message have been read? o2_socket_handler handler; // handler for socket int port; // port number: if this is a TCP_SOCKET, this is the UDP port // number so that we can check for changes in discovery, and // port is set by an incoming init message, the first message // received on the port. // If this is an OSC_SOCKET or OSC_TCP_SERVER_SOCKET, // this is the corresponding port number that is used by // o2_osc_port_free(). If this is an OSC_TCP_SOCKET, then // this is the port number of the OSC_TCP_SERVER_SOCKET from // which this socket was accepted. (It is used by // o2_osc_port_free() to identify the sockets to close.) union { struct { o2string name; // e.g. "128.2.1.100:55765", this is used so that when // we add a service, we can enumerate all the processes and send // them updates. Updates are addressed using this name field. Also, // when a new process is connected, we send an /in message to this // name. name is "owned" by the process_info struct and will be // deleted when the struct is freed int status; // PROCESS_LOCAL through PROCESS_OK dyn_array services; // these are the keys of remote_service_entry // objects, owned by the service entries (do not free) struct sockaddr_in udp_sa; // address for sending UDP messages } proc; struct { o2string service_name; } osc; }; } process_info, *process_info_ptr; extern char o2_local_ip[24]; extern int o2_local_tcp_port; extern dyn_array o2_fds_info; #define GET_PROCESS(i) (*DA_GET(o2_fds_info, process_info_ptr, (i))) extern int o2_found_network; // true if we have an IP address, which implies a // network connection; if false, we only talk to 127.0.0.1 (localhost) extern dyn_array o2_fds; ///< pre-constructed fds parameter for poll() extern int o2_socket_delete_flag; /** * In windows, before we want to use the socket to transport, we need * to initialize the socket first. Call this function. * * @returns: return the state of the socket */ #ifdef WIN32 int o2_initWSock(); #endif #ifndef O2_NO_DEBUGGING #define SOCKET_DEBUG #ifdef SOCKET_DEBUG void o2_sockets_show(); #endif #endif process_info_ptr o2_add_new_socket(SOCKET sock, int tag, o2_socket_handler handler); void o2_disable_sigpipe(SOCKET sock); int o2_process_initialize(process_info_ptr info, int status); void o2_socket_mark_to_free(process_info_ptr info); int o2_sockets_initialize(); int o2_make_tcp_recv_socket(int tag, int port, o2_socket_handler handler, process_info_ptr *info); int o2_make_udp_recv_socket(int tag, int *port, process_info_ptr *info); int o2_osc_delegate_handler(SOCKET sock, process_info_ptr info); void o2_free_deleted_sockets(); /** * o2_recv will check all the set up sockets of the local process, * including the udp socket and all the tcp sockets. The message will be * dispatched to a matching method if one is found. * Note: the recv will not set up new socket, as o2_discover will do that * for the local process. * * @return O2_SUCESS if succeed, O2_FAIL if not. */ int o2_recv(); int o2_tcp_initial_handler(SOCKET sock, process_info_ptr info); int o2_osc_tcp_accept_handler(SOCKET sock, process_info_ptr info); #endif /* o2_socket_h */ o2-1.0/src/o2_socket.c0000644000175000017500000006617013072261166014765 0ustar zmoelnigzmoelnig// // o2_socket.c // O2 // // Created by 弛张 on 2/4/16. // Copyright © 2016 弛张. All rights reserved. // #include "ctype.h" #include "o2_internal.h" #include "o2_discovery.h" #include "o2_message.h" #include "o2_sched.h" #include "o2_send.h" #include "o2_interoperation.h" #include "o2_socket.h" #ifdef WIN32 #include #include #include #include typedef struct ifaddrs { struct ifaddrs *ifa_next; /* Next item in list */ char *ifa_name; /* Name of interface */ unsigned int ifa_flags; /* Flags from SIOCGIFFLAGS */ struct sockaddr *ifa_addr; /* Address of interface */ struct sockaddr *ifa_netmask; /* Netmask of interface */ union { struct sockaddr *ifu_broadaddr; /* Broadcast address of interface */ struct sockaddr *ifu_dstaddr; /* Point-to-point destination address */ } ifa_ifu; #define ifa_broadaddr ifa_ifu.ifu_broadaddr #define ifa_dstaddr ifa_ifu.ifu_dstaddr void *ifa_data; /* Address-specific data */ } ifaddrs; #else #include "sys/ioctl.h" #include #endif static int osc_tcp_handler(SOCKET sock, process_info_ptr info); static int read_whole_message(SOCKET sock, process_info_ptr info); static int tcp_accept_handler(SOCKET sock, process_info_ptr info); static void tcp_message_cleanup(process_info_ptr info); static int tcp_recv_handler(SOCKET sock, process_info_ptr info); static int udp_recv_handler(SOCKET sock, process_info_ptr info); char o2_local_ip[24]; int o2_local_tcp_port = 0; SOCKET local_send_sock = INVALID_SOCKET; // socket for sending all UDP msgs dyn_array o2_fds; ///< pre-constructed fds parameter for poll() dyn_array o2_fds_info; ///< info about sockets process_info_ptr o2_process = NULL; ///< the process descriptor for this process int o2_found_network = FALSE; static struct sockaddr_in o2_serv_addr; int o2_socket_delete_flag = FALSE; // flag to find deleted sockets static int bind_recv_socket(SOCKET sock, int *port, int tcp_recv_flag) { memset(PTR(&o2_serv_addr), 0, sizeof(o2_serv_addr)); o2_serv_addr.sin_family = AF_INET; o2_serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); // local IP address o2_serv_addr.sin_port = htons(*port); unsigned int yes = 1; if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, PTR(&yes), sizeof(yes)) < 0) { perror("setsockopt(SO_REUSEADDR)"); return O2_FAIL; } if (bind(sock, (struct sockaddr *) &o2_serv_addr, sizeof(o2_serv_addr))) { if (tcp_recv_flag) perror("Bind receive socket"); return O2_FAIL; } if (*port == 0) { // find the port that was (possibly) allocated socklen_t addr_len = sizeof(o2_serv_addr); if (getsockname(sock, (struct sockaddr *) &o2_serv_addr, &addr_len)) { perror("getsockname call to get port number"); return O2_FAIL; } *port = ntohs(o2_serv_addr.sin_port); // set actual port used } // printf("* %s: bind socket %d port %d\n", o2_debug_prefix, sock, *port); assert(*port != 0); return O2_SUCCESS; } // all O2 messages arriving by TCP or UDP are funneled through here static void deliver_or_schedule(process_info_ptr info) { // make sure endian is compatible #if IS_LITTLE_ENDIAN o2_msg_swap_endian(&(info->message->data), FALSE); #endif O2_DBr(if (info->message->data.address[1] != '_' && !isdigit(info->message->data.address[1])) o2_dbg_msg("msg received", &info->message->data, "type", o2_tag_to_string(info->tag))); O2_DBR(if (info->message->data.address[1] == '_' || isdigit(info->message->data.address[1])) o2_dbg_msg("msg received", &info->message->data, "type", o2_tag_to_string(info->tag))); o2_message_send_sched(info->message, TRUE); } #ifdef WIN32 static struct sockaddr *dupaddr(const sockaddr_gen * src) { sockaddr_gen * d = malloc(sizeof(*d)); if (d) { memcpy(d, src, sizeof(*d)); } return (struct sockaddr *) d; } static void freeifaddrs(struct ifaddrs *ifp) { struct ifaddrs *next; while (ifp) { next = ifp->ifa_next; free(ifp); ifp = next; } } static int getifaddrs(struct ifaddrs **ifpp) { SOCKET sock = INVALID_SOCKET; size_t intarray_len = 8192; int ret = -1; INTERFACE_INFO *intarray = NULL; *ifpp = NULL; sock = socket(AF_INET, SOCK_DGRAM, 0); if (sock == INVALID_SOCKET) return -1; for (;;) { DWORD cbret = 0; intarray = malloc(intarray_len); if (!intarray) break; ZeroMemory(intarray, intarray_len); if (WSAIoctl(sock, SIO_GET_INTERFACE_LIST, NULL, 0, (LPVOID) intarray, (DWORD) intarray_len, &cbret, NULL, NULL) == 0) { intarray_len = cbret; break; } free(intarray); intarray = NULL; if (WSAGetLastError() == WSAEFAULT && cbret > intarray_len) { intarray_len = cbret; } else { break; } } if (!intarray) goto _exit; /* intarray is an array of INTERFACE_INFO structures. intarray_len has the actual size of the buffer. The number of elements is intarray_len/sizeof(INTERFACE_INFO) */ { size_t n = intarray_len / sizeof(INTERFACE_INFO); size_t i; for (i = 0; i < n; i++) { struct ifaddrs *ifp; ifp = malloc(sizeof(*ifp)); if (ifp == NULL) break; ZeroMemory(ifp, sizeof(*ifp)); ifp->ifa_next = NULL; ifp->ifa_name = NULL; ifp->ifa_flags = intarray[i].iiFlags; ifp->ifa_addr = dupaddr(&intarray[i].iiAddress); ifp->ifa_netmask = dupaddr(&intarray[i].iiNetmask); ifp->ifa_broadaddr = dupaddr(&intarray[i].iiBroadcastAddress); ifp->ifa_data = NULL; *ifpp = ifp; ifpp = &ifp->ifa_next; } if (i == n) ret = 0; } _exit: if (sock != INVALID_SOCKET) closesocket(sock); if (intarray) free(intarray); return ret; } static int stateWSock = -1; int o2_initWSock() { WORD reqversion; WSADATA wsaData; if (stateWSock >= 0) { return stateWSock; } /* TODO - which version of Winsock do we actually need? */ reqversion = MAKEWORD(2, 2); if (WSAStartup(reqversion, &wsaData) != 0) { /* Couldn't initialize Winsock */ stateWSock = 0; } else if (LOBYTE(wsaData.wVersion) != LOBYTE(reqversion) || HIBYTE(wsaData.wVersion) != HIBYTE(reqversion)) { /* wrong version */ WSACleanup(); stateWSock = 0; } else { stateWSock = 1; } return stateWSock; } #endif #ifdef SOCKET_DEBUG void o2_sockets_show() { printf("Sockets:\n"); for (int i = 0; i < o2_fds.length; i++) { process_info_ptr info = GET_PROCESS(i); printf("%d: fd_index %d fd %lld tag %s info %p", i, info->fds_index, (long long) ((DA_GET(o2_fds, struct pollfd, i))->fd), o2_tag_to_string(info->tag), info); if (info->tag == TCP_SOCKET) { printf(" services:"); for (int j = 0; j < info->proc.services.length; j++) { printf("\n %s", *DA_GET(info->proc.services, char *, j)); } } else if (info->tag == OSC_SOCKET || info->tag == OSC_TCP_SERVER_SOCKET || info->tag == OSC_TCP_CLIENT) { printf("osc service %s", info->osc.service_name); } printf("\n"); } } #endif process_info_ptr o2_add_new_socket(SOCKET sock, int tag, o2_socket_handler handler) { // expand socket arrays for new port DA_EXPAND(o2_fds_info, process_info_ptr); DA_EXPAND(o2_fds, struct pollfd); process_info_ptr info = (process_info_ptr) O2_CALLOC(1, sizeof(process_info)); *DA_LAST(o2_fds_info, process_info_ptr) = info; info->tag = tag; info->fds_index = o2_fds.length - 1; // last element info->handler = handler; info->delete_me = FALSE; struct pollfd *pfd = DA_LAST(o2_fds, struct pollfd); pfd->fd = sock; pfd->events = POLLIN; pfd->revents = 0; return info; } int o2_process_initialize(process_info_ptr info, int status) { info->proc.status = status; DA_INIT(info->proc.services, o2string, 0); info->port = 0; memset(&info->proc.udp_sa, 0, sizeof(info->proc.udp_sa)); return O2_SUCCESS; } /** * Initialize discovery, tcp, and udp sockets. * * @return 0 (O2_SUCCESS) on success, -1 (O2_FAIL) on failure. */ int o2_sockets_initialize() { #ifdef WIN32 // Initialize (in Windows) WSADATA wsaData; WSAStartup(MAKEWORD(2, 2), &wsaData); #else DA_INIT(o2_fds, struct pollfd, 5); #endif // WIN32 DA_INIT(o2_fds_info, process_info_ptr, 5); memset(o2_fds_info.array, 0, 5 * sizeof(process_info_ptr)); // Set a broadcast socket. If cannot set up, // print the error and return O2_FAIL RETURN_IF_ERROR(o2_discovery_initialize()); // make udp receive socket for incoming O2 messages int port = 0; process_info_ptr info; RETURN_IF_ERROR(o2_make_udp_recv_socket(UDP_SOCKET, &port, &info)); // ignore the info for udp, get the info for tcp: // Set up the tcp server socket. RETURN_IF_ERROR(o2_make_tcp_recv_socket(TCP_SERVER_SOCKET, 0, &tcp_accept_handler, &o2_process)); assert(port != 0); o2_process->port = port; // more initialization in discovery, depends on tcp port which is now set RETURN_IF_ERROR(o2_discovery_msg_initialize()); /* IF THAT FAILS, HERE'S OLDER CODE TO GET THE TCP PORT struct sockaddr_in sa; socklen_t restrict sa_len = sizeof(sa); if (getsockname(o2_process.tcp_socket, (struct sockaddr *restrict) &sa, &sa_len) < 0) { perror("Getting port number from tcp server socket"); return O2_FAIL; } o2_process.tcp_port = ntohs(sa.sin_port); */ return O2_SUCCESS; } // Add a socket for TCP to sockets arrays o2_fds and o2_fds_info // As a side effect, if this is the TCP server socket, the // o2_process.key will be set to the server IP address int o2_make_tcp_recv_socket(int tag, int port, o2_socket_handler handler, process_info_ptr *info) { SOCKET sock = socket(AF_INET, SOCK_STREAM, 0); char name[32]; // "100.100.100.100:65000" -> 21 chars struct ifaddrs *ifap, *ifa; name[0] = 0; // initially empty if (sock == INVALID_SOCKET) { printf("tcp socket set up error"); return O2_FAIL; } O2_DBo(printf("%s created tcp socket %ld tag %s\n", o2_debug_prefix, (long) sock, o2_tag_to_string(tag))); if (tag == TCP_SERVER_SOCKET || tag == OSC_TCP_SERVER_SOCKET) { // only bind server port RETURN_IF_ERROR(bind_recv_socket(sock, &port, TRUE)); RETURN_IF_ERROR(listen(sock, 10)); O2_DBo(printf("%s bind and listen called on socket %ld\n", o2_debug_prefix, (long) sock)); } *info = o2_add_new_socket(sock, tag, handler); if (tag == TCP_SERVER_SOCKET) { o2_local_tcp_port = port; struct sockaddr_in *sa; // look for AF_INET interface. If you find one, copy it // to name. If you find one that is not 127.0.0.1, then // stop looking. if (getifaddrs(&ifap)) { perror("getting IP address"); return O2_FAIL; } for (ifa = ifap; ifa; ifa = ifa->ifa_next) { if (ifa->ifa_addr->sa_family==AF_INET) { sa = (struct sockaddr_in *) ifa->ifa_addr; if (!inet_ntop(AF_INET, &sa->sin_addr, o2_local_ip, sizeof(o2_local_ip))) { perror("converting local ip to string"); break; } sprintf(name, "%s:%d", o2_local_ip, port); if (!streql(o2_local_ip, "127.0.0.1")) { o2_found_network = O2_TRUE; break; } } } freeifaddrs(ifap); (*info)->proc.name = o2_heapify(name); RETURN_IF_ERROR(o2_process_initialize(*info, PROCESS_LOCAL)); } else { // a "normal" TCP connection: set NODELAY option // (NODELAY means that TCP messages will be delivered immediately // rather than waiting a short period for additional data to be // sent. Waiting might allow the outgoing packet to consolidate // sent data, resulting in greater throughput, but more latency. int option = 1; setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, (const char *) &option, sizeof(option)); if (tag == OSC_TCP_SERVER_SOCKET) { (*info)->port = port; } } return O2_SUCCESS; } int o2_make_udp_recv_socket(int tag, int *port, process_info_ptr *info) { SOCKET sock = socket(AF_INET, SOCK_DGRAM, 0); if (sock == INVALID_SOCKET) return O2_FAIL; // Bind the socket int err; if ((err = bind_recv_socket(sock, port, FALSE))) { closesocket(sock); return err; } O2_DBo(printf("%s created socket %ld and bind called to receive UDP\n", o2_debug_prefix, (long) sock)); *info = o2_add_new_socket(sock, tag, &udp_recv_handler); // printf("%s: o2_make_udp_recv_socket: listening on port %d\n", o2_debug_prefix, o2_process.port); return O2_SUCCESS; } // When service is delegated to OSC via TCP, a TCP connection // is created. Incoming messages are delivered to this // handler. // int o2_osc_delegate_handler(SOCKET sock, process_info_ptr info) { int n = read_whole_message(sock, info); if (n == O2_FAIL) { // not ready to process message yet return O2_SUCCESS; } else if (n != O2_SUCCESS) { return n; } O2_DBg(printf("%s ### ERROR: unexpected message from OSC server providing service %s\n", o2_debug_prefix, info->osc.service_name)); tcp_message_cleanup(info); return O2_SUCCESS; } // remove the i'th socket from o2_fds and o2_fds_info // static void socket_remove(int i) { struct pollfd *pfd = DA_GET(o2_fds, struct pollfd, i); O2_DBo(printf("%s socket_remove(%d), tag %d port %d closing socket %lld\n", o2_debug_prefix, i, GET_PROCESS(i)->tag, GET_PROCESS(i)->port, (long long) pfd->fd)); SOCKET sock = pfd->fd; #ifdef SHUT_WR shutdown(sock, SHUT_WR); #endif if (closesocket(pfd->fd)) perror("closing socket"); if (o2_fds.length > i + 1) { // move last to i struct pollfd *lastfd = DA_LAST(o2_fds, struct pollfd); memcpy(pfd, lastfd, sizeof(struct pollfd)); process_info_ptr info = *DA_LAST(o2_fds_info, process_info_ptr); GET_PROCESS(i) = info; // move to new index info->fds_index = i; } o2_fds.length--; o2_fds_info.length--; } void o2_free_deleted_sockets() { for (int i = 0; i < o2_fds_info.length; i++) { process_info_ptr info = GET_PROCESS(i); if (info->delete_me) { socket_remove(i); O2_FREE(info); i--; } } o2_socket_delete_flag = FALSE; } #ifdef WIN32 FD_SET o2_read_set; struct timeval o2_no_timeout; int o2_recv() { // if there are any bad socket descriptions, remove them now if (o2_socket_delete_flag) o2_free_deleted_sockets(); int total; FD_ZERO(&o2_read_set); for (int i = 0; i < o2_fds.length; i++) { struct pollfd *d = DA_GET(o2_fds, struct pollfd, i); FD_SET(d->fd, &o2_read_set); } o2_no_timeout.tv_sec = 0; o2_no_timeout.tv_usec = 0; if ((total = select(0, &o2_read_set, NULL, NULL, &o2_no_timeout)) == SOCKET_ERROR) { /* TODO: error handling here */ return O2_FAIL; /* TODO: return a specific error code for this */ } if (total == 0) { /* no messages waiting */ return O2_SUCCESS; } for (int i = 0; i < o2_fds.length; i++) { struct pollfd *d = DA_GET(o2_fds, struct pollfd, i); if (FD_ISSET(d->fd, &o2_read_set)) { process_info_ptr info = GET_PROCESS(i); if (((*(info->handler))(d->fd, info)) == O2_TCP_HUP) { O2_DBo(printf("%s removing remote process after O2_TCP_HUP to socket %ld", o2_debug_prefix, (long)d->fd)); o2_remove_remote_process(info); } } } // clean up any dead sockets before user has a chance to do anything // (actually, user handlers could have done a lot, so maybe this is // not strictly necessary.) if (o2_socket_delete_flag) o2_free_deleted_sockets(); return O2_SUCCESS; } #else // Use poll function to receive messages. int o2_recv() { int i; // if there are any bad socket descriptions, remove them now if (o2_socket_delete_flag) o2_free_deleted_sockets(); poll((struct pollfd *) o2_fds.array, o2_fds.length, 0); int len = o2_fds.length; // length can grow while we're looping! for (i = 0; i < len; i++) { struct pollfd *d = DA_GET(o2_fds, struct pollfd, i); // if (d->revents) printf("%d:%p:%x ", i, d, d->revents); if (d->revents & POLLERR) { } else if (d->revents & POLLHUP) { process_info_ptr info = GET_PROCESS(i); O2_DBo(printf("%s removing remote process after POLLHUP to socket %ld\n", o2_debug_prefix, (long) d->fd)); o2_remove_remote_process(info); } else if (d->revents) { process_info_ptr info = GET_PROCESS(i); assert(info->length_got < 5); if ((*(info->handler))(d->fd, info)) { O2_DBo(printf("%s removing remote process after handler reported error on socket %ld", o2_debug_prefix, (long) d->fd)); o2_remove_remote_process(info); } } if (!o2_application_name) { // handler called o2_finish() // o2_fds are all free and gone now return O2_FAIL; } } // clean up any dead sockets before user has a chance to do anything // (actually, user handlers could have done a lot, so maybe this is // not strictly necessary.) if (o2_socket_delete_flag) o2_free_deleted_sockets(); return O2_SUCCESS; } #endif void o2_socket_mark_to_free(process_info_ptr info) { info->delete_me = TRUE; o2_socket_delete_flag = TRUE; } // When a connection is accepted, we cannot know the name of the // connecting process, so the first message that arrives will have // to be to /_o2/in, type="ssii", application name, ip, udp, tcp // We then create a process (if not discovered yet) and associate // this socket with the process // int o2_tcp_initial_handler(SOCKET sock, process_info_ptr info) { int n = read_whole_message(sock, info); if (n == O2_FAIL) { // not ready to process message yet return O2_SUCCESS; } else if (n != O2_SUCCESS) { return n; } #if IS_LITTLE_ENDIAN o2_msg_swap_endian(&info->message->data, FALSE); #endif // message should be addressed to !_o2/in char *ptr = info->message->data.address; if (strcmp(ptr, "!_o2/in") != 0) { // error: close the socket return O2_FAIL; } // types will be after "!_o2/in<0>," ptr += 9; // skip over the ',' too o2_discovery_init_handler(&info->message->data, ptr, NULL, 0, info); info->handler = &tcp_recv_handler; // since we called o2_discovery_init_handler directly, // we need to free the message o2_message_free(info->message); tcp_message_cleanup(info); return O2_SUCCESS; } // On OS X, need to disable SIGPIPE when socket is created void o2_disable_sigpipe(SOCKET sock) { #ifdef __APPLE__ int set = 1; if (setsockopt(sock, SOL_SOCKET, SO_NOSIGPIPE, (void *) &set, sizeof(int)) < 0) { perror("in setsockopt in o2_disable_sigpipe"); } #endif } // This handler is for an OSC tcp server listen socket. When it is // "readable" this handler is called to accept the connection // request, creating a OSC_TCP_SOCKET // int o2_osc_tcp_accept_handler(SOCKET sock, process_info_ptr info) { // note that this handler does not call read_whole_message() // printf("%s: accepting a tcp connection\n", o2_debug_prefix); assert(info->tag == OSC_TCP_SERVER_SOCKET); SOCKET connection = accept(sock, NULL, NULL); if (connection == INVALID_SOCKET) { O2_DBg(printf("%s o2_osc_tcp_accept_handler failed to accept\n", o2_debug_prefix)); return O2_FAIL; } o2_disable_sigpipe(connection); process_info_ptr conn_info = o2_add_new_socket(connection, OSC_TCP_SOCKET, &osc_tcp_handler); assert(info->osc.service_name); conn_info->osc.service_name = info->osc.service_name; assert(info->port != 0); conn_info->port = info->port; O2_DBoO(printf("%s OSC server on port %d accepts client as socket %ld for service %s\n", o2_debug_prefix, info->port, (long) connection, info->osc.service_name)); return O2_SUCCESS; } // socket handler to forward incoming OSC to O2 service // static int osc_tcp_handler(SOCKET sock, process_info_ptr info) { int n = read_whole_message(sock, info); if (n == O2_FAIL) { // not ready to process message yet return O2_SUCCESS; } else if (n != O2_SUCCESS) { return n; } /* got the message, deliver it */ // endian corrections are done in handler RETURN_IF_ERROR(o2_deliver_osc(info)); // info->message is now freed tcp_message_cleanup(info); return O2_SUCCESS; } // returns O2_SUCCESS if whole message is read. // O2_FAIL if whole message is not read yet. // O2_TCP_HUP if socket is closed // static int read_whole_message(SOCKET sock, process_info_ptr info) { assert(info->length_got < 5); // printf("-- %s: read_whole message length_got %d length %d message_got %d\n", // o2_debug_prefix, info->length_got, info->length, info->message_got); /* first read length if it has not been read yet */ if (info->length_got < 4) { // coerce to int to avoid compiler warning; requested length is // int, so int is ok for n int n = (int) recvfrom(sock, PTR(&(info->length)) + info->length_got, 4 - info->length_got, 0, NULL, NULL); if (n < 0) { /* error: close the socket */ #ifdef WIN32 if ((errno != EAGAIN && errno != EINTR) || (GetLastError() != WSAEWOULDBLOCK && GetLastError() != WSAEINTR)) { if (errno == ECONNRESET || GetLastError() == WSAECONNRESET) { return O2_TCP_HUP; } #else if (errno != EAGAIN && errno != EINTR) { #endif perror("recvfrom in read_whole_message getting length"); tcp_message_cleanup(info); return O2_TCP_HUP; } } info->length_got += n; assert(info->length_got < 5); if (info->length_got < 4) { return O2_FAIL; } // done receiving length bytes info->length = htonl(info->length); info->message = o2_alloc_size_message(info->length); info->message_got = 0; // just to make sure } /* read the full message */ if (info->message_got < info->length) { // coerce to int to avoid compiler warning; message length is int, so n can be int int n = (int) recvfrom(sock, PTR(&(info->message->data)) + info->message_got, info->length - info->message_got, 0, NULL, NULL); if (n <= 0) { #ifdef WIN32 if ((errno != EAGAIN && errno != EINTR) || (GetLastError() != WSAEWOULDBLOCK && GetLastError() != WSAEINTR)) { if (errno == ECONNRESET || GetLastError() == WSAECONNRESET) { return O2_TCP_HUP; } #else if (errno != EAGAIN && errno != EINTR) { #endif perror("recvfrom in read_whole_message getting data"); o2_message_free(info->message); tcp_message_cleanup(info); return O2_TCP_HUP; } } info->message_got += n; if (info->message_got < info->length) { return O2_FAIL; } } info->message->length = info->length; return O2_SUCCESS; // we have a full message now } // This handler is for the tcp server listen socket. When it is // "readable" this handler is called to accept the connection // request. // static int tcp_accept_handler(SOCKET sock, process_info_ptr info) { // note that this handler does not call read_whole_message() SOCKET connection = accept(sock, NULL, NULL); if (connection == INVALID_SOCKET) { O2_DBg(printf("%s tcp_accept_handler failed to accept\n", o2_debug_prefix)); return O2_FAIL; } int set = 1; #ifdef __APPLE__ setsockopt(connection, SOL_SOCKET, SO_NOSIGPIPE, (void *) &set, sizeof(int)); #endif process_info_ptr conn_info = o2_add_new_socket(connection, TCP_SOCKET, &o2_tcp_initial_handler); conn_info->proc.status = PROCESS_CONNECTED; O2_DBdo(printf("%s O2 server socket %ld accepts client as socket %ld\n", o2_debug_prefix, (long) sock, (long) connection)); return O2_SUCCESS; } static void tcp_message_cleanup(process_info_ptr info) { /* clean up for next message */ info->message = NULL; info->message_got = 0; info->length = 0; info->length_got = 0; } static int tcp_recv_handler(SOCKET sock, process_info_ptr info) { int n = read_whole_message(sock, info); if (n == O2_FAIL) { // not ready to process message yet return O2_SUCCESS; } else if (n != O2_SUCCESS) { return n; } // endian fixup is included in this handler: deliver_or_schedule(info); // info->message is now freed tcp_message_cleanup(info); return O2_SUCCESS; } static int udp_recv_handler(SOCKET sock, process_info_ptr info) { int len; if (ioctlsocket(sock, FIONREAD, &len) == -1) { perror("udp_recv_handler"); return O2_FAIL; } info->message = o2_alloc_size_message(len); if (!info->message) return O2_FAIL; int n; // coerce to int to avoid compiler warning; len is int, so int is good for n if ((n = (int) recvfrom(sock, (char *) &(info->message->data), len, 0, NULL, NULL)) <= 0) { // I think udp errors should be ignored. UDP is not reliable // anyway. For now, though, let's at least print errors. perror("recvfrom in udp_recv_handler"); o2_message_free(info->message); info->message = NULL; return O2_FAIL; } info->message->length = n; // endian corrections are done in handler if (info->tag == UDP_SOCKET || info->tag == DISCOVER_SOCKET) { deliver_or_schedule(info); } else if (info->tag == OSC_SOCKET) { return o2_deliver_osc(info); } else { assert(FALSE); // unexpected tag in fd_info return O2_FAIL; } info->message = NULL; // message is deleted by now return O2_SUCCESS; } o2-1.0/src/o2_dynamic.h0000644000175000017500000000370513072261166015121 0ustar zmoelnigzmoelnig/* o2_dynamic.h -- generic dynamic arrays */ #ifndef o2_dynamic_h #define o2_dynamic_h typedef struct dyn_array { int32_t allocated; int32_t length; char *array; } dyn_array, *dyn_array_ptr; /* initialize a dynamic array. typ is the type of each element, siz is the initial space allocated (number of elements). The initial length is 0 */ #define DA_INIT(a, typ, siz) { \ (a).allocated = (siz); \ (a).length = 0; \ (a).array = ((siz) > 0 ? O2_MALLOC((siz) * sizeof(typ)) : NULL); } /* get a pointer to the index'th item of array. The type of each element is typ. */ #define DA_GET(a, typ, index) \ ((typ *) ((a).array + sizeof(typ) * (index))) /* get a pointer to the last element. Assumes length > 0. */ #define DA_LAST(a, typ) (DA_GET(a, typ, (a).length - 1)) /* set an array element at index to data. typ is the type of each element in array. */ #define DA_SET(a, typ, index, data) \ (*((typ *) ((a).array + sizeof(typ) * (index))) = (data)) /* return if index of a dynamic array is in bounds */ #define DA_CHECK(a, index) \ ((index) >= 0 && (index) < (a).length) /* make sure there is room for at least one more element, and increase the length by one. Caller should immediately assign a value to the last element */ #define DA_EXPAND(a, typ) { \ if ((a).length + 1 > (a).allocated) { \ o2_da_expand(&(a), sizeof(typ)); } \ (a).length++; } /* append data (of type typ) to the dynamic array */ #define DA_APPEND(a, typ, data) { \ DA_EXPAND(a, typ); \ DA_SET(a, typ, (a).length - 1, data); } /* remove an element at index i, replacing with last */ #define DA_REMOVE(a, typ, i) { \ *DA_GET(a, typ, i) = *DA_LAST(a, typ); \ (a).length--; } #define DA_FINISH(a) { (a).length = (a).allocated = 0; \ O2_FREE((a).array); (a).array = NULL; } void o2_da_expand(dyn_array_ptr array, int siz); #endif /* o2_dynamic_h */ o2-1.0/src/o2.h0000644000175000017500000020555613072261166013425 0ustar zmoelnigzmoelnig// o2.h -- public header file for o2 system // Roger B. Dannenberg and Zhang Chi // see license.txt for license // June 2016 #ifndef O2_H #define O2_H #ifdef __cplusplus extern "C" { #endif /** \file o2.h \mainpage \section Introduction This documentation is divided into modules. Each module describes a different area of functionality: Basics, Return Codes, Low-Level Message Send, and Low-Level Message Parsing. \section Overview O2 is a communication protocol for interactive music and media applications. O2 is inspired by Open Sound Control (OSC) and uses similar means to form addresses, specify types, and encode messages. However, in addition to providing message delivery, O2 offers a discovery mechanism where processes automatically discover and connect to other processes. Each process can offer zero or more named "services," which are top-level nodes in a global, tree-structured address space for a distributed O2 application. In O2, services replace the notion of network addresses (e.g. 128.2.100.57) in OSC. O2 is based on IP (Internet Protocol), but there are some mechanisms that allow an O2 process to serve as a bridge to other networks such as Bluetooth. O2 addresses begin with the service name. Thus, a complete O2 address would be written simply as "/synth/filter/cutoff," where "synth" is the service name. Furthermore, O2 implements a clock synchronization protocol. A single process is designated as the "master," and other processes automatically synchronize their local clocks to the master. All O2 messages are timestamped. Messages are delivered immediately, but their designated operations are invoked according to the timestamp. A timestamp of zero (0.0) means deliver the message immediately. Messages with non-zero timestamps are only deliverable after both the sender and receiver have synchronized clocks. A service is created using the functions: o2_service_new("service_name") and o2_method_new("address," "types," handler, user_data, coerce, parse), where o2_method_new is called to install a handler for each node, and each "address" includes the service name as the first node. Some major components and concepts of O2 are the following: - **Application** - a collection of collaborating processes are called an "application." Applications are named by a simple ASCII string. In O2, all components belong to an application, and O2 supports communication only *within* an application. This allows multiple independent applications to co-exist and share the same local area network. - **Host** - (conventional definition) a host is a computer (virtual or real) that may host multiple processes. Essentially, a Host is equivalent to an IP address. - **Process** - (conventional definition) an address space and one or more threads. A process can offer one or more O2 services and can serve as a client of O2 services in the same or other processes. Each process using O2 has one directory of services shared by all O2 activity in that process (thus, this O2 implementation can be thought of as a "singleton" object). The single O2 instance in a process belongs to one and only one application. O2 does not support communication between applications. - **Service** - an O2 service is a named server that receives and acts upon O2 messages. A service is addressed by name. Multiple services can exist within one process. A service does not imply a (new) thread because services are "activated" by calling o2_poll(). It is up to the programmer ("user") to call o2_poll() frequently, and if this is done using multiple threads, it is up to the programmer to deal with all concurrency issues. O2 is not reentrant, so o2_poll() should be called by only one thread. Since there is at most one O2 instance per process, a single call to o2_poll() handles all services in the process. - **Message** - an O2 message, similar to an OSC message, contains an address pattern representing a function, a type string and a set of values representing parameters. - **Address Pattern** - O2 messages use URL-like addresses, as does OSC, but the top-level node in the hierarchical address space is the service name. Thus, to send a message to the "note" node of the "synth" service, the address pattern might be "/synth/note." The OSC pattern specification language is used unless the first character is "!", e.g. "!synth/note" denotes the same address as "/synth/note" except that O2 can assume that there are no pattern characters such as "*" or "[". - **Scheduler** - O2 implements two schedulers for timed message delivery. Schedulers are served by the same o2_poll() call that runs other O2 activity. The #o2_gtsched schedules according to the application's master clock time, but since this depends on clock synchronization, nothing can be scheduled until clock synchronization is achieved (typically within a few seconds of starting the process that provides the master clock). For local-only services, it is possible to use the #o2_ltsched scheduler (not with o2_send(), but by explicitly constructing messages and scheduling them with o2_schedule(). In any case, scheduling is useful *within* a service for any kind of timed activity. */ // get uint32_t, etc.: #include #include #ifndef TRUE #define TRUE 1 #define FALSE 0 #endif /** \defgroup debugging Debugging Support * @{ */ /// \brief Enable debugging output. /// /// Unless O2_NO_DEBUG is defined at compile time, O2 is /// compiled with debugging code that prints information to /// stdout, including network addresses, services discovered, /// and clock synchronization status. Enable the debugging /// information by calling o2_debug_flags() with a string /// containing any of the following characters: /// - c - for basic connection data /// - r - for tracing non-system incoming messages /// - s - for tracing non-system outgoing messages /// - R - for tracing system incoming messages /// - S - for tracing system outgoing messages /// - k - for tracing clock synchronization protocol /// - d - for tracing discovery messages /// - t - for tracing user messages dispatched from schedulers /// - T - for tracing system messages dispatched from schedulers /// - m - trace O2_MALLOC and O2_FREE calls /// - o - trace socket creating and closing /// - O - open sound control messages /// - g - print general status info /// - a - all debug flags except m (malloc/free) #ifndef O2_NO_DEBUG void o2_debug_flags(const char *flags); #endif /** @} */ /** \defgroup returncodes Return Codes * @{ */ // Status return values used in o2 functions #define O2_SUCCESS 0 ///< function was successful /// \brief an error return value: a non-specific error occurred. /// /// In general, any return value < 0 indicates an error. Testing for /// only O2_FAIL will not detect more specific error return values /// such as O2_SERVICE_CONFLICT, O2_NO_MEMORY, etc. #define O2_FAIL (-1) // TODO: this value is never used/returned /// an error return value: path to handler specifies a remote service #define O2_SERVICE_CONFLICT (-2) // TODO: this value is never used/returned /// an error return value: path to handler specifies non-existant service #define O2_NO_SERVICE (-3) /// an error return value: process is out of free memory #define O2_NO_MEMORY (-4) /// an error return value for o2_initialize(): O2 is already running. #define O2_ALREADY_RUNNING (-5) /// an error return value for o2_initialize(): invalid name parameter. #define O2_BAD_NAME (-6) /// an error return value for o2_add_vector(): invalid element type #define O2_BAD_TYPE (-7) /// \brief an error return value: mismatched types and arguments /// returned by o2_message_build(), o2_send(), o2_send_cmd() #define O2_BAD_ARGS (-8) /// an error return value for o2_initialize(): the socket is closed. #define O2_TCP_HUP (-9) /// \brief an error return value indicating inet_pton() failed to convert a /// string to an IP address #define O2_HOSTNAME_TO_NETADDR_FAIL (-10) /// an error return value: attempt to make a TCP connection failed #define O2_TCP_CONNECT_FAIL (-11) /// \brief an error return value: message was not scheduled or delivered /// because the current time is not available #define O2_NO_CLOCK (-12) /// an error return value: no handler for an address #define O2_NO_HANDLER (-13) /// an error return value: an O2 message is invalid #define O2_INVALID_MSG (-14) /// an error return value: could not write to socket or send datagram #define O2_SEND_FAIL (-15) /// an error return value: a service name was NULL or contained a slash (/) #define O2_BAD_SERVICE_NAME (-16) /// an error return value: attempt to create a local service when one exists already #define O2_SERVICE_EXISTS (-17) /// an error return value: O2 has not been initialized #define O2_NOT_INITIALIZED (-18) // Status return codes for o2_status function: /// \brief return value for o2_status(): local service, no clock sync yet /// /// This is a local service /// but clock sync has not yet been established so messages with non-zero /// timestamps will be dropped. #define O2_LOCAL_NOTIME 0 /// \brief return value for o2_status(): remote service but no clock sync yet /// /// This is a remote service but clock sync has not yet been established so /// messages with non-zero timestamps will be dropped. The remote service /// may represent a bridge to a non-IP destination or to an OSC /// server. #define O2_REMOTE_NOTIME 1 /// \brief return value for o2_status(): service is connected but no /// clock sync yet. /// /// The service is attached to this process by a non-IP link. Clock sync /// has not yet been established between the master clock and this /// process, so non-zero timestamped messages to this service will be /// dropped. Note that within other processes, /// the status for this service will be #O2_REMOTE_NOTIME rather than /// #O2_BRIDGE_NOTIME. Note also that O2 does not require the /// remote bridged process to have a synchronized clock, so "NOTIME" only /// means that *this* process is not synchronized and therefore cannot /// (and will not) schedule a timestamped message for timed delivery. #define O2_BRIDGE_NOTIME 2 /// \brief return value for o2_status(): service is connected but no /// clock sync yet. /// /// The service is local and forwards messages to an OSC server. The status /// of the OSC server is not reported by O2 (and in the typical UDP case, /// there is no way to determine if the OSC server is operational, so /// "connected" may just mean that the service has been defined). /// Clock sync has not yet been established between the master clock /// and this process, so messages with non-zero timestamps to this service /// will be dropped. Note that within other processes, /// the status for this service will be #O2_REMOTE_NOTIME rather than /// #O2_TO_OSC_NOTIME. Note also that O2 does not require the /// OSC server to have a synchronized clock, so "NOTIME" only /// means that *this* process is not synchronized to O2 and therefore cannot /// (and will not) schedule a timestamped message for timed delivery. #define O2_TO_OSC_NOTIME 3 /// \brief return value for o2_status(): local service with clock sync. /// /// Note that even though the /// service is local to the process and therefore shares a local /// clock, clocks are not considered to be synchronized until the /// local clock is synchronized to the master clock. If this process /// provides the master clock, it is considered to be synchronized /// immediately. #define O2_LOCAL 4 /// \brief return value for o2_status(): remote service with clock sync. /// /// Messages with non-zero timestamps can be sent because /// clock sync has been established. #define O2_REMOTE 5 /// \brief return value for o2_status(): connected with clock sync. /// /// The service is attached by a non-IP link, and this process is synchronized. /// If the bridged process is also synchronized, timed messages are /// sent immediately and dispatched according to the synchronized /// clock; if the bridged process is *not* synchronized, timed /// messages are scheduled locally and sent according to the /// timestamp, resulting in some added network latency. #define O2_BRIDGE 6 /// \brief return value for o2_status(): connected with clock sync. /// /// The service forwards messages directly from the current process /// to an OSC server, and the process is synchronized. The status of /// the OSC server is not reported by O2 (and in the typical UDP case, /// there is no way to determine if the OSC server is operational). /// Non-bundle O2 messages will be scheduled locally and sent according /// to the timestamp to avoid creating a timestamped bundle, but this /// will result in some added network latency. O2 bundles will be /// converted to OSC bundles with timestamps based on Unix gettimeofday() /// or Windows GetSystemTimeAsFileTime() which are then converted to /// OSC-compatible NTP timestamps (this is all based on liblo; timestamped /// message to liblo implementations of OSC will be correctly interpreted). /// The resulting OSC bundles are sent immediately. #define O2_TO_OSC 7 /** @} */ // Macros for o2 protocol /* an internal value, ignored in transmission but check against O2_MARKER in the * argument list. Used to do primitive bounds checking */ #define O2_MARKER_A (void *) 0xdeadbeefdeadbeefL #define O2_MARKER_B (void *) 0xf00baa23f00baa23L //#endif extern void *((*o2_malloc)(size_t size)); extern void ((*o2_free)(void *)); void *o2_calloc(size_t n, size_t s); /** \defgroup basics Basics * @{ */ #ifndef O2_NO_DEBUG void *o2_dbg_malloc(size_t size, char *file, int line); void o2_dbg_free(void *obj, char *file, int line); #define O2_MALLOC(x) o2_dbg_malloc(x, __FILE__, __LINE__) #define O2_FREE(x) o2_dbg_free(x, __FILE__, __LINE__) #endif /** \brief allocate memory * * O2 allows you to provide custom heap implementations to avoid * priority inversion or other real-time problems. Normally, you * should not need to explicitly allocate memory since O2 functions * are provided to allocate, construct, and deallocate messages, but * if you need to allocate memory, especially in an O2 message * handler callback, i.e. within the sphere of O2 execution, you * should use #O2_MALLOC, #O2_FREE, and #O2_CALLOC. */ #ifndef O2_MALLOC #ifdef NO_O2_DEBUG #define O2_MALLOC(x) (*o2_malloc)(x) #else #define O2_MALLOC(x) o2_dbg_malloc(x, __FILE__, __LINE__) #endif #endif /** \brief free memory allocated by #O2_MALLOC */ #ifndef O2_FREE #ifdef NO_O2_DEBUG #define O2_FREE(x) (*o2_free)(x) #else #define O2_FREE(x) (*o2_dbg_free)(x, __FILE__, __LINE__) #endif #endif /** \brief allocate and zero memory (see #O2_MALLOC) */ #ifndef O2_CALLOC #ifdef NO_O2_DEBUG void *o2_calloc(size_t n, size_t s); #define O2_CALLOC(n, s) o2_calloc((n), (s)) #else void *o2_dbg_calloc(size_t n, size_t s, char *file, int line); #define O2_CALLOC(n, s) o2_dbg_calloc((n), (s), __FILE__, __LINE__) #endif #endif /** \brief O2 timestamps are doubles representing seconds since the * approximate start time of the application. */ typedef double o2_time; /** \brief data part of an O2 message * * This data type is used to pass o2 message data to message handlers. * It appears many other times in the code. You should NEVER allocate * or free an o2_msg_data struct. Instead, create a message using * o2_send_start(), o2_add_*(), and o2_message_finish() to get an * o2_message_ptr. Within the o2_message, the data field is an * o2_msg_data structure. We would use o2_message everywhere instead * of o2_msg_data, but bundles can contain multiple o2_msg_data * structures without the extra baggage contained in an o2_message. * * Note: it is assumed that an o2_msg_data struct is always preceded * by a 32-bit length. Ideally, length should therefore be in this * struct, but then the compiler might add padding to put the timestamp * on an 8-byte alignment. This could be solved with a pack pragma, but * that is not standard C. To be safe and portable, I decided to just * leave length out of the struct. The macro MSG_DATA_LENGTH can be used * to access the length field. */ typedef struct o2_msg_data { o2_time timestamp; ///< the message delivery time (0 for immediate) /** \brief the message address string * * Although this field is declared as 4 bytes, actual messages * have variable length, and the address is followed by a * string of type codes and the actual parameters. The length * of the entire message including the timestamp is given by * the `length` field. */ char address[4]; } o2_msg_data, *o2_msg_data_ptr; // get the length from a pointer to an o2_msg_data. This macro dereferences // the o2_msg_data pointer to impose some mild typechecking. Not just any // pointer will work. #define MSG_DATA_LENGTH(m) (((int32_t *) &((m)->timestamp))[-1]) /** \brief get the type string from o2_msg_data_ptr * * Type strings begin with the comma (",") character, which is skipped */ #define WORD_ALIGN_PTR(p) ((char *) (((size_t) (p)) & ~3)) #define O2_MSG_TYPES(msg) \ WORD_ALIGN_PTR((msg)->address + strlen((msg)->address) + 4) + 1; /** \brief an O2 message container * * Note: This struct represents an O2 message that is stored on the heap. * The length field must preceded data with no padding (see o2_msg_data * declaration and the note that precedes it). To make sure there is no * padding between length and data, we force the next pointer to occupy * 8 bytes even if this is a 32-bit machine by making it part of a union * with an 8-byte int64_t field named "pad_if_needed." * * Note that o2_messages are on the heap and can be allocated, scheduled, * sent, and freed. In contrast, o2_msg_data structures are contained * within o2_messages and are passed to method handlers, but cannot be * allocated, scheduled, sent, or freed. They are always the data field * of a containing o2_message. */ typedef struct o2_message { union { struct o2_message *next; ///< links used for free list and scheduler int64_t pad_if_needed; ///< make sure allocated is 8-byte aligned }; union { int tcp_flag; ///< send message by tcp? int64_t pad_if_needed2; ///< make sure allocated is 8-byte aligned }; int32_t allocated; ///< how many bytes allocated in data part int32_t length; ///< the length of the message in data part o2_msg_data data; } o2_message, *o2_message_ptr; /** * \brief The structure for binary large object. * * A blob can be passed in an O2 message using the 'b' type. Created * by calls to o2_blob_new(). */ typedef struct o2_blob { uint32_t size; ///< size of data char data[4]; ///< the data, actually of variable length } o2_blob, *o2_blob_ptr; /** * \brief An enumeration of the O2 message types. */ typedef enum { // basic O2 types O2_INT32 = 'i', ///< 32 bit signed integer. O2_FLOAT = 'f', ///< 32 bit IEEE-754 float. O2_STRING = 's', ///< NULL terminated string (Standard C). O2_BLOB = 'b', ///< Binary Large OBject (BLOB) type. O2_ARRAY_START = '[', ///< Start array or tuple O2_ARRAY_END = ']', ///< End array or tuple // extended O2 types O2_INT64 = 'h', ///< 64 bit signed integer. O2_TIME = 't', ///< OSC time type. O2_DOUBLE = 'd', ///< 64 bit IEEE-754 double. O2_SYMBOL = 'S', ///< Used in systems distinguish strings and symbols. O2_CHAR = 'c', ///< 8bit char variable (Standard C). O2_MIDI = 'm', ///< 4 byte MIDI packet. O2_TRUE = 'T', ///< Symbol representing the value True. O2_FALSE = 'F', ///< Symbol representing the value False. O2_NIL = 'N', ///< Symbol representing the value Nil. O2_INFINITUM = 'I', ///< Symbol representing the value Infinitum. // O2 types O2_BOOL = 'B', ///< Boolean value returned as either 0 or 1 O2_VECTOR = 'v', ///< Prefix to indicate a vector } o2_type, *o2_type_ptr; /** * \brief union of all O2 parameter types * * An o2_arg_ptr is a pointer to an O2 message argument. If argument * parsing is requested (by setting the parse parameter in o2_method_new), * then the handler receives an array of o2_arg_ptrs. If argument parsing * is not requested, you have the option of parsing the message one * parameter at a time by calling o2_get_next(), which returns an * o2_arg_ptr. * * The o2_arg_ptr can then be dereferenced to obtain a value of the * expected type. For example, you could write * \code{.c} * double d = o2_get_next()->d; * \endcode * to extract a parameter of type double. (This assumes that the message * is properly formed and the type string indicates that this parameter is * a double, or that type coercion was enabled by the coerce flag in * o2_method_new().) */ typedef union { int32_t i32; ///< 32 bit signed integer. int32_t i; ///< an alias for i32 int64_t i64; ///< 64 bit signed integer. int64_t h; ///< an alias for i64 float f; ///< 32 bit IEEE-754 float. float f32; ///< an alias for f double d; ///< 64 bit IEEE-754 double. double f64; ///< an alias for d char s[4]; ///< Standard C, NULL terminated string. /** \brief Standard C, NULL terminated, string. Used in systems which distinguish strings and symbols. */ char S[4]; int c; ///< Standard C, 8 bit, char, stored as int. uint32_t m; ///< A 4 byte MIDI packet. MSB to LSB are port id, ///< status, data1, data2 o2_time t; ///< TimeTag value. o2_blob b; ///< a blob (unstructured bytes) int32_t B; ///< a boolean value, either 0 or 1 struct { int32_t len; ///< length of vector in bytes ///< IMPORTANT: divide by 4 or 8 to get length in elements int32_t typ; ///< type of vector elements union { int32_t *vi; ///< vector of 32-bit signed integers int64_t *vh; ///< vector of 64-bit signed integers double *vd; ///< vector of IEEE-754 doubles float *vf; ///< vector of IEEE-754 floats // note that a blob is basically a vector of bytes; // there is no type conversion from blob to vector though, // and no vector of shorts or bytes because if you converted // to a vector of int64_t, it would take 8x the message // space, forcing us to allocate very big buffers to // unpack messages. }; } v; } o2_arg, *o2_arg_ptr; extern o2_arg_ptr o2_got_start_array; extern o2_arg_ptr o2_got_end_array; /** \brief name of the application * * A collection of cooperating O2 processes forms an * *application*. Applications must have unique names. This allows * more than one application to exist within a single network without * conflict. For example, there could be two applications, "joe" and * "sue", each with services named "synth." Since the application * names are different, joe's messages to the synth service go to * joe's synth and not to sue's synth. * * Do not set, modify or free this variable! Consider it to be * read-only. It is managed by O2 using o2_initialize() and o2_finish(). */ extern const char *o2_application_name; /** \brief set this flag to stop o2_run() * * Some O2 applications will initialize and call o2_run(), which is a * simple loop that calls o2_poll(). To exit the loop, set * #o2_stop_flag to #TRUE */ extern int o2_stop_flag; /** * \brief callback function to receive an O2 message * * @param msg The full message in host byte order. * @param types If you set a type string in your method creation call, * then this type string is provided here. If you did not * specify a string, types will be the type string from the * message (without the initial ','). If parse_args and * coerce_flag were set in the method creation call, * types will match the types in argv, but not necessarily * the type string or types in msg. * @param argv An array of #o2_arg types containing the values, e.g. if the * first argument of the incoming message is of type 'f' then * the value will be found in argv[0]->f. (If parse_args was * not set in the method creation call, argv will be NULL.) * For vectors, specified in types by the sequence "vi", "vh", * "vf", or "vd", there will be one pointer in argv pointing to * a vector description (the v field in o2_arg). For arrays, * there are *no* pointers corresponding to '[' or ']' in the * types string; but there is one pointer in argv for each array * element. * @param argc The number of arguments received. (This is valid even if * parse_args was not set in the method creation call.) This is * the length of argv. Vectors count as one, array elements count * as one each, and arrays themselves are not represented. For * example, an empty array ("[]") in the type string adds * nothing to the argc count or argv vector. * @param user_data This contains the user_data value passed in the call * to the method creation call. */ typedef void (*o2_method_handler)(const o2_msg_data_ptr msg, const char *types, o2_arg_ptr *argv, int argc, void *user_data); /** * \brief Start O2. * * If O2 has not been initialized, it is created and intialized. * O2 will begin to establish connections to other instances * with a matching application name. * * @param application_name the name of the application. O2 will attempt to * discover other processes with a matching application name, * ignoring all processes with non-matching names. * * @return #O2_SUCCESS if success, #O2_FAIL if an error occurs, * #O2_RUNNING if already running, #O2_BAD_NAME if `application_name` * is NULL. */ int o2_initialize(const char *application_name); /** * \brief Tell O2 how to allocate/free memory. * * In many C library implementations, the standard implementation of * free() must lock a data structure. This can lead to priority * inversion if O2 runs at an elevated priority. Furthermore, the * standard `malloc()` and `free()` do not run in constant (real) time. To * avoid these problems, you can provide an alternate heap * implementation for O2 by calling this function before calling * o2_initialize(). For example, the provided functions can implement * a private heap for the thread running O2. * * @param malloc a function pointer that behaves like standard * `malloc()` * @param free a function pointer that behaves like standard `free()` * * @return O2_SUCCESS if succeed, O2_FAIL if not. */ int o2_memory(void *((*malloc)(size_t size)), void ((*free)(void *))); /** * \brief Set discovery period * * O2 discovery messages are broadcast periodically in case a new process * has joined the application. The default period is 4 seconds. If there * are N processes, each host will receive N/4 discovery messages per * second. Since there are 5 discovery ports, each process will handle * N/20 discovery messages per second, and a discovery message from any * given process will be received every 20 seconds. (Note, however, that * new processes send more frequently, sending 2 discovery messages to * each of the 5 discovery port numbers within 2 seconds, so if messages * are not dropped frequently, discovery of new processes will happen much * faster than the worst-case 20 second polling period or even the * 10 second expected wait.) * * You can change the polling period from 4s by calling this function. The * new polling period takes effect when the next discovery message is sent * at the end of the current polling period. * * @param period the requested polling period; a minimum of 0.1s is enforced; * 4s is the default (recommended). * * @return the previous polling period */ o2_time o2_set_discovery_period(o2_time period); /** * \brief Add a service to the current application. * * Once created, services are "advertised" to other processes with * matching application names, and messages are delivered * accordingly. E.g. to handle messages addressed to "/synth/volume" * you call * \code{.c} * o2_service_new("synth"); * o2_method_new("/synth/volume", "f", synth_volume_handler, NULL, NULL, TRUE); * \endcode * and define `synth_volume_handler` (see the type declaration for * #o2_method_handler and o2_method_new()) * Normally, services should be *unique* across the application. If * #service_name is already locally defined in this process (by a previous * call to #o2_service_new or #o2_osc_delegate), this call will fail, * returning #O2_SERVICE_EXISTS. If matching service names are defined * in two different processes, the process with the highest IP and port * number (lexicographically) will provide the service. However, due to * the distributed and asynchronous nature of O2, there may be some * intervening time (typically a fraction of a second) during which a * service is handled by two different processes. Furthermore, the switch * to a new service provider could redirect a stream of messages, causing * unexpected behavior in the application. * * @param service_name the name of the service * * @return #O2_SUCCESS if success, #O2_FAIL if not. */ int o2_service_new(const char *service_name); /** * \brief Remove a local service * * The #service_name corresponds to the parameter previously passed to * #o2_service_new or #o2_osc_delegate. Note that if an OSC port * forwards to this service (see #o2_osc_port_new), the port remains * open, but the OSC messages will be dropped. See #o2_osc_port_free(). * * @param service_name the name of the service * * @return #O2_SUCCSS if success, #O2_FAIL if not. */ int o2_service_free(char *service_name); /** * \brief Add a handler for an address. * * @param path the address including the service name. If the address * is only the service name with no trailing slash, * the handler will match any message to the service. * Addresses should not conflict: An address should * not match another address, and for every pair of * addresses X and Y, X/ should not be a prefix of Y. * @param typespec the types of parameters, use "" for no parameters and * NULL for no type checking * @param h the handler * @param user_data pointer saved and passed to handler * @param coerce is true if you want to allow automatic coercion of types. * Coercion is only enabled if both coerce and parse are * true. * @param parse is true if you want O2 to construct an argv argument * vector to pass to the handle * * @return O2_SUCCESS if succeed, O2_FAIL if not. */ int o2_method_new(const char *path, const char *typespec, o2_method_handler h, void *user_data, int coerce, int parse); /** * \brief Process current O2 messages. * * Since O2 does not create a thread and O2 requires active processing * to establish and maintain connections, the O2 programmer (user) * should call o2_poll() periodically, even if not offering a service. * o2_poll() runs a discovery protocol to find and connect to other * processes, runs a clock synchronization protocol to establish valid * time stamps, and handles incoming messages to all services. O2_poll() * should be called at least 10 times per second. Messages can only be * delivered during a call to o2_poll() so more frequent calls will * generally lower the message latency as well as the accuracy of the * clock synchronization (at the cost of greater CPU utilization). * Human perception of timing jitter is on the order of 10ms, so * polling rates of 200 to 1000 are advised in situations where * rhythmic accuracy is expected. * * @return 0 (O2_SUCCESS) if succeed, -1 (O2_FAIL) if not. */ int o2_poll(); /** * \brief Run O2. * * Call o2_poll() at the rate (in Hz) indicated. * Returns if a handler sets #o2_stop_flag to non-zero. */ int o2_run(int rate); /** * \brief Check the status of the service. * * @param service the name of the service * @return * - #O2_FAIL if no service is found, * - #O2_LOCAL_NOTIME if the service is local but we have no clock sync yet, * - #O2_REMOTE_NOTIME if the service is remote but we have no clock sync yet, * - #O2_BRIDGE_NOTIME if service is attached by a non-IP link, but we have * no clock sync yet (if the non-IP connection is not handled * by this process, the service status will be #O2_REMOTE_NOTIME), * - #O2_TO_OSC_NOTIME if service forwards to an OSC server but we * have no clock sync yet (if the OSC connection is not handled * by this process, the service status will be #O2_REMOTE_NOTIME), * - #O2_LOCAL if service is local and we have clock sync, * - #O2_REMOTE if service is remote and we have clock sync, * - #O2_BRIDGE if service is handled locally by forwarding to an * attached non-IP link, and we have clock sync. (If the non-IP * connection is not local, the service status will be #O2_REMOTE). * - #O2_TO_OSC if service is handled locally by forwarding to an OSC * server and this process has clock sync. (If the OSC * connection is not handled locally, the service status will be * #O2_REMOTE). * * @return Note that codes are carefully * ordered to allow testing for categories: * - to test if delivery is possible with a zero (immediate) timestamp, * use `o2_status(service) > O2_FAIL`, `o2_status(service) >= 0`, or * `o2_status(service) >= O2_LOCAL_NOTIME`. * - to test if delivery is possible with a non-zero timestamp, use * `o2_status(service) >= O2_LOCAL`. Note that status can change over * time, e.g. the * status of a remote service will be #O2_FAIL until the service is * discovered. It will then change to #O2_REMOTE_NOTIME until both the * sender and receiver achieve clock synchronization and share their * synchronized status, and finally the status will become #O2_REMOTE. * * In the cases with no clock sync, it is safe to send an immediate message * with timestamp = 0, but non-zero timestamps are meaningless because * either the sending process has no way to obtain a valid timestamp * or the receiver has no way to schedule delivery according to a * timestamp. * * Messages to services are *dropped* if the service has not been * discovered. Timestamped messages (timestamp != 0) are *dropped* if * the sender and receiver are not * clock-synchronized. (`o2_status(service) >= O2_LOCAL`). * * A special case is with `BRIDGE` and `OSC` services. In these cases, * the O2 process offering the service can either schedule the * messages locally, sending them according to the timestamp (and * suffering some network latency), or if the destination process is * synchronized, messages can be forwarded immediately for more * precise scheduling at their final destination. O2 does not provide * any way for clients/users to determine which of these methods is in * effect, and in the case of messages being forwarded by an * intermediary O2 process, the originator of the message cannot * determine whether the service is offered by an O2 server on the * local network, by an OSC server, or through a bridge to another * network such as Bluetooth. The status at the originator will be * simply #O2_REMOTE or #O2_REMOTE_NOTIME. */ int o2_status(const char *service); /** * \brief A variable indicating that the clock is the master or is * synchronized to the master. */ extern int o2_clock_is_synchronized; /** * \brief Get network round-trip information. * * @return If clock is synchronized, return O2_SUCCESS and set * `*mean` to the mean round-trip time and `*min` to the minimum * round-trip time of the last 5 (where 5 is the value of * CLOCK_SYNC_HISTORY_LEN) clock sync requests. Otherwise, * O2_FAIL is returned and `*mean` and `*min` are unaltered. */ int o2_roundtrip(double *mean, double *min); /** \brief signature for callback that defines the master clock * * See o2_clock_set() for details. */ typedef o2_time (*o2_time_callback)(void *rock); /** * \brief Provide a time reference to O2. * * Exactly one process per O2 application should provide a master * clock. All other processes synchronize to the master. To become * the master, call o2_clock_set(). * * The time reported by the gettime function will be offset to * match the current local time so that local time continues to * increase smoothly. You cannot force O2 time to match an external * absolute time, but once o2_clock_set() is called, the difference * between the time reference and O2's local time (as reported by * o2_local_time()) will be fixed. * * @param gettime function to get the time in units of seconds. The * reference may be operating system time, audio system time, MIDI * system time, or any other time source. The times returned by this * function must be non-decreasing and must increase by one second * per second of real time to close approximation. The value may be * NULL, in which case a default time reference will be used. * * @parm rock an arbitrary value that is passed to the gettime * function. This may be need to provide context. Use NULL if no * context is required. * * @return #O2_SUCCESS if success, #O2_FAIL if not. */ int o2_clock_set(o2_time_callback gettime, void *rock); /** * \brief Construct and send O2 message with best effort protocol * * Normally, this constructs and sends an O2 message via UDP. If the * destination service is reached via some other network protocol * (e.g. Bluetooth), the message is delivered in the lowest latency * protocol available, with no guaranteed delivery. * * @param path an address pattern * @param time when to dispatch the message, 0 means right now. In any * case, the message is sent to the receiving service as soon as * possible. If the message arrives early, it will be held at the * service and dispatched as soon as possible after the indicated time. * @param typestring the type string for the message. Each character * indicates one data item. Type codes are as in OSC. * @param ... the data of the message. There is one parameter for each * character in the typestring. * * @return #O2_SUCCESS if success, #O2_FAIL if not. * */ /** \hideinitializer */ // turn off Doxygen report on o2_send_marker() #define o2_send(path, time, ...) \ o2_send_marker(path, time, FALSE, \ __VA_ARGS__, O2_MARKER_A, O2_MARKER_B) /** \cond INTERNAL */ \ int o2_send_marker(const char *path, double time, int tcp_flag, const char *typestring, ...); /** \endcond */ /** * \brief Construct and send an O2 message reliably. * * Normally, this constructs and sends an O2 message via TCP. If the * destination service is reached via some other network protocol * (e.g. Bluetooth), the message is delivered using the most reliable * protocol available. (Thus, this call is considered a "hint" rather * than an absolute requirement.) * * @param path an address pattern * @param time when to dispatch the message, 0 means right now. In any * case, the message is sent to the receiving service as soon as * possible. If the message arrives early, it will be held at the * service and dispatched as soon as possible after the indicated time. * @param typestring the type string for the message. Each character * indicates one data item. Type codes are defined by #o2_type. * @param ... the data of the message. There is one parameter for each * character in the typestring. * * @return #O2_SUCCESS if success, #O2_FAIL if not. */ /** \hideinitializer */ // turn off Doxygen report on o2_send_marker() #define o2_send_cmd(path, time, ...) \ o2_send_marker(path, time, TRUE, \ __VA_ARGS__, O2_MARKER_A, O2_MARKER_B) /** * \brief Send an O2 message. (See also macros #o2_send and #o2_send_cmd). * * @param msg points to an O2 message. * * @return #O2_SUCCESS if success, #O2_FAIL if not. * * After the call, the `msg` parameter is "owned" by O2, which will * free it. Therefore, do *not* free msg after calling o2_message_send(). */ int o2_message_send(o2_message_ptr msg); /** * \brief Get the estimated synchronized global O2 time. * * This function returns a valid value either after you call * o2_clock_set(), making the local clock the master clock for the O2 * application, or after O2 has finished discovering and * synchronizing with the master clock. Until then, -1 is returned. * * The clock accuracy depends upon network latency, how often * o2_poll() is called, and other factors, but * * @return the time in seconds, or -1 if global (master) time is unknown. */ o2_time o2_time_get(); /** * \brief Get the real time using the local O2 clock * * @return the local time in seconds */ o2_time o2_local_time(); /** * \brief Return text representation of an O2 error * * @param i error number returned from some O2 function * * @return return the error message as a string */ const char *o2_error_to_string(int i); /** * \brief release the memory and shut down O2. * * Close all sockets, free all memory, and restore critical * variables so that O2 behaves as if it was never initialized. * * @return #O2_SUCCESS if success, #O2_FAIL if not. */ int o2_finish(); // Interoperate with OSC /** * \brief Create a port to receive OSC messages. * * OSC messages are converted to O2 messages and directed to the service. * E.g. if the service is "maxmsp" and the message address is * `/foo/x`, then the message is directed to and handled by * `/maxmsp/foo/x`. If the #service_name does not exist at any time * after calling #o2_osc_port_new, incoming OSC messages will be dropped * until the service is available again. * * @param service_name The name of the service to which messages are delivered * @param port_num Port number. * @param tcp_flag Be a TCP server for remote clients. Otherwise, use UDP * * @return #O2_SUCCESS if success, #O2_FAIL if not. */ int o2_osc_port_new(const char *service_name, int port_num, int tcp_flag); /** * \brief Remove a port receiving OSC messages. * * This removes a port created by #o2_osc_port_new(). If you want to * remove the corresponding service, you must also call #o2_service_free() * with the service name. * * @param port_num The port number that receives OSC messages. * * @return #O2_SUCCESS if success, #O2_FAIL if not. * */ int o2_osc_port_free(int port_num); /** * \brief Create a service that forwards O2 messages to an OSC server. * * @param service_name The o2 service name without a '/' prefix. * @param ip The ip address of the osc server. * @param port_num The port number of the osc server. * @param tcp_flag Send OSC message via TCP protocol, in which case * port_num is the TCP server port, not a connection. * * @return #O2_SUCCESS if success, #O2_FAIL if not. * * If `tcp_flag` is set, a TCP connection will be established with * the OSC server. * When the created service receives any O2 messages, it will * send the message to the OSC server. If the incoming message has * a timestamp for some future time, the message will be held until * that time, then sent to the OSC server. (Ideally, O2 could convert * the message to an OSC timestamped bundle and send it immediately * to achieve precise forward-synchronous timing, but this requires * clock synchronization with the OSC server, which is normally * unimplemented.) * * If this is a tcp connection, close it by calling #o2_service_free(). */ int o2_osc_delegate(const char *service_name, const char *ip, int port_num, int tcp_flag); /** * \brief Set the OSC time offset. * * @param offset the offset between (global) O2 time and OSC time * * @return the previous offset * * O2 global time should start from 0.0 when the clock is started, whereas * OSC time starts at 1 Jan 1900. The offset is the OSC time corresponding * to O2 time 0.0. Equivalently, OSC_time = O2_time + offset. */ uint64_t o2_osc_time_offset(uint64_t offset); /** @} */ // end of Basics /** * \defgroup lowlevelsend Low-Level Message Send * * Rather than passing all parameters in one call or letting O2 * extract parameters from a message before calling its handler, * these functions allow building messages one parameter at a time * and extracting message parameters one at a time. * The functions operate on "hidden" messages, so these functions are * not reentrant. * * To build a message, begin by calling o2_send_start() to allocate a * message. Then call one of the `o2_add_()` functions to add each * parameter. Finally, call either o2_send_finish() to send the * message. You should not explicitly allocate or deallocate a * message using this procedure. * * To extract parameters from a message, begin by calling * o2_extract_start() to prepare to get parameters from the * message. Then call o2_get_next() to get each parameter. If the * result is non-null, a parameter of the requested type was obtained * and you can read the parameter from the result. Results other than * strings, MIDI, and blobs may only remain valid until the next call * to o2_get_next(), so you should use or copy the value before reading * the next one. Values that are not coerced (requiring a copy) are * left in the O2 message and have the same lifetime as the message. * You should not reuse this storage because the message may have * multiple destinations; thus, the message content should not be altered. * * A by-product of o2_extract_start() and o2_get_next() is an argument * vector (argv) that can be accessed from o2_argv. (This is the same * argument vector created automatically when a handler is added with * o2_method_new() when the parse parameter is true.) A possible * advantage of using a sequence of o2_get_next() calls rather than * simply setting the parse flag is that you can receive messages with * various types and numbers of parameters. Also, you can check vector * lengths and stop parsing if unacceptable lengths are encountered. * * o2_get_next() will perform type conversion if possible * when the requested type does not match the actual type. You can * determine the original type by reading the type string in the * message. The number of parameters is determined by the length of * the type string, with some exceptions. * * Vectors can be coerced into arrays, in which case * each element will be coerced as requested. Arrays can be coerced * into vectors if each element of the array can be coerced into * the expected vector element type. Vector lengths are provided by * the message; there is no way to coerce or limit vector lengths or * check that the length matches an expected value. (You can determine * the length from the return value and of course you can decide to * reject the message if the length is not acceptable.) * * When a vector is returned, the argument vector has a single element * that points to a vector descriptor (the "v" field), which contains * the vector element types and the length of the vector (>= 0). * * When an array is returned, the argument vector contains the value * o2_got_start_array followed by an o2_arg_ptr for each element of * the array, followed by o2_got_end_array. * * When types T (True), F (False), I (Infinitum), or N (Nil) are in * the message, there is an entry in the argument vector; however, * there is no data associated with these types (other than the type * itself), so the pointers point to zero bytes and therefore should * not be used. * * In all other cases, the argument vector contains data * corresponding to the data item in the message. This may be a pointer * into the actual message or a pointer to a temporary location in case * the element was coerced to a different type. * * When the actual type code in the message is in "TFIN" you should * call o2_get_next() even though there is no corresponding data * stored in the message. The return value, if successful, is a * non-NULL pointer that points within or just after the message, but * you must not dereference this pointer. (NULL indicates failure as * with other type codes. One rationale for calling o2_get_next() * even when there is nothing to "get" is that you can call * o2_get_next('B') to retrieve 'T', 'F', or 'B' types as an int32_t * which is 0 or 1. The 'I' and 'N' types are never coerced. * * Normally, you should not free the message because * normally you are accessing the message in a handler and the message * will be freed by the O2 message dispatch code that called the * handler. * * Arrays denoted by [...] in the type string are handled in a somewhat * special way: * * If an array is expected, call o2_get_next('['). The return value will be * o2_got_start_array on success, or NULL if there is no array. The actual * value in the message may be an array or a vector. If it is a vector, the * elements of the vector will be coerced to the types requested in * successive calls to o2_get_next(). After retrieving array elements, call * o2_get_next(']'). The return value should be o2_got_end_array. NULL is * returned if there is an error. For example, suppose you call o2_get_next() * with characters from the type string "[id]" and the actual parameter is * a vector integers ("vi") of length 2. The return values from o2_get_next() * will be o2_got_start_array, an o2_arg_ptr to an integer, an o2_arg_ptr to * a double (coerced from the integer vector), and finally o2_got_end_array. * If the vector length is 1, the third return value will be NULL. If the * vector length is 3 (or more), the fourth return value will be NULL rather * than o2_got_end_array. * * The special values o2_got_start_array and o2_got_end_array are not valid * structures. In other words, fields such as o2_got_start_array->i32 are * never valid or meaningful. Instead, o2_got_start_array and o2_got_end_array * are just 'tokens' used to indicate success in type checking. These values * are distinct from NULL, which indicates a type incompatibility. * * Note also that vector elements cannot be retrieved directly without * calling o2_get_next('v') or o2_get_next('['). For example, if the actual * argument is a two-element integer vector ("vi"), a call to * o2_get_next(O2_INT32) will fail unless it is preceded by * o2_get_next(O2_VECTOR) or o2_get_next(O2_ARRAY_START). * * If a vector is expected, call o2_get_next(O2_VECTOR). The return value will * be a non-null o2_arg_ptr if the next argument in the actual message * is a vector or array, and otherwise NULL. You should not dereference this * return value yet... * * You *must* then call o2_get_next() with the desired type for vector * elements. The return value will be an o2_arg_ptr (which will be * the same value previously returned) containing v.typ set to * the desired type, v.len still set to the number of elements, and v.vi, * v.vh, v.vd, v.vf, or v.vc pointing to the possibly coerced elements. * * Note that the sequence of calling o2_get_next() twice for vectors * corresponds to the two type characters used to encode them, e.g. "vi" * indicates a vector of integers. * * Coercion is supported as follows. If coercion is provided from * the type indicated on the left on some row to the types corresponding * to columns where an "x" appears ("*" indicates special consideration * described below. * * i h f d t s S T F B b m c N I * i x x x x x * * x 32-bit int * h x x x x x * * x 64-bit int * f x x x x x * * x float * d x x x x x * * x double * t x x x x x time * s x x String * S x x Symbol * T x x x x x x True * F x x x x x x False * B x x x x * * x Boolean * b x blob * m x MIDI * c x character * N x Nil * I x Infinitum * * *Entries marked with "*": Coercion succeeds * from 0 to False and from non-zero to True, * otherwise coercion fails. */ /** \addtogroup lowlevelsend * @{ */ /** * \brief Allocate a blob. * * Allocate a blob and initialize the size field. If the return address * is not NULL, copy data (up to length size) to `blob->data`. You can * change `blob->size`, but of course you should not set `blob->size` * greater than the `size` parameter originally passed to o2_blob_new(). * * Caller is responsible for freeing the returned blob using O2_FREE(). * * A constructed blob can be added to a message. If you add parameters to * a message one-at-a-time, you can use o2_add_blob_data() to copy data * directly to a message without first allocating a blob and copying * data into it. * * @param size The size of the data to be added to the blob * * @return the address of the new blob or NULL if memory cannot be allocated. */ o2_blob_ptr o2_blob_new(uint32_t size); /** * \brief Prepare to build a message * * @return #O2_SUCCESS if success, #O2_FAIL if not. * * Allocates a "hidden" message in preparation for adding * parameters. After calling this, you should call `o2_add_` functions * such as o2_add_int32() to add parameters. Then call * o2_send_finish() to send the message. */ int o2_send_start(); /// \brief add a `float` to the message (see o2_send_start()) int o2_add_float(float f); /// \brief This function suppports o2_add_symbol() and o2_add_string() /// Normally, you should not call this directly. int o2_add_string_or_symbol(o2_type tcode, const char *s); /// \brief add a symbol to the message (see o2_send_start()) #define o2_add_symbol(s) o2_add_string_or_symbol(O2_SYMBOL, s) /// \brief add a string to the message (see o2_send_start()) #define o2_add_string(s) o2_add_string_or_symbol(O2_STRING, s) /// \brief add an `o2_blob` to the message (see o2_send_start()), where /// the blob is given as a pointer to an #o2_blob object. int o2_add_blob(o2_blob_ptr b); /// \brief add an `o2_blob` to the message (see o2_send_start()), where /// the blob is specified by a size and a data address. int o2_add_blob_data(uint32_t size, void *data); /// \brief add an `int64` to the message (see o2_send_start()) int o2_add_int64(int64_t i); /// \brief This function supports o2_add_double() and o2_add_time() /// Normally, you should not call this directly. int o2_add_double_or_time(o2_type tchar, double d); /// \brief add a `double` to the message (see o2_send_start()) #define o2_add_double(d) o2_add_double_or_time(O2_DOUBLE, d) /// \brief add a time (`double`) to the message (see o2_send_start()) #define o2_add_time(t) o2_add_double_or_time(O2_TIME, t) /// \brief This function supports o2_add_int32() and o2_add_char() /// Normally, you should not call this directly. int o2_add_int32_or_char(o2_type tcode, int32_t i); /// \brief add an `int32` to the message (see o2_send_start()) #define o2_add_int32(i) o2_add_int32_or_char(O2_INT32, i) /// \brief add a `char` to the message (see o2_send_start()) #define o2_add_char(c) o2_add_int32_or_char(O2_CHAR, c) /// \brief add a short midi message to the message (see o2_send_start()) int o2_add_midi(uint32_t m); /// \brief This function supports o2_add_true(), o2_add_false(), o2_add_bool(), /// o2_add_nil(), o2_add_infinitum(), and others. /// Normally, you should not call this directly. int o2_add_only_typecode(o2_type typecode); /// \brief add "true" to the message (see o2_send_start()) #define o2_add_true() o2_add_only_typecode(O2_TRUE); /// \brief add a "false" to the message (see o2_send_start()) #define o2_add_false() o2_add_only_typecode(O2_FALSE); /// \brief add 0 (false) or 1 (true) to the message (see o2_send_start()) #define o2_add_bool(x) o2_add_int32_or_char(O2_BOOL, x != 0) /// \brief add "nil" to the message (see o2_send_start()) #define o2_add_nil() o2_add_only_typecode(O2_NIL); /// \brief add "infinitum" to the message (see o2_send_start()) #define o2_add_infinitum() o2_add_only_typecode(O2_INFINITUM); /// \brief start adding an array #define o2_add_start_array() o2_add_only_typecode(O2_ARRAY_START); /// \brief finish adding an array #define o2_add_end_array() o2_add_only_typecode(O2_ARRAY_END); /** \brief add a vector * * @param element_type the type of the vector elements * @param length the number of vector elements * @param data the vector elements; arranged sequentially in memory * in a format determined by element_type. The element * type is restricted to a character in "ifhtdc" */ int o2_add_vector(o2_type element_type, int length, void *data); /** * \brief add a message to a bundle * * @param msg a message or bundle to add * * @return O2_SUCCESS * * This function can be called after o2_send_start(). If you * add a message to a bundle with this function, you must not * call any other o2_add_*() functions. E.g. do not call both * o2_add_int32() and o2_add_message() on the same message. * * This function does NOT free msg. Probably you should call * o2_message_free(msg) after calling o2_add_message(msg). */ int o2_add_message(o2_message_ptr msg); /** * \brief finish and return the message. * * @param time the timestamp for the message (0 for immediate) * @param address the O2 address pattern for the message * @param tcp_flag boolean if true, send message reliably * * @return the address of the completed message, or NULL on error * * The message must be freed using o2_message_free() or by calling * o2_message_send(). If the message is a bundle (you have added * messages using o2_add_message()), do not call o2_message_finish(). * Instead, call o2_service_message_finish(). */ o2_message_ptr o2_message_finish(o2_time time, const char *address, int tcp_flag); /** * \brief finish and return a message, prepending service name * * @param time the timestamp for the message (0 for immediate) * @param service a string to prepend to address or NULL. * @param address the O2 address pattern for the message. If this * is a bundle, address should be "" (empty string) * @param tcp_flag boolean if true, send message reliably * * @return the address of the completed message, or NULL on error * * The message must be freed using o2_message_free() or by calling * o2_message_send(). This function is intended to be used to * forward OSC messages to a service, but it is the implementation * of o2_message_finish(), which simply passes NULL for service. */ o2_message_ptr o2_service_message_finish(o2_time time, const char *service, const char *address, int tcp_flag); /** * \brief free a message allocated by o2_send_start(). * * This function is not normally used because O2 functions that send * messages take "ownership" of messages and (eventually) free them. */ void o2_message_free(o2_message_ptr msg); /** * \brief send a message allocated by o2_send_start(). * * This is similar to calling o2_send(), except you use a three-step * process of (1) allocate the message with o2_send_start(), (2) add * parameters to it using `o2_add_` functions, and (3) call * o2_send_finish() to send it. * * @param time the timestamp for the message * @param address the destination address including the service name. * To send a bundle to a service named foo, use the * address "#foo" * @param tcp_flag boolean that says to send the message reliably. * Normally, true means use TCP, and false means use UDP. * * @return #O2_SUCCESS if success, #O2_FAIL if not. */ int o2_send_finish(o2_time time, const char *address, int tcp_flag); /** @} */ /** * \defgroup lowlevelparse Low-Level Message Parsing * * These functions can retrieve message arguments one-at-a-time. * There are some hidden state variables to keep track of the state * of unpacking, so these functions are not reentrant. * Arguments are returned using a pointer to a union type: #o2_arg_ptr. * */ /** \addtogroup lowlevelparse * @{ */ /** * \brief initialize internal state to parse, extract, and coerce * message arguments. * * @return length of the type string in msg * * To get arguments from a message, call o2_extract_start(), then for * each parameter, call o2_get_next(). */ int o2_extract_start(o2_msg_data_ptr msg); /** * \brief get the next message parameter * * This function is called repeatedly to obtain parameters in order * from the message passed to o2_extract_start(). * * If the message parameter type matches the `type_code`, a pointer to * the parameter is returned. If the types do not match, but coercion * is possible, the parameter is coerced, copied to a new location, * and a pointer is returned. Otherwise, NULL is returned. * * The type of any non-NULL return value always matches the type * specified by the parameter `type_code`. To determine the * original type of the parameter as specified by the message, use the * `types` string which is passed to message handlers. (Or course, * this assumes that message type strings are correct. Badly formed * messages are detected when the type string and data imply that the * message is longer than the actual length, but otherwise there is no * way to detect errors in type strings.) * * The result points into the message or to a statically allocated * buffer if type coercion is required. This storage is valid * until the next call to `o2_extract_start`. If the value is a * pointer (string, symbol, midi data, blob), then the value was * not copied and remains in place within the message, so there should * never be the need to immediately copy the data pointed to. * However, since the storage for the value is the message, and * the message will be freed when the handler returns, * pointers to strings, symbols, midi data, and blobs * *must not* be used after the handler returns. * ### Example 1: Simple but not completely robust Note: call o2_method_new() with type_spec = "id", h = my_handler, coerce = false, parse = false. In this case, since there is no type coercion, type_spec must match the message exactly, so o2_get_next() should always return a non-null o2_arg_ptr. However, this code can fail if a badly formed message is sent because there is no test for the NULL value that will be returned by o2_get_next(). \code{.c} int my_handler(o2_message_ptr msg, char *types, o2_arg_ptr *argv, int argc, void *user_data) { o2_extract_start(msg); // we expect an int32 and a double argument int32_t my_int = o2_get_next(O2_INT32)->i32; double my_double = o2_get_next(O2_DOUBLE)->d; ... } \endcode ### Example 2: Type coercion and type checking. Note: call o2_method_new() with type_spec = NULL, h = my_handler, coerce = false, parse = false. In this case, even though coerce is false, there is no type_spec, so the handler will be called without type checking. We could check the actual message types (given by types), but here, we will coerce into our desired types (int32 and double) if possible. Since type coercion can fail (e.g. string will not be converted to number, not even "123"), we need to check the return value from o2_get_next(), where NULL indicates incompatible types. \code{.c} int my_handler(o2_message_ptr msg, char *types, o2_arg_ptr *argv, int argc, void *user_data) { o2_extract_start(msg); // we want to get an int32 and a double argument o2_arg_ptr ap = o2_get_next(O2_INT32); if (!ap) return O2_FAIL; // parameter cannot be coerced int32_t my_int = ap->i32; o2_arg_ptr ap = o2_get_next(O2_DOUBLE); if (!ap) return O2_FAIL; // parameter cannot be coerced double my_double = ap->d; ... } \endcode * * @param type_code the desired parameter type * * @return the next message parameter or NULL if no more parameters */ o2_arg_ptr o2_get_next(o2_type type_code); /** @} */ /* Scheduling */ /** \addtogroup basics * @{ */ // Messages are stored in the table modulo their timestamp, so the // table acts sort of like a hash table (this is also called the // timing wheel structure). Messages are stored as linked lists sorted // by increasing timestamps when there are collisions. /** \cond INTERNAL */ \ // Size of scheduler table. #define O2_SCHED_TABLE_LEN 128 // Scheduler data structure. typedef struct o2_sched { int64_t last_bin; double last_time; o2_message_ptr table[O2_SCHED_TABLE_LEN]; } o2_sched, *o2_sched_ptr; /** \endcond */ /** * \brief Scheduler that schedules according to global (master) clock * time * * Scheduling on this scheduler (including sending timed messages) * will only work after clock synchronization is obtained. Until then, * timed message sends will fail and attempts to o2_schedule() will * fail. */ extern o2_sched o2_gtsched; /** * \brief Scheduler that schedules according to local clock time * * It may be necessary to schedule events before clock synchronization * with the master clock, or you may want to schedule local processing * that ignores any changes in clock time or clock speed needed to * stay synchronized with the master clock (even though these should * be small). For example, O2 uses the local time scheduler to * schedule the clock synchronization protocol, which of course must * run before clock synchronization is obtained. * * In these cases, you should schedule messages using #o2_ltsched. */ extern o2_sched o2_ltsched; /** * \brief Current scheduler. * * When a timed message is delivered by a scheduler, #o2_active_sched * is set to pount to the scheduler. A handler that constructs and * schedules a message can use this pointer to continue using the same * scheduler. */ extern o2_sched_ptr o2_active_sched; // the scheduler that should be used /** * /brief Schedule a message. * * Rather than sending a message, messages can be directly * scheduled. This is particulary useful if you want to schedule * activity before clock synchronization is achieved. For example, you * might want to poll every second waiting for clock * synchronization. In that case, you need to use the local scheduler * (#o2_ltsched). o2_send() will use the global time scheduler * (#o2_gtsched), so your only option is to construct a message and * call o2_schedule(). * * @param scheduler a pointer to a scheduler (`&o2_ltsched` or * `&o2_gtsched`) * @param msg a pointer to the message to schedule * * The message is scheduled for delivery according to its timestamp * (which is interpreted as local or global time depending on the * scheduler). * * The message is delivered immediately if the time is zero or less * than the current time; however, to avoid unbounded recursion, * messages scheduled within handlers are appended to a "pending * messages" queue and delivered after the handler returns. */ int o2_schedule(o2_sched_ptr scheduler, o2_message_ptr msg); /** @} */ // end of a basics group #ifdef __cplusplus } #endif #endif /* O2_H */ o2-1.0/src/o2_discovery.c0000644000175000017500000004454713072261166015510 0ustar zmoelnigzmoelnig// // O2_discovery.c // O2 // // Created by 弛张 on 1/26/16. // Copyright © 2016 弛张. All rights reserved. // #include "o2_internal.h" #include "o2_message.h" #include "o2_send.h" #include "o2_clock.h" #include "o2_discovery.h" // o2_discover: // initially send a discovery message every 0.133s, but increase the // interval by 10% each time until a maximum interval of 4s is // reached. This gives a message every 40ms on average when there // are 100 processes. Also gives 2 tries on 5 ports within first 2s. // next_discovery_index is the port we will send discover message to // next_discovery_recv_time is the time in seconds when we should try // to receive a discovery message double next_discovery_recv_time = 0; double o2_discovery_recv_interval = 0.1; double o2_discovery_send_interval = 0.133; int next_discovery_index = 0; // which port to send to (0 - 4) struct sockaddr_in broadcast_to_addr; // address for sending broadcast messages struct sockaddr_in local_to_addr; // address for sending local discovery msgs SOCKET broadcast_sock = INVALID_SOCKET; int broadcast_recv_port = -1; // port we grabbed o2_time o2_discovery_period = DEFAULT_DISCOVERY_PERIOD; static int disc_port_index = -1; // From Wikipedia: The range 49152–65535 (215+214 to 216−1) contains // dynamic or private ports that cannot be registered with IANA.[198] // This range is used for private, or customized services or temporary // purposes and for automatic allocation of ephemeral ports. // These ports were randomly generated from that range. int o2_port_map[PORT_MAX] = { 64541, 60238, 57143, 55764, 56975, 62711, 57571, 53472, 51779, 63714, 53304, 61696, 50665, 49404, 64828, 54859 }; int o2_discovery_initialize() { #ifdef WIN32 //Initialize (in Windows) WSADATA wsaData; WSAStartup(MAKEWORD(2, 2), &wsaData); #endif // WIN32 // Set up a socket for broadcasting discovery info if ((broadcast_sock = socket(AF_INET, SOCK_DGRAM, 0)) < 0) { perror("Create broadcast socket"); return O2_FAIL; } O2_DBo(printf("%s broadcast socket %ld created\n", o2_debug_prefix, (long) broadcast_sock)); // Set the socket's option to broadcast int optval = TRUE; if (setsockopt(broadcast_sock, SOL_SOCKET, SO_BROADCAST, (const char *) &optval, sizeof(int)) == -1) { perror("Set socket to broadcast"); return O2_FAIL; } // Initialize addr for broadcasting broadcast_to_addr.sin_family = AF_INET; if (inet_pton(AF_INET, "255.255.255.255", &(broadcast_to_addr.sin_addr.s_addr)) != 1) return O2_FAIL; // Create socket to receive broadcasts // Try to find an available port number from the discover port map. // If there are no available port number, print the error & return O2_FAIL. int ret; for (disc_port_index = 0; disc_port_index < PORT_MAX; disc_port_index++) { broadcast_recv_port = o2_port_map[disc_port_index]; process_info_ptr info; ret = o2_make_udp_recv_socket(DISCOVER_SOCKET, &broadcast_recv_port, &info); if (ret == O2_SUCCESS) break; } if (disc_port_index >= PORT_MAX) { broadcast_recv_port = -1; // no port to receive discovery messages disc_port_index = -1; fprintf(stderr, "Unable to allocate a discovery port."); return ret; } O2_DBo(printf("%s created discovery port %ld\n", o2_debug_prefix, (long) broadcast_recv_port)); // Set up a socket for sending discovery info locally if ((local_send_sock = socket(AF_INET, SOCK_DGRAM, 0)) < 0) { perror("Create local discovery send socket"); return O2_FAIL; } O2_DBo(printf("%s discovery send socket (UDP) %lld created\n", o2_debug_prefix, (long long) local_send_sock)); // Initialize addr for local sending local_to_addr.sin_family = AF_INET; if (inet_pton(AF_INET, "127.0.0.1", &(local_to_addr.sin_addr.s_addr)) != 1) { return O2_FAIL; } return O2_SUCCESS; } // we are the "client" connecting to a remote process acting as the "server" static int make_tcp_connection(char *ip, int tcp_port, o2_socket_handler handler, process_info_ptr *info) { // We are the client because our ip:port string is lower struct sockaddr_in remote_addr; //set up the sockaddr_in #ifndef WIN32 bzero(&remote_addr, sizeof(remote_addr)); #endif // expand socket arrays for new port RETURN_IF_ERROR(o2_make_tcp_recv_socket(TCP_SOCKET, 0, handler, info)); o2_process_initialize(*info, PROCESS_CONNECTED); // set up the connection remote_addr.sin_family = AF_INET; //AF_INET means using IPv4 inet_pton(AF_INET, ip, &(remote_addr.sin_addr)); remote_addr.sin_port = htons(tcp_port); // note: our local port number is not recorded, not needed // get the socket just created by o2_make_tcp_recv_socket SOCKET sock = DA_LAST(o2_fds, struct pollfd)->fd; O2_DBo(printf("%s connect to %s:%d with socket %ld\n", o2_debug_prefix, ip, tcp_port, (long) sock)); if (connect(sock, (struct sockaddr *) &remote_addr, sizeof(remote_addr)) == -1) { perror("Connect Error!\n"); o2_fds_info.length--; // restore socket arrays o2_fds.length--; return O2_FAIL; } o2_disable_sigpipe(sock); O2_DBd(printf("%s connected to %s:%d index %d\n", o2_debug_prefix, ip, tcp_port, o2_fds.length - 1)); return O2_SUCCESS; } // Since discovery message is fixed, we'll cache it and reuse it. // the o2_discovery_msg is in network byte order o2_message_ptr o2_discovery_msg = NULL; /// construct a discovery message for this process int o2_discovery_msg_initialize() { int err = o2_send_start() || o2_add_string(o2_application_name) || o2_add_string(o2_local_ip) || o2_add_int32(o2_local_tcp_port) || o2_add_int32(broadcast_recv_port); o2_message_ptr msg; if (err || !(msg = o2_message_finish(0.0, "!_o2/dy", FALSE))) return O2_FAIL; int size = MESSAGE_SIZE_FROM_ALLOCATED(msg->length); if (!((o2_discovery_msg = (o2_message_ptr) o2_malloc(size)))) { return O2_FAIL; } O2_DBd(printf("%s broadcast discovery message created:\n ", o2_debug_prefix); o2_message_print(msg); printf("\n")); #if IS_LITTLE_ENDIAN o2_msg_swap_endian(&msg->data, TRUE); #endif memcpy(o2_discovery_msg, msg, size); o2_message_free(msg); O2_DBg(printf("%s in o2_initialize,\n name is %s, local IP is %s, \n" " udp receive port is %d,\n" " tcp connection port is %d,\n broadcast recv port is %d\n", o2_debug_prefix, o2_application_name, o2_local_ip, o2_process->port, o2_local_tcp_port, broadcast_recv_port)); return O2_SUCCESS; } int o2_discovery_finish() { // sockets are all freed elsewhere O2_FREE(o2_discovery_msg); return O2_SUCCESS; } /** * Broadcast o2_discovery_msg to a discovery port. * * @param port_s the destination port number */ static void o2_broadcast_message(int port) { // set up the address and port broadcast_to_addr.sin_port = htons(port); o2_msg_data_ptr msg = &o2_discovery_msg->data; int len = o2_discovery_msg->length; // broadcast the message if (o2_found_network) { O2_DBd(printf("%s broadcasting discovery msg to port %d\n", o2_debug_prefix, port)); if (sendto(broadcast_sock, (char *) msg, len, 0, (struct sockaddr *) &broadcast_to_addr, sizeof(broadcast_to_addr)) < 0) { perror("Error attempting to broadcast discovery message"); } } // assume that broadcast messages are not received on the local machine // so we have to send separately to localhost using the same port; // since we own broadcast_recv_port, there is no need to send in that case if (port != broadcast_recv_port) { local_to_addr.sin_port = broadcast_to_addr.sin_port; // copy port number O2_DBd(printf("%s sending localhost discovery msg to port %d\n", o2_debug_prefix, port)); if (sendto(local_send_sock, (char *) msg, len, 0, (struct sockaddr *) &local_to_addr, sizeof(local_to_addr)) < 0) { perror("Error attempting to send discovery message locally"); } } } /// callback function that implements sending discovery messages // message args are: // o2_process_ip (as a string), udp port (int), tcp port (int) // void o2_discovery_send_handler(o2_msg_data_ptr msg, const char *types, o2_arg_ptr *argv, int argc, void *user_data) { // O2 is not going to work if we did not get a discovery port if (disc_port_index < 0) return; next_discovery_index = (next_discovery_index + 1) % (disc_port_index + 1); o2_broadcast_message(o2_port_map[next_discovery_index]); o2_time next_time = o2_local_time() + o2_discovery_send_interval; // back off rate by 10% until we're sending every o2_discovery_period (4s): o2_discovery_send_interval *= 1.1; if (o2_discovery_send_interval > o2_discovery_period) { o2_discovery_send_interval = o2_discovery_period; } // want to schedule another call to send again. Do not use send() // because we are operating off of local time, not synchronized // global time. Instead, form a message and schedule it: if (o2_send_start()) return; o2_message_ptr ds_msg = o2_message_finish(next_time, "!_o2/ds", TRUE); // printf("ds_msg %p\n", ds_msg); if (!ds_msg) return; o2_schedule(&o2_ltsched, ds_msg); } int o2_send_initialize(process_info_ptr process) { assert(o2_process->port); // send initial message to newly connected process int err = o2_send_start() || o2_add_string(o2_local_ip) || o2_add_int32(o2_local_tcp_port) || o2_add_int32(o2_process->port) || o2_add_int32(o2_clock_is_synchronized); if (err) return err; // This will be expected as first TCP message and directly // delivered by the o2_tcp_initial_handler() callback o2_message_ptr msg = o2_message_finish(0.0, "!_o2/in", TRUE); if (!msg) return O2_FAIL; err = send_by_tcp_to_process(process, &msg->data); o2_message_free(msg); return err; } int o2_send_services(process_info_ptr process) { // send services if any if (o2_process->proc.services.length <= 0) { return O2_SUCCESS; } o2_send_start(); o2_add_string(o2_process->proc.name); for (int i = 0; i < o2_process->proc.services.length; i++) { char *service = *DA_GET(o2_process->proc.services, char *, i); // ugly, but just a fast test if service is _o2: if ((*((int32_t *) service) != *((int32_t *) "_o2"))) { o2_add_string(service); o2_add_true(); O2_DBd(printf("%s o2_send_services sending %s to %s\n", o2_debug_prefix, service, process->proc.name)); } } char address[32]; snprintf(address, 32, "!%s/sv", process->proc.name); return o2_send_finish(0.0, address, TRUE); } // /_o2/dy handler, parameters are: application name, ip, tcp, and upd // void o2_discovery_handler(o2_msg_data_ptr msg, const char *types, o2_arg_ptr *argv, int argc, void *user_data) { O2_DBd(o2_dbg_msg("o2_discovery_handler gets", msg, NULL, NULL)); o2_arg_ptr app_arg, ip_arg, tcp_arg, udp_arg; // get the arguments: application name, ip as string, // tcp port, discovery port o2_extract_start(msg); if (!(app_arg = o2_get_next('s')) || !(ip_arg = o2_get_next('s')) || !(tcp_arg = o2_get_next('i')) || !(udp_arg = o2_get_next('i'))) { // the discovery port return; } char *ip = ip_arg->s; int tcp = tcp_arg->i32; if (!streql(app_arg->s, o2_application_name)) { O2_DBd(printf(" Ignored: application name is not %s\n", o2_application_name)); return; } char name[32]; // ip:port + pad with zeros snprintf(name, 32, "%s:%d%c%c%c%c", ip, tcp, 0, 0, 0, 0); int compare = strcmp(o2_process->proc.name, name); if (compare == 0) { O2_DBd(printf(" Ignored: I received my own broadcast message\n")); return; // the "discovered process" is this one } o2_entry_ptr *entry_ptr = o2_lookup(&o2_path_tree, name); // if process is connected, ignore it if (*entry_ptr) { #ifndef NDEBUG process_info_ptr remote = NULL; services_entry_ptr services = (services_entry_ptr) *entry_ptr; assert(services && services->tag == SERVICES && services->services.length == 1); remote = (process_info_ptr) GET_SERVICE(services->services, 0); assert(remote && remote->tag == TCP_SOCKET && remote->fds_index != -1); O2_DBd(printf(" Ignored: already connected\n")); #endif return; // we've already connected or accepted, so ignore the /dy data } if (compare > 0) { // we are server, the other party should connect // send a discover message back to sender's UDP port, which is now known struct sockaddr_in udp_sa; udp_sa.sin_family = AF_INET; #ifdef __APPLE__ udp_sa.sin_len = sizeof(udp_sa); #endif inet_pton(AF_INET, ip, &(udp_sa.sin_addr.s_addr)); udp_sa.sin_port = htons(udp_arg->i32); if (sendto(local_send_sock, (char *) &o2_discovery_msg->data, o2_discovery_msg->length, 0, (struct sockaddr *) &udp_sa, sizeof(udp_sa)) < 0) { perror("Error attepting to send discovery message directly"); } O2_DBd(printf("%s o2_discovery_handler to become server for %s\n", o2_debug_prefix, name)); } else { // we are the client process_info_ptr remote; O2_DBg(printf("%s ** Discovered and connecting to %s\n", o2_debug_prefix, name)); if (make_tcp_connection(ip, tcp, &o2_tcp_initial_handler, &remote)) { return; } remote->proc.name = o2_heapify(name); assert(remote->tag == TCP_SOCKET); o2_service_provider_new(name, (o2_info_ptr) remote, remote); o2_send_initialize(remote); o2_send_services(remote); } } // After a TCP connection is made, processes exchange information, // arriving info is used to create a process description which is stored // in the tcp port info of the connected tcp port // void o2_discovery_init_handler(o2_msg_data_ptr msg, const char *types, o2_arg_ptr *argv, int argc, void *user_data) { o2_arg_ptr ip_arg, tcp_arg, udp_arg, clocksync_arg; // get the arguments: application name, ip as string, // tcp port, udp port if (o2_extract_start(msg) != 4 || !(ip_arg = o2_get_next('s')) || !(tcp_arg = o2_get_next('i')) || !(udp_arg = o2_get_next('i')) || !(clocksync_arg = o2_get_next('i'))) { printf("**** error in o2_tcp_initial_handler -- code incomplete ****\n"); return; } int tcp_port = tcp_arg->i32; int udp_port = udp_arg->i32; if (udp_port == 0) return; char *ip = ip_arg->s; // get process name char name[32]; // ip:port + pad with zeros snprintf(name, 32, "%s:%d%c%c%c%c", ip, tcp_port, 0, 0, 0, 0); // no byte-swap check needed for clocksync because it is just zero/non-zero int status = (clocksync_arg->i32 ? PROCESS_OK : PROCESS_NO_CLOCK); // if o2_path_tree entry does not exist, create it process_info_ptr info = (process_info_ptr) user_data; assert(info->proc.status == PROCESS_CONNECTED); o2_entry_ptr *entry_ptr = o2_lookup(&o2_path_tree, name); if (!*entry_ptr) { // we are the server, and we accepted a client connection, // but we did not yet create a service named for client's IP:port assert(info->tag == TCP_SOCKET); o2_service_provider_new(name, (o2_info_ptr) info, info); assert(info->proc.name == NULL); info->proc.name = o2_heapify(name); // now that we have a name and service, we can send init message back: o2_send_initialize(info); o2_send_services(info); } // else we are the client, and we connected after receiving a // /dy message, also created a service named for server's IP:port info->proc.status = status; info->proc.udp_sa.sin_family = AF_INET; assert(info != o2_process); info->port = udp_port; #ifdef __APPLE__ info->proc.udp_sa.sin_len = sizeof(info->proc.udp_sa); #endif inet_pton(AF_INET, ip, &(info->proc.udp_sa.sin_addr.s_addr)); info->proc.udp_sa.sin_port = htons(udp_port); O2_DBd(printf("%s init msg from %s (udp port %ld)\n to local socket " "%ld process_info %p\n", o2_debug_prefix, name, (long) udp_port, (long) (info->fds_index), info)); return; } // /ip:port/sv: called to announce services available or removed. Arguments are // process name, service1, added_flag, service2, added_flag, ... // void o2_services_handler(o2_msg_data_ptr msg, const char *types, o2_arg_ptr *argv, int argc, void *user_data) { o2_extract_start(msg); o2_arg_ptr arg = o2_get_next('s'); if (!arg) return; char *name = arg->s; // note that name is padded with zeros to 32-bit boundary process_info_ptr proc = (process_info_ptr) o2_service_find(name); if (!proc || proc->tag != TCP_SOCKET) { O2_DBg(printf("%s ### ERROR: o2_services_handler did not find %s\n", o2_debug_prefix, name)); return; // message is bogus (should we report this?) } o2_arg_ptr addarg; while ((arg = o2_get_next('s')) && (addarg = o2_get_next('B'))) { if (strchr(arg->s, '/')) { O2_DBg(printf("%s ### ERROR: o2_services_handler got bad service " "name - %s\n", o2_debug_prefix, arg->s)); } else if (addarg->B) { O2_DBd(printf("%s found service /%s offered by /%s\n", o2_debug_prefix, arg->s, proc->proc.name)); o2_service_provider_new(arg->s, (o2_info_ptr) proc, proc); } else { o2_service_provider_replace(proc, arg->s, NULL); } } } o2-1.0/src/o2_sched.h0000644000175000017500000000027613072261166014563 0ustar zmoelnigzmoelnig// o2_sched.h -- header for os_sched.c void o2_sched_finish(o2_sched_ptr s); void o2_sched_start(o2_sched_ptr s, o2_time start_time); void o2_sched_initialize(); void o2_sched_poll(); o2-1.0/src/o2_message.c0000644000175000017500000015656213072261166015126 0ustar zmoelnigzmoelnig// // o2_message.c // O2 // // Created by 弛张 on 1/26/16. // Copyright © 2016 弛张. All rights reserved. // // This module constructs and deconstructs messages. // // For deconstruction, the "deluxe" result is an argument vector // (argv) consisting of (essentially) one pointer per argument. // These argv pointers point into the message when no type // conversion is required, and into an argument data buffer when // data must be copied from the message and converted. (We do not // convert data in place because the message must be retained for // possible delivery to another handler.) // // Deconstruction can also be incremental, fetching one argument // at a time, but that still results in forming an argument vector. // // To simplify deconstruction, we allocate two buffers: One is // for the argument vector (pointers). The other is for argument // data. The problem is further simplified by allocating space for // the worst case based on the total message length. The worst case // number of arguments is message length / 4 assuming parameters // take at least 4 bytes. Thus, the size for argv must be: // (length/4) * sizeof(o2_arg_ptr) // However, vectors could have zero length yet have an arg // vector element. This could be represented in the message as // "[]..." so we should allocate 4 times the message length for // the arg vector. To tighten this bound, notice that we only need // 4 times the type string length for arrays and twice the // remaining message length for vectors. // // The worst case data size happens when data is coerced to // 64-bits, e.g. doubles, from 32-bit ints or floats. Another // possibility is coercion from 0-length arrays to 0-length vectors. // The type strings would be "[][]..." taking two bytes per element. // The vectors take a length count, type, and pointer, which could total // 16 bytes, resulting in a factor of 8 expansion. We can cut this // down by storing only the length (32-bits) when it is // zero, again limiting the expansion factor to 2. If the arrays // have one element, e.g. "[f][f]...", we require 7 bytes per array. // If these are converted to vectors of doubles, we need 24 bytes // per vector (len = 1, typ = 'f', pointer to vector, and the // double itself. Here, the memory requirement is 24/7 times // the message length. Again, we can tighten the bound: upper // bounds are 24/3 times the size of the type string, and // 24/4 times the size of the remaining message. // // The main motivation to pre-allocate storage before unpacking // messages is that vectors can have pointers to coerced data // so if we have to reallocate data, we have to scan the coerced // data and adjust the pointers. This in turn, requires that we // retain the types of all the arg vectors, requiring even more // allocation and bookkeeping. #include "o2_internal.h" #include "o2_message.h" #include "o2_discovery.h" #include "o2_send.h" // --------- PART 1 : SCRATCH AREAS FOR MESSAGE CONSTRUCTION -------- // Construct messages by writing type string to msg_types and data to // msg_data. These arrays grow as needed, so they are dynamic arrays. // These arrays are only freed when o2 is shut down. Since the storage // is retained, message construction is NOT REENTRANT. You MUST finish // construction and take away a message before starting the next message. // This approach potentially adds a copy operation from msg_data to // the message itself, but except for cases where the type string is // known in advance, you have to copy anyway to place the data after the // type string. Furthermore, even if you know the type string, you do // not necessarily know the data length -- if you start assembling the // message and have to "grow" the message, you'll have to copy anyway. // Therefore, you can only avoid copies on short messages with known // type strings, but if the message is short, the copy cost is probably // insignificant compared to all the other work to send, schedule, and // dispatch the message. // msg_types is used to hold type codes as message args are accumulated static dyn_array msg_types; // msg_data is used to hold data as message args are accumulated static dyn_array msg_data; // make sure enough memory is allocated to add an element to msg_data // static void message_check_length(int needed) { while (msg_data.length + needed > msg_data.allocated) { o2_da_expand(&msg_data, sizeof(char)); } } static void add_type(o2_type type_code) { DA_APPEND(msg_types, char, type_code); } #define ADD_DATA(data_type, code, data) \ message_check_length(sizeof(data_type)); \ *((data_type *) (msg_data.array + msg_data.length)) = (data); \ msg_data.length += sizeof(data_type); \ DA_APPEND(msg_types, char, code); // -------- PART 2 : SCRATCH AREA FOR MESSAGE EXTRACTION // Messages are unpacked into an argv of pointer to union type // o2_arg_ptr, to allow access according to type codes. There // is also storage pointed to by the argv pointers. When types // are converted, we often need to copy from the message into // this storage, but if possible we avoid copies by pointing // into the message itself. o2_argv_data is a dynamic array for // o2_argv, and o2_arg_data is a dynamic array for type-converted // message data. Because of the pointers, it's very messy to // reallocate the dynamic arrays. Instead, we precompute the // worst case based on the length of the message type string // and the length of the message data, and we expand the // dynamic arrays to accommodate the worst case scenario. o2_arg_ptr *o2_argv; // arg vector extracted by calls to o2_get_next() int o2_argc; // length of argv // o2_argv_data is used to create the argv for handlers. It is expanded as // needed to handle the largest message and is reused. dyn_array o2_argv_data; // o2_arg_data holds parameters that are coerced from message data // It is referenced by o2_argv_data and expanded as needed. dyn_array o2_arg_data; // make sure enough memory is allocated and initialize o2_argv and o2_argc // static void need_argv(int argv_needed, int arg_needed) { while (o2_argv_data.allocated < argv_needed) { o2_da_expand(&o2_argv_data, 1); } while (o2_arg_data.allocated < arg_needed) { o2_da_expand(&o2_arg_data, 1); } o2_argv_data.length = 0; // initialize arrays to empty o2_arg_data.length = 0; o2_argv = DA_GET(o2_argv_data, o2_arg_ptr, 0); o2_argc = 0; } // call this once when o2 is initialized void o2_argv_initialize() { DA_INIT(o2_argv_data, o2_arg_ptr, 16); DA_INIT(o2_arg_data, char, 96); DA_INIT(msg_types, char, 16); DA_INIT(msg_data, char, 96); } // call this when o2 is finalized void o2_argv_finish() { DA_FINISH(o2_argv_data); DA_FINISH(o2_arg_data); DA_FINISH(msg_types); DA_FINISH(msg_data); } // update o2_arg_data to indicate the something has been appended #define ARG_DATA_USED(data_type) o2_arg_data.length += sizeof(data_type) // write a data item into o2_arg_data as part of message construction #define ARG_DATA(rslt, data_type, data) \ *((data_type *) (rslt)) = (data); \ ARG_DATA_USED(data_type); #define ARG_NEXT ((o2_arg_ptr) (o2_arg_data.array + o2_arg_data.length)) /// end of message must be zero to prevent strlen from running off the /// end of malformed message #define MSG_ZERO_END(msg, siz) *((int32_t *) &(PTR(msg))[(siz) - 4]) = 0 // ------- PART 3 : ADDING ARGUMENTS TO MESSAGE DATA // These functions add data to msg_types and msg_data static int is_bundle = FALSE; static int is_normal = FALSE; int o2_send_start() { msg_types.length = 0; msg_data.length = 0; is_bundle = FALSE; is_normal = FALSE; add_type(','); return O2_SUCCESS; } int o2_add_float(float f) { if (is_bundle) return O2_FAIL; is_normal = TRUE; ADD_DATA(float, 'f', f); return O2_SUCCESS; } int o2_add_int64(int64_t i) { if (is_bundle) return O2_FAIL; is_normal = TRUE; ADD_DATA(int64_t, 'h', i); return O2_SUCCESS; } int o2_add_int32_or_char(o2_type code, int32_t i) { if (is_bundle) return O2_FAIL; is_normal = TRUE; ADD_DATA(int32_t, code, i); return O2_SUCCESS; } int o2_add_double_or_time(o2_type code, double d) { if (is_bundle) return O2_FAIL; is_normal = TRUE; ADD_DATA(double, code, d); return O2_SUCCESS; } int o2_add_only_typecode(o2_type code) { if (is_bundle) return O2_FAIL; is_normal = TRUE; message_check_length(0); DA_APPEND(msg_types, char, code); return O2_SUCCESS; } int o2_add_string_or_symbol(o2_type code, const char *s) { if (is_bundle) return O2_FAIL; is_normal = TRUE; // coerce to avoid compiler warning; o2 messages cannot be that long, but // this could overflow if user passed absurd data, but then the string would // be arbitrarily truncated. The message could then still be huge, so I'm not // sure what would happen. int s_len = (int) strlen(s); message_check_length(s_len + 4); // add 4 in case of padding char *dst = msg_data.array + msg_data.length; char *last = dst + s_len; size_t ilast = (((size_t) last + 4)) & ~3; *((int32_t *) (PTR(ilast) - 4)) = 0; memcpy(dst, s, s_len); msg_data.length += (s_len + 4) & ~3; DA_APPEND(msg_types, char, code); return O2_SUCCESS; } int o2_add_blob_data(uint32_t size, void *data) { if (is_bundle) return O2_FAIL; is_normal = TRUE; message_check_length(size + 8); // add 8 for length and padding o2_add_int32_or_char('b', size); char *dst = msg_data.array + msg_data.length; char *last = dst + size; size_t ilast = (((size_t) last + 3)) & ~3; if (size > 0) { *((int32_t *) (PTR(ilast) - 4)) = 0; } memcpy(dst, data, size); msg_data.length += (size + 3) & ~3; return O2_SUCCESS; } int o2_add_blob(o2_blob *b) { return o2_add_blob_data(b->size, b->data); } int o2_add_midi(uint32_t m) { return o2_add_int32_or_char(O2_MIDI, (int32_t) m); } int o2_add_vector(o2_type element_type, int32_t length, void *data) { if (is_bundle) return O2_FAIL; is_normal = TRUE; if (!strchr("ihfd", element_type)) { return O2_BAD_TYPE; } int size = (element_type == 'd' || element_type == 'h') ? sizeof(double) : sizeof(int32_t); length *= size; // length is now the vector length in bytes // the message contains the number of bytes in the vector data message_check_length(sizeof(int32_t) + length); o2_add_int32_or_char('v', length); add_type(element_type); char *dst = msg_data.array + msg_data.length; memcpy(dst, data, length); msg_data.length += length; return O2_SUCCESS; } // add a message to a bundle int o2_add_message(o2_message_ptr msg) { if (is_normal) return O2_FAIL; is_bundle = TRUE; // add a length followed by data portion of msg int msg_len = msg->length + 4; // add 4 for length message_check_length(msg_len); char *src = PTR(&msg->data) - 4; // get length and data char *dst = PTR(msg_data.array) + msg_data.length; memcpy(dst, src, msg_len); msg_data.length += (msg_len + 3) & ~3; return O2_SUCCESS; } o2_message_ptr o2_message_finish(o2_time time, const char *address, int tcp_flag) { return o2_service_message_finish(time, NULL, address, tcp_flag); } // finish building message, sending to service with address appended. // to create a bundle, o2_service_message_finish(time, service, "", tcp_flag) // o2_message_ptr o2_service_message_finish( o2_time time, const char *service, const char *address, int tcp_flag) { int addr_len = (int) strlen(address); // if service is provided, we'll prepend '/', so add 1 to string length int service_len = (service ? (int) strlen(service) + 1 : 0); // total service + address length with zero padding int addr_size = (service_len + addr_len + 4) & ~3; int types_len = msg_types.length; int types_size = (is_bundle ? 0 : ((types_len + 4) & ~3)); int prefix = (is_bundle ? '#' : '/'); int msg_size = sizeof(o2_time) + addr_size + types_size + msg_data.length; o2_message_ptr msg = o2_alloc_size_message(msg_size); if (!msg) return NULL; msg->next = NULL; msg->length = msg_size; msg->data.timestamp = time; char *dst = msg->data.address; int32_t *last_32 = (int32_t *) (dst + addr_size - sizeof(int32_t)); *last_32 = 0; // fill last 32-bit word with zeros if (service) { *dst = prefix; memcpy(dst + 1, service, service_len); dst += service_len; } memcpy(dst, address, addr_len); dst = PTR(last_32 + 1); last_32 = (int32_t *) (dst + types_size - sizeof(int32_t)); *last_32 = 0; // fil last 32-bit word with zeros // if building a bundle, types will be ',', and types will be // copied to the message, but types will be overwritten by the // first message of the bundle (or if the bundle has zero messages, // the type string will be written after the end of the message, // (and yes, there is room because small messages are allocated // from a list of fixed-size buffers). memcpy(dst, msg_types.array, types_len); dst += types_size; memcpy(dst, msg_data.array, msg_data.length); msg->tcp_flag = tcp_flag; return msg; } // ------- ADDENDUM: FUNCTIONS TO BUILD OSC BUNDLE FROM O2 BUNDLE ---- int o2_add_bundle_head(int64_t time) { message_check_length(16); memcpy(msg_data.array + msg_data.length, "#bundle", 8); #if IS_LITTLE_ENDIAN time = swap64(time); #endif *((int64_t *) (msg_data.array + msg_data.length + 8)) = time; msg_data.length += 16; return O2_SUCCESS; } int *o2_msg_len_ptr() { message_check_length(sizeof(int32_t)); msg_data.length += sizeof(int32_t); return (int *) (msg_data.array + msg_data.length - sizeof(int32_t)); } int o2_set_msg_length(int32_t *msg_len_ptr) { int32_t len = (int32_t) ((msg_data.array + msg_data.length) - PTR(msg_len_ptr + 1)); #if IS_LITTLE_ENDIAN len = swap32(len); #endif *msg_len_ptr = len; return O2_SUCCESS; } int o2_add_raw_bytes(int32_t len, char *bytes) { message_check_length(len); memcpy(msg_data.array + msg_data.length, bytes, len); msg_data.length += len; return O2_SUCCESS; } char *o2_msg_data_get(int32_t *len_ptr) { *len_ptr = msg_data.length; return PTR(msg_data.array); } // ------- PART 4 : MESSAGE DECONSTRUCTION FUNCTIONS ------- // These functions build o2_argv and the data these pointers reference // // For deconstruction, the "deluxe" result is an argument vector // (argv) consisting of (essentially) one pointer per argument. // These argv pointers point into the message when no type // conversion is required, and into an argument data buffer when // data must be copied from the message and converted. (We do not // convert data in place because the message must be retained for // possible delivery to another handler.) // // To simplify deconstruction, we allocate two buffers: One is // for the argument vector (pointers). The other is for argument // data. The problem is further simplified by allocating space for // the worst case based on the total message length. The worst case // number of arguments is message length / 4 assuming parameters // take at least 4 bytes. Thus, the size for argv must be: // (length/4) * sizeof(o2_arg_ptr) // However, vectors could have zero length yet have an arg // vector element. This could be represented in the message as // "[]..." so we should allocate 4 times the message length for // the arg vector. To tighten this bound, notice that we only need // 4 times the type string length for arrays and twice the // remaining message length for vectors. // // The worst case data size happens when data is coerced to // 64-bits, e.g. doubles, from 32-bit ints or floats. Another // possibility is coercion from 0-length arrays to 0-length vectors. // The type strings would be "[][]..." taking two bytes per element. // The vectors take a length count and pointer, which could be // 16 bytes, resulting in a factor of 8 expansion. We can cut this // down by storing only the length (a 32-bit int) when the length is // zero, again limiting the expansion factor to 2. If the arrays // have one element, e.g. "[f][f]...", we require 7 bytes per array. // If these are converted to vectors of doubles, we need 24 bytes // per vector (assuming 4 bytes of padding after the 4-byte length // plus a pointer to one double and the double itself. Here, the // memory requirement is 24/7 times the message length. Again, we // can tighten the bound: upper bounds are 24/3 times the size of // the type string, and 24/4 times the size of the remaining // message. // // The main motivation to pre-allocate storage before unpacking // messages is that vectors can have pointers to coerced data // so if we have to reallocate data, we have to scan the coerced // data and adjust the pointers. This in turn, requires that we // retain the types of all the arg vectors, requiring even more // allocation and bookkeeping. // State and macros for extracting data. A vector is requested by // passing 'v' to o2_get_next(), an o2_arg_ptr with the vector // length is returned. Then pass an element type code, one of "ihfd" // to o2_get_next(). The same o2_arg_ptr will be returned, but this // time the pointer will be valid (if the length is non-zero). // // For arrays, pass in '[', which returns o2_got_start_array if an // array can be returned. Then pass in type codes for each element // and an o2_arg_ptr for that element will be returned. Finally, // pass in ']' and o2_get_end_array will be returned if you are at // the end of an array or vector (otherwise NULL is returned to // indicate error, as usual). static o2_msg_data_ptr mx_msg = NULL; // the message we are extracting from static char *mx_types = NULL; // pointer to the type codes static char *mx_type_next = NULL; // pointer to the next type code static char *mx_data_next = NULL; // pointer to the next data item in mx_msg static char *mx_barrier = NULL; // pointer to end of message static int mx_vector_to_vector_pending = FALSE; // expecting vector element // type code, will return a whole vector static int mx_array_to_vector_pending = FALSE; // expecting vector element // type code, will return whole vector from array elements static int mx_vector_to_array = FALSE; // when non-zero, we are extracting vector // elements as array elements. The value will be one of "ihfd" depending // on the vector element type static int mx_vector_remaining = 0; // when mx_vector_to_array is set, this // counts how many vector elements remain to be retrieved // macros to extract data: // define functions to read different types from mx_data_next and increment it #define MX_TYPE(fn, typ) typ fn() { typ x = *((typ *) mx_data_next); \ mx_data_next += sizeof(typ); return x; } MX_TYPE(rd_float, float) MX_TYPE(rd_double, double) MX_TYPE(rd_int32, int32_t) MX_TYPE(rd_int64, int64_t) #define MX_FLOAT (*((float *) mx_data_next)) #define MX_DOUBLE (*((double *) mx_data_next)) #define MX_INT32 (*((int32_t *) mx_data_next)) #define MX_INT64 (*((int64_t *) mx_data_next)) #define MX_SKIP(n) mx_data_next += ((n) + 3) & ~3 // ------- PART 5 : GENERAL MESSAGE FUNCTIONS ------- /// a free list of 240-byte (MESSAGE_DEFAULT_SIZE) o2_messages o2_message_ptr message_freelist = NULL; // get a free message of size MESSAGE_DEFAULT_SIZE static o2_message_ptr message_alloc() { o2_message_ptr msg; if (!message_freelist) { msg = (o2_message_ptr) o2_malloc(MESSAGE_DEFAULT_SIZE); // printf("new msg %p\n", msg); msg->allocated = MESSAGE_ALLOCATED_FROM_SIZE(MESSAGE_DEFAULT_SIZE); MSG_ZERO_END(msg, MESSAGE_DEFAULT_SIZE); } else { msg = message_freelist; message_freelist = message_freelist->next; msg->length = 0; } return msg; } void o2_message_free(o2_message_ptr msg) { assert(msg->length != -1); // check if message is already freed msg->length = -1; if (msg->allocated == MESSAGE_ALLOCATED_FROM_SIZE(MESSAGE_DEFAULT_SIZE)) { msg->next = message_freelist; message_freelist = msg; } else { O2_FREE(msg); } } void o2_message_list_free(o2_message_ptr msg) { while (msg) { o2_message_ptr next = msg->next; o2_message_free(msg); msg = next; } } o2_message_ptr o2_alloc_size_message(int size) { if (size <= MESSAGE_ALLOCATED_FROM_SIZE(MESSAGE_DEFAULT_SIZE)) { // standard pre-allocated message is big enough so use one */ return message_alloc(); } else { o2_message_ptr msg = (o2_message_ptr) o2_malloc(MESSAGE_SIZE_FROM_ALLOCATED(size)); msg->allocated = size; return msg; } } int o2_strsize(const char *s) { // coerce to int to avoid compiler warning, O2 messages can't be that long return (int) ((strlen(s) + 4) & ~3); } // o2_blob_new - allocate a blob // o2_blob_ptr o2_blob_new(uint32_t size) { // allocate space for length and extend to word boundary: int64_t needed = WORD_OFFSET(sizeof(uint32_t) + size + 3); if (needed > 0xFFFFFF00) { // allow almost 2^32 byte blobs return NULL; // but leave a little extra room } // int64_t could be bigger than size_t. Avoid compiler warning by coercing: o2_blob_ptr blob = (o2_blob_ptr) O2_MALLOC((size_t) needed); if (blob) { // coerce to avoid compiler warning; we tested so this will not overflow blob->size = (int) needed; } return blob; } #ifdef VALIDATION_FUNCTIONS // o2_validate_string - test if data is a valid string whose // representation is less than or equal to size // returns length of representation (including all zero padding) // ssize_t o2_validate_string(void *data, ssize_t size) { ssize_t i = 0, len = 0; char *pos = data; if (size < 0) { return -O2_ESIZE; // invalid size } for (i = 0; i < size; ++i) { if (pos[i] == '\0') { len = 4 * (i / 4 + 1); break; } } if (0 == len) { return -O2_ETERM; // string not terminated } if (len > size) { return -O2_ESIZE; // would overflow buffer } for (; i < len; ++i) { if (pos[i] != '\0') { return -O2_EPAD; // non-zero char found in pad area } } return len; } ssize_t o2_validate_blob(void *data, ssize_t size) { ssize_t i, end, len; uint32_t dsize; char *pos = (char *) data; if (size < 0) { return -O2_ESIZE; // invalid size } dsize = ntohl(*(uint32_t *)data); if (dsize > O2_MAX_MSG_SIZE) { // avoid int overflow in next step return -O2_ESIZE; } end = sizeof(uint32_t) + dsize; // end of data len = 4 * ((end + 3) / 4); // full padded size if (len > size) { return -O2_ESIZE; // would overflow buffer } for (i = end; i < len; ++i) { if (pos[i] != '\0') { return -O2_EPAD; // non-zero char found in pad area } } return len; } ssize_t o2_validate_bundle(void *data, ssize_t size) { ssize_t len = 0, remain = size; char *pos = data; ssize_t elem_len; len = o2_validate_string(data, size); if (len < 0) { return -O2_ESIZE; // invalid size } if (!streql(data, "#bundle")) { return -O2_EINVALIDBUND; // not a bundle } pos += len; remain -= len; // time tag if (remain < 8) { return -O2_ESIZE; } pos += 8; remain -= 8; while (remain >= 4) { elem_len = ntohl(*((uint32_t *)pos)); pos += 4; remain -= 4; if (elem_len > remain) { return -O2_ESIZE; } pos += elem_len; remain -= elem_len; } if (0 != remain) { return -O2_ESIZE; } return size; } #endif // VALIDATION_FUNCTIONS #define PREPARE_TO_ACCESS(typ) \ char *end = data_next + sizeof(typ); \ if (end > end_of_msg) return O2_INVALID_MSG; /* convert endianness of a message */ int o2_msg_swap_endian(o2_msg_data_ptr msg, int is_host_order) { char *types = O2_MSG_TYPES(msg); int types_len = (int) strlen(types); char *data_next = WORD_ALIGN_PTR(types + types_len + 4); int64_t i64_time = *(int64_t *)(&(msg->timestamp)); i64_time = swap64(i64_time); msg->timestamp = *(o2_time *)(&i64_time); if (IS_BUNDLE(msg)) { FOR_EACH_EMBEDDED(msg, int32_t *len_ptr = ((int32_t *) embedded) - 1; if (is_host_order) len = *len_ptr; *len_ptr = swap32(*len_ptr); if (!is_host_order) len = *len_ptr; if (PTR(msg) + len > end_of_msg) { return O2_FAIL; } o2_msg_swap_endian(embedded, is_host_order)); return O2_SUCCESS; } // do not write beyond barrier (message may be malformed) char *end_of_msg = PTR(msg) + MSG_DATA_LENGTH(msg); while (*types) { if (data_next >= end_of_msg) { return O2_FAIL; } switch (*types) { case O2_INT32: case O2_BOOL: case O2_MIDI: case O2_FLOAT: case O2_CHAR: { PREPARE_TO_ACCESS(int32_t); int32_t i = *(int32_t *)data_next; *(int32_t *)data_next = swap32(i); data_next = end; break; } case O2_BLOB: { int32_t size; PREPARE_TO_ACCESS(int32_t); // this is a bit tricky: size gets the // blob length field either before swapping or after // swapping, depending on whether the message starts // out in host order or not. int32_t *len_ptr = (int32_t *) data_next; if (is_host_order) size = *len_ptr; *len_ptr = swap32(*len_ptr); if (!is_host_order) size = *len_ptr; // now skip the blob data end += size; if (end > end_of_msg) return O2_INVALID_MSG; data_next = end; break; } case O2_TIME: case O2_INT64: case O2_DOUBLE: { PREPARE_TO_ACCESS(int64_t); int64_t i = *(int64_t *)data_next; *(int64_t *)data_next = swap64(i); data_next = end; break; } case O2_STRING: case O2_SYMBOL: { char *end = data_next + o2_strsize(data_next); if (end > end_of_msg) return O2_INVALID_MSG; data_next = end; break; } case O2_TRUE: case O2_FALSE: case O2_NIL: case O2_INFINITUM: /* these are fine, no data to modify */ break; case O2_VECTOR: { PREPARE_TO_ACCESS(int32_t); int32_t *len_ptr = (int32_t *) data_next; int len; if (is_host_order) len = *len_ptr; *len_ptr = swap32(*len_ptr); if (!is_host_order) len = *len_ptr; data_next = end; // now test for vector data within end_of_msg end += len; if (end > end_of_msg) return O2_INVALID_MSG; // swap each vector element len /= 4; // assuming 32-bit elements o2_type vtype = *types++; if (vtype == O2_DOUBLE || vtype == O2_INT64) { len /= 2; // half as many elements if they are 64-bits } for (int i = 0; i < len; i++) { // for each vector element if (i > 0) printf(" "); if (vtype == O2_INT32 || vtype == O2_FLOAT) { *(int32_t *)data_next = swap32(*(int32_t *)data_next); data_next += sizeof(int32_t); } else if (vtype == O2_INT64 || vtype == O2_DOUBLE) { *(int64_t *)data_next = swap64(*(int64_t *)data_next); data_next += sizeof(int64_t); } } break; } default: fprintf(stderr, "O2 warning: unhandled type '%c' at %s:%d\n", *types, __FILE__, __LINE__); return O2_INVALID_MSG; } types++; } return O2_SUCCESS; } int o2_message_build(o2_message_ptr *msg, o2_time timestamp, const char *service_name, const char *path, const char *typestring, int tcp_flag, va_list ap) { o2_send_start(); // add data, a NULL typestring or "" means "no arguments" while (typestring && *typestring) { switch (*typestring++) { case O2_INT32: // get int in case int32 was promoted to int64 o2_add_int32(va_arg(ap, int)); break; case O2_FLOAT: o2_add_float((float) va_arg(ap, double)); break; case O2_SYMBOL: o2_add_symbol(va_arg(ap, char *)); break; case O2_STRING: { char *string = va_arg(ap, char *); o2_add_string(string); #ifndef USE_ANSI_C if (string == (char *) O2_MARKER_A) { fprintf(stderr, "o2 error: o2_send or o2_message_add called with " "invalid string pointer, probably arg mismatch.\n"); } #endif break; } case O2_BLOB: // argument should be a pointer to an o2_blob! o2_add_blob(va_arg(ap, o2_blob_ptr)); break; case O2_INT64: o2_add_int64(va_arg(ap, int64_t)); break; case O2_TIME: o2_add_time(va_arg(ap, double)); break; case O2_DOUBLE: o2_add_double(va_arg(ap, double)); break; case O2_CHAR: o2_add_char(va_arg(ap, int)); break; case O2_MIDI: o2_add_midi(va_arg(ap, uint32_t)); break; case O2_BOOL: o2_add_bool(va_arg(ap, int)); break; case O2_TRUE: case O2_FALSE: case O2_NIL: case O2_INFINITUM: add_type(typestring[-1]); break; // fall through to unknown type default: { fprintf(stderr, "o2 warning: unknown type '%c'\n", *(typestring - 1)); break; } } } #ifndef USE_ANSI_C void *i = va_arg(ap, void *); if ((((unsigned long) i) & 0xFFFFFFFFUL) != (((unsigned long) O2_MARKER_A) & 0xFFFFFFFFUL)) { // bad format/args goto error_exit; } i = va_arg(ap, void *); if ((((unsigned long) i) & 0xFFFFFFFFUL) != (((unsigned long) O2_MARKER_B) & 0xFFFFFFFFUL)) { goto error_exit; } #endif va_end(ap); *msg = o2_service_message_finish(timestamp, service_name, path, tcp_flag); return (*msg ? O2_SUCCESS : O2_FAIL); #ifndef USE_ANSI_C error_exit: fprintf(stderr, "o2 error: o2_send or o2_send_cmd called with mismatching types and data.\n"); va_end(ap); return O2_BAD_ARGS; #endif } // state and macros for message construction // TODO CLEANUP: // use WR_ macros to write data, so // start is where the data starts, this is 1/5 of the way through // the allocated message memory, assuming most arguments are floats // and int32, so we need about 4x as much memory for data. // next is where the data ends, where to append the next argument. // TODO REMOVE static char *mc_barrier = NULL; // end of allocated message data int o2_send_finish(o2_time time, const char *address, int tcp_flag) { o2_message_ptr msg = o2_message_finish(time, address, tcp_flag); if (!msg) return O2_FAIL; return o2_message_send_sched(msg, TRUE); } /// get ready to extract args with o2_get_next /// returns length of type string (not including ',') in message // int o2_extract_start(o2_msg_data_ptr msg) { mx_msg = msg; // point temp_type_end to the first type code byte. // skip over padding and ',' mx_types = O2_MSG_TYPES(msg); mx_type_next = mx_types; // argv needs 4 * type string length + 2 * remaining length // coerce to int to avoid compiler warning; o2 messages can't be that long int types_len = (int) strlen(mx_types); // mx_types + types_len points to the end-of-typestring byte and there can // be up to 3 more zero-pad bytes to the next word boundary mx_data_next = WORD_ALIGN_PTR(mx_types + types_len + 4); // now, mx_data_next points to the first byte of real data // subtract pointer to get length of real data, coerce to int to avoid // compiler warning; message cannot be that big int msg_data_len = (int) (PTR(msg) + MSG_DATA_LENGTH(msg) - mx_data_next); // add 2 for safety int argv_needed = types_len * 4 + msg_data_len * 2 + 2; // o2_arg_data needs at most 24/3 times type string and at most 24/4 // times remaining data. int arg_needed = types_len * 8; if (arg_needed > msg_data_len * 6) arg_needed = msg_data_len * 6; arg_needed += 16; // add some space for safety need_argv(argv_needed, arg_needed); mx_barrier = WORD_ALIGN_PTR(PTR(msg) + MSG_DATA_LENGTH(msg)); // use WR_ macros to write coerced parameters mx_vector_to_array = FALSE; mx_vector_remaining = 0; mx_vector_to_vector_pending = FALSE; return types_len; } static o2_arg_ptr convert_int(o2_type to_type, int64_t i, int siz) { o2_arg_ptr rslt = ARG_NEXT; switch (to_type) { case O2_INT32: // coerce to int to avoid compiler warning; O2 can in fact lose // data coercing from type O2_INT64 to O2_INT32 ARG_DATA(rslt, int32_t, (int32_t) i); break; case O2_INT64: ARG_DATA(rslt, int64_t, i); break; case O2_FLOAT: // coerce to float to avoid compiler warning; O2 can in fact lose // data coercing from type O2_INT64 to O2_FLOAT ARG_DATA(rslt, float, (float) i); break; case O2_DOUBLE: case O2_TIME: // coerce to double to avoid compiler warning; O2 can in fact lose // data coercing from type O2_INT64 to O2_DOUBLE: ARG_DATA(rslt, double, (double) i); break; case O2_BOOL: ARG_DATA(rslt, int32_t, i != 0); break; case O2_TRUE: if (!i) rslt = NULL; break; case O2_FALSE: if (i) rslt = NULL; break; default: return NULL; } return rslt; } static o2_arg_ptr convert_float(o2_type to_type, double d, int siz) { o2_arg_ptr rslt = (o2_arg_ptr) (o2_arg_data.array + o2_arg_data.length); switch (to_type) { case O2_INT32: // coerce to int32_t to avoid compiler warning; O2 can in fact lose // data coercing from type O2_DOUBLE to O2_INT32 ARG_DATA(rslt, int32_t, (int32_t) d); break; case O2_INT64: // coerce to int64_t to avoid compiler warning; O2 can in fact lose // data coercing from type O2_DOUBLE to O2_INT64 ARG_DATA(rslt, int64_t, (int64_t) d); break; case O2_FLOAT: // coerce to float to avoid compiler warning; O2 can in fact lose // data coercing from type O2_DOUBLE to O2_FLOAT ARG_DATA(rslt, float, (float) d); break; case O2_DOUBLE: case O2_TIME: ARG_DATA(rslt, double, d); break; case O2_BOOL: ARG_DATA(rslt, int32_t, d != 0.0); break; case O2_TRUE: if (d == 0.0) rslt = NULL; break; case O2_FALSE: if (d != 0.0) rslt = NULL; break; default: return NULL; } return rslt; } static o2_arg ea, sa; o2_arg_ptr o2_got_end_array = &ea; o2_arg_ptr o2_got_start_array = &sa; /// get the next argument from the message. If the to_type /// does not match the actual type in the message, convert /// if possible; otherwise, return NULL for the o2_arg_ptr. /// Note that if coerce_flag was false, the type checking /// will have compared types for an exact match, so if we /// make it this far and we are constructing argv, then /// no type coercion will take place (and the tests for type /// matching are all redundant because they will all fail). /// If client code is calling this, then there is no way to /// turn off type coercion except that you can compare your /// desired to_type to the character in the actual type /// string and if there is no match, do not call o2_get_next(). /// // todo: vector to array // array to vector // vector to vector - done // array to array o2_arg_ptr o2_get_next(o2_type to_type) { o2_arg_ptr rslt = (o2_arg_ptr) mx_data_next; if (mx_type_next >= mx_barrier) return NULL; // overrun if (*mx_type_next == 0) return NULL; // no more args, end of type string if (mx_vector_to_vector_pending) { mx_vector_to_vector_pending = FALSE; // returns pointer to a vector descriptor with typ, len, and vector // address; this descriptor is always allocated in o2_arg_data // mx_data_next points to vector in message // allowed types for target are i, h, f, t, d rslt = ARG_NEXT; ARG_DATA_USED(o2_arg); // get pointer to the vector (pointed to type doesn't actually matter) // so this code is common to all the different type cases if (to_type == *mx_type_next) { rslt->v.vi = (int32_t *) mx_data_next; } else { rslt->v.vi = (int32_t *) ARG_NEXT; } if (mx_data_next + rslt->v.len > mx_barrier) { mx_vector_to_vector_pending = FALSE; return NULL; // bad message } switch (*mx_type_next++) { // switch on actual (in message) type case O2_INT32: rslt->v.len >>= 2; // byte count / 4 if (to_type != O2_INT32) { for (int i = 0; i < rslt->v.len; i++) { if (!convert_int(to_type, MX_INT32, sizeof(int32_t))) return NULL; mx_data_next += sizeof(int32_t); } } else { MX_SKIP(sizeof(int32_t) * rslt->v.len); } break; case O2_INT64: rslt->v.len >>= 3; // byte count / 8 if (to_type != O2_INT64) { // we'll need len int64s of free space for (int i = 0; i < rslt->v.len; i++) { if (!convert_int(to_type, MX_INT64, sizeof(int32_t))) return NULL; mx_data_next += sizeof(int64_t); } } else { MX_SKIP(sizeof(int64_t) * rslt->v.len); } break; case O2_FLOAT: rslt->v.len >>= 2; // byte count / 4 if (to_type != O2_FLOAT) { for (int i = 0; i < rslt->v.len; i++) { if (!convert_float(to_type, MX_FLOAT, sizeof(float))) return NULL; mx_data_next += sizeof(float); } } else { MX_SKIP(sizeof(float) * rslt->v.len); } break; case O2_DOUBLE: rslt->v.len >>= 3; // byte count / 8 if (to_type != O2_DOUBLE) { for (int i = 0; i < rslt->v.len; i++) { if (!convert_float(to_type, MX_DOUBLE, sizeof(double))) return NULL; mx_data_next += sizeof(double); } } else { MX_SKIP(sizeof(double) * rslt->v.len); } break; default: return NULL; break; } o2_argc--; // argv already has pointer to vector } else if (mx_vector_to_array) { // return vector elements as array elements if (to_type == O2_ARRAY_END) { if (mx_vector_remaining == 0) { rslt = o2_got_end_array; mx_vector_to_array = FALSE; } else { return NULL; } } else { int siz = ((mx_vector_to_array == 'h' || mx_vector_to_array == 'd') ? 8 : 4); mx_vector_remaining -= siz; if (mx_vector_remaining < 0) { return NULL; // perhaps message was invalid } } switch (mx_vector_to_array) { case O2_INT32: if (to_type != O2_INT32) { rslt = convert_int(to_type, MX_INT32, sizeof(int32_t)); } mx_data_next += sizeof(int32_t); break; case O2_INT64: if (to_type != O2_INT64) { rslt = convert_int(to_type, MX_INT64, sizeof(int64_t)); } mx_data_next += sizeof(int64_t); break; case O2_FLOAT: if (to_type != O2_FLOAT) { rslt = convert_float(to_type, MX_FLOAT, sizeof(float)); } mx_data_next += sizeof(float); break; case O2_DOUBLE: if (to_type != O2_DOUBLE) { rslt = convert_float(to_type, MX_DOUBLE, sizeof(double)); } mx_data_next += sizeof(double); break; default: // this happens when we reach the end of the vector break; } if (mx_data_next > mx_barrier) { mx_vector_to_array = FALSE; return NULL; // badly formatted message } } else if (mx_array_to_vector_pending) { // to_type is desired vector type // array types are in mx_type_next rslt = ((o2_arg_ptr) ARG_NEXT) - 1; // already allocated the vector header o2_argv_data.length--; // "vi" should get just one element in the arg vector // we already added one and will add another (below), so decrement length // so that rslt will not be written to a new location // of size sizeof(o2_arg), so -1 gets us back to the address of the header rslt->v.vi = (int32_t *) ARG_NEXT; rslt->v.typ = to_type; // now we know what the element type will be while (*mx_type_next != O2_ARRAY_END) { switch (*mx_type_next++) { case O2_INT32: convert_int(to_type, MX_INT32, sizeof(int32_t)); mx_data_next += sizeof(int32_t); break; case O2_INT64: convert_int(to_type, MX_INT64, sizeof(int64_t)); mx_data_next += sizeof(int64_t); break; case O2_FLOAT: convert_float(to_type, MX_FLOAT, sizeof(float)); mx_data_next += sizeof(float); break; case O2_DOUBLE: convert_float(to_type, MX_DOUBLE, sizeof(double)); mx_data_next += sizeof(double); break; default: return NULL; // bad type string (no ']') or bad types } rslt->v.len++; if (mx_data_next > mx_barrier) { mx_array_to_vector_pending = FALSE; return NULL; // badly formatted message } } mx_array_to_vector_pending = FALSE; } else { o2_type type_code = *mx_type_next++; switch (type_code) { case O2_INT32: if (to_type != O2_INT32) { rslt = convert_int(to_type, MX_INT32, sizeof(int32_t)); } mx_data_next += sizeof(int32_t); break; case O2_TRUE: if (to_type != O2_TRUE) { rslt = convert_int(to_type, 1, sizeof(int32_t)); } break; case O2_FALSE: if (to_type != O2_TRUE) { rslt = convert_int(to_type, 0, sizeof(int32_t)); } break; case O2_BOOL: if (to_type != O2_BOOL) { rslt = convert_int(to_type, MX_INT32, sizeof(int32_t)); } mx_data_next += sizeof(int32_t); break; case O2_FLOAT: if (to_type != O2_FLOAT) { rslt = convert_float(to_type, MX_FLOAT, sizeof(float)); } mx_data_next += sizeof(float); break; case O2_SYMBOL: case O2_STRING: if (to_type != O2_SYMBOL && to_type != O2_STRING) { rslt = NULL; // type error } // otherwise the requested type is suitable MX_SKIP(strlen(mx_data_next) + 1); // add one for end-of-string break; case O2_CHAR: if (to_type != O2_CHAR) { rslt = NULL; } mx_data_next += sizeof(int32_t); // char stored as int32_t break; case O2_BLOB: if (to_type != O2_BLOB) { rslt = NULL; // type mismatch } MX_SKIP(sizeof(uint32_t) + rslt->b.size); break; case O2_INT64: if (to_type != O2_INT64) { rslt = convert_int(to_type, MX_INT64, sizeof(int64_t)); } mx_data_next += sizeof(int64_t); break; case O2_DOUBLE: case O2_TIME: if (to_type != O2_DOUBLE && to_type != O2_TIME) { rslt = convert_float(to_type, MX_DOUBLE, sizeof(double)); } // otherwise the requested type is suitable mx_data_next += sizeof(double); break; case O2_MIDI: if (to_type != O2_MIDI) { rslt = NULL; // type mismatch } MX_SKIP(4); break; case O2_NIL: case O2_INFINITUM: if (to_type != type_code) { rslt = NULL; } break; case O2_ARRAY_START: if (to_type == O2_ARRAY_START) { rslt = o2_got_start_array; } else if (to_type == O2_VECTOR) { // see if we can extract a vector next time // when we get an element type mx_array_to_vector_pending = TRUE; rslt = (o2_arg_ptr) ARG_NEXT; ARG_DATA_USED(o2_arg); // initially, the vector type is the type of the first element // in the array, or double if the array is empty rslt->v.typ = *mx_type_next; if (rslt->v.typ == ']') rslt->v.typ = 'd'; rslt->v.len = 0; // unkknown rslt->v.vi = NULL; // pointer to data is not valid yet } else { rslt = NULL; } break; case O2_ARRAY_END: if (to_type == O2_ARRAY_END) { rslt = o2_got_end_array; } else { rslt = NULL; } break; case O2_VECTOR: if (to_type == O2_ARRAY_START) { // extract the vector as array elements mx_vector_to_array = *mx_type_next++; mx_vector_remaining = rd_int32(); // assuming 'v' was followed by a type, we have a vector rslt = mx_vector_to_array ? o2_got_start_array : NULL; } else if (to_type == O2_VECTOR) { // next call to o2_get_next() will get special processing mx_vector_to_vector_pending = TRUE; rslt = (o2_arg_ptr) ARG_NEXT; // do not call ARG_DATA_USED() because we will get // this address again on next call to o2_get_next() rslt->v.typ = *mx_type_next; // do not increment mx_type_next because we will use // it again (and increment on next call to o2_get_next() rslt->v.len = rd_int32(); rslt->v.vi = NULL; // pointer to data is not valid yet } else { rslt = NULL; } break; default: // could be O2_ARRAY_END or EOS among others fprintf(stderr, "O2 warning: unhandled OSC type '%c'\n", type_code); return NULL; } if (mx_data_next > mx_barrier) { mx_data_next = mx_barrier; // which points to 4 zero bytes at end return NULL; // of the message } } // This is equivalent to DA_APPEND, but since we've already allocated the // space, we don't need to check for space, and it would be an error if // we did expand the space because pointers would be wrong o2_argv_data.length++; o2_argv[o2_argc++] = rslt; // o2_argv is o2_argv_data.array as o2_arg_ptr. return rslt; } // o2_msg_data_print_2 - print message as text to stdout // // It would be most convenient to use o2_extract_start() and o2_get_next() // here, but this would overwrite extracted parameters if called from a // message handler, so here we duplicate some code to pull parameters from // messages (although the code is simple since there's no coercion). // static void o2_msg_data_print_2(o2_msg_data_ptr msg, int tcp_flag) { int i; printf("%s @ %g", msg->address, msg->timestamp); if (tcp_flag >= 0) { printf(" by %s", tcp_flag ? "TCP" : "UDP"); } if (msg->timestamp > 0.0) { if (msg->timestamp > o2_global_now) { printf(" (now+%gs)", msg->timestamp - o2_global_now); } else { printf(" (%gs late)", o2_global_now - msg->timestamp); } } if (IS_BUNDLE(msg)) { FOR_EACH_EMBEDDED(msg, printf(" "); len = MSG_DATA_LENGTH(embedded)) return; } char *types = O2_MSG_TYPES(msg); int types_len = (int) strlen(types); char *data_next = WORD_ALIGN_PTR(types + types_len + 4); while (*types) { switch (*types) { case O2_INT32: printf(" %d", *((int32_t *) data_next)); data_next += sizeof(int32_t); break; case O2_FLOAT: printf(" %gf", *((float *) data_next)); data_next += sizeof(float); break; case O2_STRING: printf(" \"%s\"", data_next); data_next += o2_strsize(data_next); break; case O2_BLOB: { int size = *((int32_t *) data_next); data_next += sizeof(int32_t); if (size > 12) { printf(" (%d byte blob)", size); } else { printf(" ("); for (i = 0; i < size; i++) { if (i > 0) printf(" "); printf("%#02x", (unsigned char) (data_next[i])); } printf(")"); } data_next += ((size + 3) & ~3); break; } case O2_INT64: // note: gcc complained about %lld with int64_t: printf(" %lld", *((long long *) data_next)); data_next += sizeof(int64_t); break; case O2_DOUBLE: printf(" %g", *((double *) data_next)); data_next += sizeof(double); break; case O2_TIME: printf(" %gs", *((double *) data_next)); data_next += sizeof(double); break; case O2_SYMBOL: printf(" '%s", data_next); data_next += o2_strsize(data_next); break; case O2_CHAR: printf(" '%c'", *((int32_t *) data_next)); data_next += sizeof(int32_t); break; case O2_MIDI: printf(" 0) printf(" "); printf("0x%02x", data_next[i]); } printf(">"); data_next += 4; break; case O2_BOOL: printf(" %s", (*(int32_t *) data_next) ? "Bool:true" : "Bool:false"); data_next += sizeof(int32_t); break; case O2_TRUE: printf(" #T"); break; case O2_FALSE: printf(" #F"); break; case O2_NIL: printf(" Nil"); break; case O2_INFINITUM: printf(" Infinitum"); break; case O2_ARRAY_START: printf(" ["); break; case O2_ARRAY_END: printf(" ]"); break; case O2_VECTOR: { int len = *((int32_t *) data_next); data_next += sizeof(int32_t); printf(" <"); o2_type vtype = *types++; for (i = 0; i < len; i++) { if (i > 0) printf(" "); if (vtype == O2_INT32) { printf(" %d", *((int32_t *) data_next)); data_next += sizeof(int32_t); } else if (vtype == O2_INT64) { // note: gcc complains about %lld and int64_t printf(" %lld", *((long long *) data_next)); data_next += sizeof(int64_t); } else if (vtype == O2_FLOAT) { printf(" %gf", *((float *) data_next)); data_next += sizeof(float); } else if (vtype == O2_DOUBLE) { printf(" %g", *((double *) data_next)); data_next += sizeof(double); } // note: vector of O2_TIME is not valid } break; } default: printf(" O2 WARNING: unhandled type: %c\n", *types); break; } types++; } } void o2_message_print(o2_message_ptr msg) { o2_msg_data_print_2(&msg->data, msg->tcp_flag ? TRUE : FALSE); fflush(stdout); } void o2_msg_data_print(o2_msg_data_ptr msg) { o2_msg_data_print_2(msg, -1); fflush(stdout); } o2-1.0/src/o2_sched.c0000644000175000017500000001613013072261166014552 0ustar zmoelnigzmoelnig/* o2_sched.c -- scheduling */ /* Overview: There are two schedulers here: o2_gtsched, and o2_ltsched. They are identical, but one should use "real" local time, and the other should use synchronized clock time. There is no code here for smoothing the synchronized clock or making sure it does not go backward. (Well, maybe if it goes backward, nothing happens.) The algorithm is the "timing wheel" algorithm: times are quantized to "bins" of 10ms. These are "hashed" into a table using modulo arithmetic, so each time you poll for work, you linearly search bins for activity. Assuming you poll every 10ms or less, on average, you will only look into one bin. Each bin has a simple linked list of messages with timestamps. The lists are sorted in increasing time order. This means the insertion cost is O(N), but assuming messages are distributed evenly into the bins, the cost is reduced by a factor of SCHED_TABLE_LEN == 128. My guess is that few messages are actually scheduled, so in practice there should not be many collisions, e.g. the typical list length is 0 or 1, making this a constant-time insert algorithm. The dispatch time is O(1) because lists are sorted with earliest time first. You also have to look at 100 bins per second whether anything is scheduled or not, but if you call poll every 10ms or so, scanning bins is not an issue. If time jumps by d, there is O(d) cost to scan the bins. However, the time constant is small: scanning even 10s worth of bins only requires looking at 1000 bins. Two difficult issues are: (1) the floating point time can be in the middle of a bin, so we need to be careful not to dispatch messages in the future, and since there may be messages in the bin that were not dispatched on the previous poll, we have to begin each poll by reexamining the bin where we stopped in the previous poll. (2) if time jumps ahead by more than SCHED_TABLE_LEN, we'll "wrap around" when we scan for messages and might schedule something out of order. To avoid this, we detect jumps and dispatch in 1s increments (the table size is 1.28s) to schedule messages in time order. (See more comments on this below.) This code assumes message structures have a "next" field so that we can make a linked list of messages, and also a "time" field with the scheduled time. */ #include "ctype.h" #include "o2_internal.h" #include "o2_message.h" #include "o2_sched.h" #include "o2_clock.h" #include "o2_send.h" #define SCHED_BIN(time) ((int64_t) ((time) * 100)) #define SCHED_BIN_TO_INDEX(b) ((b) & (O2_SCHED_TABLE_LEN - 1)) // Get modulo #define SCHED_INDEX(t) (SCHED_BIN_TO_INDEX(SCHED_BIN(t) )) o2_sched o2_gtsched, o2_ltsched; o2_sched_ptr o2_active_sched = &o2_gtsched; int o2_gtsched_started = FALSE; // cannot use o2_gtsched until clock is in sync /* KEEP THIS FOR DEBUGGING void sched_debug_print(const char *msg, sched_ptr s) { printf("sched_debug_print from %s: s %p, last_bin %lld, last_time %g\n", msg, s, s->last_bin, s->last_time); for (int i = 0; i < SCHED_TABLE_LEN; i++) { for (o2_message_ptr m = s->table[i]; m; m = m->next) { printf(" %d: %p %s\n", i, m, m->path); } } printf("\n"); } */ void o2_sched_finish(o2_sched_ptr s) { for (int i = 0; i < O2_SCHED_TABLE_LEN; i++) { o2_message_list_free(s->table[i]); } o2_gtsched_started = FALSE; } void o2_sched_start(o2_sched_ptr s, o2_time start_time) { memset(s->table, 0, sizeof(s->table)); s->last_bin = SCHED_BIN(start_time); if (s == &o2_gtsched) { o2_gtsched_started = TRUE; } s->last_time = start_time; } void o2_sched_initialize() { // TODO: is start_time right? o2_sched_start(&o2_ltsched, o2_local_time()); o2_gtsched_started = FALSE; } /*DEBUG int scheduled_for(o2_sched_ptr s, double when) { for (int i = 0; i < O2_SCHED_TABLE_LEN; i++) { o2_message_ptr msg = s->table[i]; while (msg) { if (msg->data.timestamp == when) return TRUE; msg = msg->next; } } return FALSE; } DEBUG*/ // Schedule a message for a particular service. Assumes that the service is local. // Use o2_message_send() if you do not know if the service is local or not. // int o2_schedule(o2_sched_ptr s, o2_message_ptr m) { o2_time mt = m->data.timestamp; if (mt <= 0 || mt < s->last_time) { // it was probably a mistake to schedule the message when the timestamp // is not in the future, but we'll try a local delivery anyway o2_msg_data_deliver(&m->data, m->tcp_flag, NULL); o2_message_free(m); return O2_SUCCESS; } if (s == &o2_gtsched && !o2_gtsched_started) { // cannot schedule in the future until there is a valid clock o2_message_free(m); return O2_NO_CLOCK; } int64_t index = SCHED_INDEX(mt); o2_message_ptr *m_ptr = &(s->table[index]); // find insertion point in list so that messages are sorted while (*m_ptr && ((*m_ptr)->data.timestamp <= mt)) { m_ptr = &((*m_ptr)->next); } // either *m_ptr is null or it points to a time > mt m->next = *m_ptr; *m_ptr = m; // assert(scheduled_for(s, m->data.timestamp)); return O2_SUCCESS; } // This looks for messages <= now and delivers them // static void sched_dispatch(o2_sched_ptr s, o2_time run_until_time) { // examine slots between last_bin and bin, inclusive // this is tricky: if time has advanced more than SCHED_TABLE_LEN, // then we'll "wrap around" the table and if nothing is done to // stop it, messages could be dispatched not in time order. We'll // detect the wrap-around and advance time 1s at a time to avoid // the problem. while (s->last_time + 1 < run_until_time) { sched_dispatch(s, s->last_time + 1); } int64_t bin = SCHED_BIN(run_until_time); // now we know that we have less than 1s to go to catch up, so the // table will not wrap around while (s->last_bin <= bin) { o2_message_ptr *m_ptr = &(s->table[SCHED_BIN_TO_INDEX(s->last_bin)]); while (*m_ptr && ((*m_ptr)->data.timestamp <= run_until_time)) { o2_message_ptr m = *m_ptr; *m_ptr = m->next; // unlink message m o2_active_sched = s; // if we recursively schedule another message, // use this same scheduler. // careful: this can call schedule and change the table O2_DBt(if (m->data.address[1] != '_' && !isdigit(m->data.address[1])) o2_dbg_msg("sched_dispatch", &m->data, NULL, NULL)); O2_DBT(if (m->data.address[1] == '_' || isdigit(m->data.address[1])) o2_dbg_msg("sched_dispatch", &m->data, NULL, NULL)); o2_message_send_sched(m, FALSE); // don't assume local and call // o2_msg_data_deliver; maybe this is an OSC message } s->last_bin++; } s->last_bin--; // we should revisit this bin next time s->last_time = run_until_time; } // call this periodically void o2_sched_poll() { sched_dispatch(&o2_ltsched, o2_local_now); if (o2_gtsched_started) { sched_dispatch(&o2_gtsched, o2_global_now); } } o2-1.0/src/o2_dynamic.c0000644000175000017500000000071713072261166015114 0ustar zmoelnigzmoelnig/* o2_dynamic.c -- generic dynamic arrays */ #include "assert.h" #include "o2.h" #include "string.h" #include "o2_dynamic.h" void o2_da_expand(dyn_array_ptr array, int siz) { if (array->allocated > 0) array->allocated *= 2; else array->allocated = 1; void *bigger = O2_MALLOC(array->allocated * siz); assert(bigger); memcpy(bigger, array->array, array->length * siz); if (array->array) O2_FREE(array->array); array->array = bigger; } o2-1.0/src/o2_send.c0000644000175000017500000002102313072261166014412 0ustar zmoelnigzmoelnig// // o2_send.c // O2 // // Created by 弛张 on 2/4/16. // Copyright © 2016 弛张. All rights reserved. // #include "ctype.h" #include "o2_internal.h" #include "o2_send.h" #include "o2_message.h" #include "o2_interoperation.h" #include "o2_discovery.h" #include // to prevent deep recursion, messages go into a queue if we are already // delivering a message via o2_msg_data_deliver: static int in_find_and_call_handlers = FALSE; static o2_message_ptr pending_head = NULL; static o2_message_ptr pending_tail = NULL; void o2_deliver_pending() { while (pending_head) { o2_message_ptr msg = pending_head; if (pending_head == pending_tail) { pending_head = pending_tail = NULL; } else { pending_head = pending_head->next; } o2_message_send_sched(msg, TRUE); } } /*o2string o2_key_pad(char *padded, const char *key) { int i; for (i = 0; i < MAX_SERVICE_LEN; i++) { char c = (padded[i] = key[i]); if (c == '/') { padded[i] = 0; break; } else if (c == 0) { break; } } // make sure final word is padded to word boundary (or else hash // computation might be corrupted by stray bytes padded[i++] = 0; padded[i++] = 0; padded[i++] = 0; padded[i++] = 0; return padded; } */ /* prereq: service_name does not contain '/' */ services_entry_ptr *o2_services_find(const char *service_name) { // all callers are passing in (possibly) unaligned strings, so we // need to copy the service_name to aligned storage and pad it char key[NAME_BUF_LEN]; o2_string_pad(key, service_name); return (services_entry_ptr *) o2_lookup(&o2_path_tree, key); } o2_info_ptr o2_msg_service(o2_msg_data_ptr msg) { char *service_name = msg->address + 1; char *slash = strchr(service_name, '/'); if (slash) *slash = 0; o2_info_ptr rslt = o2_service_find(service_name); if (slash) *slash = '/'; return rslt; } /* prereq: service_name does not contain '/' */ o2_info_ptr o2_service_find(const char *service_name) { services_entry_ptr services = *o2_services_find(service_name); if (!services) return NULL; assert(services->services.length > 0); return GET_SERVICE(services->services, 0); } // This function is invoked by macros o2_send and o2_send_cmd. // It expects arguments to end with O2_MARKER_A and O2_MARKER_B int o2_send_marker(const char *path, double time, int tcp_flag, const char *typestring, ...) { va_list ap; va_start(ap, typestring); o2_message_ptr msg; int rslt = o2_message_build(&msg, time, NULL, path, typestring, tcp_flag, ap); #ifndef O2_NO_DEBUGGING if (o2_debug & // either non-system (s) or system (S) mask (msg->data.address[1] != '_' && !isdigit(msg->data.address[1]) ? O2_DBs_FLAG : O2_DBS_FLAG)) { printf("O2: sending%s ", (tcp_flag ? " cmd" : "")); o2_msg_data_print(&(msg->data)); printf("\n"); } #endif if (rslt != O2_SUCCESS) { return rslt; // could not allocate a message! } return o2_message_send_sched(msg, TRUE); } // This is the externally visible message send function. // int o2_message_send(o2_message_ptr msg) { return o2_message_send_sched(msg, TRUE); } // Internal message send function. // schedulable is normally TRUE meaning we can schedule messages // according to their timestamps. If this message was dispatched // by o2_ltsched, schedulable will be FALSE and we should ignore // the timestamp, which has already been observed by o2_ltsched. // int o2_message_send_sched(o2_message_ptr msg, int schedulable) { // Find the remote service, note that we skip over the leading '/': o2_info_ptr service = o2_msg_service(&msg->data); if (!service) { o2_message_free(msg); return O2_FAIL; } else if (service->tag == TCP_SOCKET) { // remote delivery? o2_send_remote(&msg->data, msg->tcp_flag, (process_info_ptr) service); o2_message_free(msg); } else if (service->tag == OSC_REMOTE_SERVICE) { // this is a bit complicated: send immediately if it is a bundle // or is not scheduled in the future. Otherwise use O2 scheduling. if (!schedulable || IS_BUNDLE(&msg->data) || msg->data.timestamp == 0.0 || msg->data.timestamp <= o2_gtsched.last_time) { o2_send_osc((osc_info_ptr) service, &msg->data); o2_message_free(msg); } else { return o2_schedule(&o2_gtsched, msg); // delivery on time } } else if (schedulable && msg->data.timestamp > 0.0 && msg->data.timestamp > o2_gtsched.last_time) { // local delivery return o2_schedule(&o2_gtsched, msg); // local delivery later } else if (in_find_and_call_handlers) { if (pending_tail) { pending_tail->next = msg; pending_tail = msg; } else { pending_head = pending_tail = msg; } } else { in_find_and_call_handlers = TRUE; o2_msg_data_deliver(&msg->data, msg->tcp_flag, service); o2_message_free(msg); in_find_and_call_handlers = FALSE; } return O2_SUCCESS; } // deliver msg_data; similar to o2_message_send but local future // delivery requires the creation of an o2_message int o2_msg_data_send(o2_msg_data_ptr msg, int tcp_flag) { o2_info_ptr service = o2_msg_service(msg); if (!service) return O2_FAIL; if (service->tag == TCP_SOCKET) { return o2_send_remote(msg, tcp_flag, (process_info_ptr) service); } else if (service->tag == OSC_REMOTE_SERVICE) { if (IS_BUNDLE(msg) || (msg->timestamp == 0.0 || msg->timestamp <= o2_gtsched.last_time)) { return o2_send_osc((osc_info_ptr) service, msg); } } else if (msg->timestamp == 0.0 || msg->timestamp <= o2_gtsched.last_time) { o2_msg_data_deliver(msg, tcp_flag, service); return O2_SUCCESS; } // need to schedule o2_msg_data, so we need to copy to an o2_message int len = MSG_DATA_LENGTH(msg); o2_message_ptr message = o2_alloc_size_message(len); memcpy((char *) &(message->data), msg, len); message->length = len; return o2_schedule(&o2_gtsched, message); } int o2_send_remote(o2_msg_data_ptr msg, int tcp_flag, process_info_ptr info) { // send the message to remote process if (tcp_flag) { return send_by_tcp_to_process(info, msg); } else { // send via UDP O2_DBs(if (msg->address[1] != '_' && !isdigit(msg->address[1])) o2_dbg_msg("sent UDP", msg, "to", info->proc.name)); O2_DBS(if (msg->address[1] == '_' || isdigit(msg->address[1])) o2_dbg_msg("sent UDP", msg, "to", info->proc.name)); #if IS_LITTLE_ENDIAN o2_msg_swap_endian(msg, TRUE); #endif if (sendto(local_send_sock, (char *) msg, MSG_DATA_LENGTH(msg), 0, (struct sockaddr *) &(info->proc.udp_sa), sizeof(info->proc.udp_sa)) < 0) { perror("o2_send_remote"); return O2_FAIL; } } return O2_SUCCESS; } // Note: the message is converted to network byte order. Free the // message after calling this. int send_by_tcp_to_process(process_info_ptr info, o2_msg_data_ptr msg) { O2_DBs(if (msg->address[1] != '_' && !isdigit(msg->address[1])) o2_dbg_msg("sending TCP", msg, "to", info->proc.name)); O2_DBS(if (msg->address[1] == '_' || isdigit(msg->address[1])) o2_dbg_msg("sending TCP", msg, "to", info->proc.name)); #if IS_LITTLE_ENDIAN o2_msg_swap_endian(msg, TRUE); #endif // Send the length of the message followed by the message. // We want to do this in one send; otherwise, we'll send 2 // network packets due to the NODELAY socket option. int32_t len = MSG_DATA_LENGTH(msg); MSG_DATA_LENGTH(msg) = htonl(len); SOCKET fd = DA_GET(o2_fds, struct pollfd, info->fds_index)->fd; if (send(fd, (char *) &MSG_DATA_LENGTH(msg), len + sizeof(int32_t), MSG_NOSIGNAL) < 0) { if (errno != EAGAIN && errno != EINTR) { O2_DBo(printf("%s removing remote process after send error to socket %ld", o2_debug_prefix, (long) fd)); o2_remove_remote_process(info); } else { perror("send_by_tcp_to_process"); } return O2_FAIL; } // restore len just in case caller needs it to skip over the // message, which has now been byte-swapped and should not be read MSG_DATA_LENGTH(msg) = len; return O2_SUCCESS; } o2-1.0/src/o2.c0000644000175000017500000006314713072261166013416 0ustar zmoelnigzmoelnig// // O2.c // O2 // // Created by 弛张 on 1/26/16. // Copyright © 2016 弛张. All rights reserved. // /* Design Notes ============ service o2_fds o2_fds_info names +----+ +---------+ +---------+ ______ | | | -+->| process +-->|______| |----| |---------| | info | |______| | | | | +-----^---+ |----| |---------| | | | | | | +----+ +---------+ | | o2_path_tree | +----------+ +--------+ | | -+--->|services+--+ +----------+ | entry | | -+-> | | +--------+ +----------+ | -+-->|(local)-+---> etc +--------+ | node | +---------+ | entry |->| handler | +--------+ | entry | +---------+ Note: o2_path_tree is a node_entry (hash table) node_entry (hash table) entries can be: node_entry - the next node in a path handler_entry - handler at end of path services_entry - in o2_path_tree only, a list of services of the same name, the highest IP:Port string overrides any others. o2_full_path_table +----------+ +--------+ | -+------------>| handler| +----------+ +--------+ | node | | -+->| handler| +--------+ +----------+ | node | +--------+ Each application has: o2_path_tree - a dictionary mapping service names to a services_entry, which keeps a list of who offers the service. Only the highest IP:port string (lexicographically) is valid. Generally, trying to offer identical service names from multiple processes is a bad idea, and note that until the true provider with the highest IP:port string is discovered, messages may be sent to a different service with the same name. Each services_entry has an array (not a hash table) of entries of the following types: node_entry: a local service. This is the root of a tree of hash tables where leaves are handler_entry's. handler_entry: a local service. If there is a handler_entry at this level, it is the single handler for all messages to this local service. remote_service_entry: includes index of the socket and IP:port name of the service provider osc_entry: delegates to an OSC server. For the purposes of finding the highest IP:port string, this is considered to be a local service. A services_entry can have at most one of node_entry, handler_entry, osc_entry, but any number of remote_service_entry's. The first element in the array of entries in a service_entry is the "active" service -- the one with the highest IP:port string. Other elements are not sorted, so when a service is removed, a linear search to find the largest remaining offering (if any) is performed. The o2_path_tree also maps IP addresses + ports (as strings that begin with a digit and have the form 128.2.100.120:4000) to a services_entry that contains one remote_service_entry. o2_full_path_table is a dictionary for full paths, permitting a single hash table lookup for addresses of the form !synth/lfo/freq. In practice an additional lookup of just the service name is required to determine if the service is local and if there is a single handler for all messages to that service. Each handler object is referenced by some node in the o2_path_tree and by the o2_full_path_table dictionary, except for handlers that handle all service messages. These are only referenced by the o2_path_tree. o2_fds is a dynamic array of sockets for poll o2_fds_info is a parallel dynamic array of pointers to process info process_info includes a tag: UDP_SOCKET is for all incoming UDP messages TCP_SOCKET makes a TCP connection to a remote process DISCOVER_SOCKET is a UDP socket for incoming discovery messages TCP_SERVER_SOCKET is the server socket, representing local process OSC_SOCKET is for incoming OSC messages via UDP OSC_TCP_SERVER_SOCKET is a server socket for OSC TCP connections OSC_TCP_SOCKET is for incoming OSC messages via TCP OSC_TCP_CLIENT is for outgoing OSC messages via TCP All process info records contain an index into o2_fds (and o2_fds_info and the index must be updated if a socket is moved.) If the tag is TCP_SOCKET or TCP_SERVER_SOCKET, fields are: name - the ip address:port number used as a service name status - PROCESS_DISCOVERED through PROCESS_OK services - an dynamic array of local service names udp_sa - a sockaddr_in structure for sending udp to this process If the tag is OSC_SOCKET or OSC_TCP_SERVER_SOCKET or OSC_TCP_SOCKET, osc_service_name - name of the service to forward to Sockets ------- o2_fds_info has state to receive messages. When a message is received, there is a handler function that is called to process the message. For outgoing O2 messages, we have an associated process to tell where to send. For incoming O2 messages, no extra info is needed; just deliver the message. For incoming OSC messages, fds_info has the process with the osc_service_name where messages are redirected. Discovery --------- Discovery messages are sent to discovery ports. Discovery ports come from a list of unassigned port numbers. Every process opens the first port on the list that is available as a receive port. The list is the same for every process, so each processes knows what ports to send to. The only question is how many of up to 16 discovery ports do we need to send discovery messages to in order to reach everyone? The answer is that if we receive on the Nth port in the list, we transmit to ports 1 through N. For any pair of processes that are receiving on ports M and N, respectively, assume, without loss of generality, that M > N. Since the first process sends to ports 1 through M, it will send to port N and discovery will happen. The second process will not send to port M, but the discovery protocol only relies on discovery happening in one direction. Once a discovery message is received, in either direction, a TCP connection is established for two-way communication. The address for discovery messages is !_o2/dy, and the arguments are: application name (string) local ip (string) tcp (int32) discovery port (int32) When a discovery is made, a TCP connection is made. When a TCP connection is connected or accepted, the process sends the UDP port number and a list of services to !_o2/sv. Process Creation ---------------- o2_discovery_handler() receives !_o2/dy message. There are two cases based on whether the local host is the server or client. The server is the host with the greater IP:Port string. The client connects to the server. If the server gets a discovery message from the client, it can't connect because it's the server, so it merely generates an !_o2/dy message to the client to prompt the client to connect. Info for each process is stored in fds_info, which has one entry per socket. Most sockets are TCP sockets and the associated process info pointed to by fds_info represents a remote process. However, the info associated with the TCP server port (every process has one of these) represents the local process and is created when O2 is initialized. There are a few other sockets: discovery socket, UDP send socket, UDP receive socket, and OSC sockets (if created by the user). The message sequence is: Client broadcasts /dy (discovery) to all, including server. This triggers the sending of the next message, but the next message is also sent periodically. Either way, the next message is sent either to the discovery port or to the UDP port. Server sends /dy (discovery) to client's discovery or UDP port. Client receives /dy and replies with: Client connect()'s to server, creating TCP socket. The server status is set to PROCESS_CONNECTED Client sends /in (initialization) to server using the TCP socket. Client sends /sv (services) to server using the TCP socket. Locally, the client creates a service named "IP:port" representing the server so that if another /dy message arrives, the client will not make another connection. Server accepts connect request, creating TCP socket. The TCP socket is labeled with status PROCESS_CONNECTED. Server sends /in (initialization) to client using the TCP socket. Locally, the server creates an O2 service named "IP:port" representing the client so that if another /dy message arrives, the server will not make another connection. Server sends /sv (services) to client using the TCP socket. The /in message updates the status of the remote process to PROCESS_NO_CLOCK or PROCESS_OK. The latter is obtained only when both processes have clock sync. Services and Processes ---------------------- A process includes a list of services (strings). Each of these services is mapped by o2_path_tree to a services_entry that contains a remote_service_entry, which has an integer index of the corresponding remote process (both the socket in the fds array and the process info in the fds_info array). When sockets are closed, the last socket in fds is moved to fill the void and fds is shortened. The corresponding fds_info element has to be copied as well, which means that remote_service_entry's now index the wrong location. However, each process has an array of service names, so we look up each service name, find the remote_service_entry, and update the index to fds (and fds_info). The list of service names is also used to remove services when the socket connecting to the process is closed. Remote Process Name: Allocation, References, Freeing ---------------------------------------------------- Each remote process has a name, e.g. "128.2.1.100:55765" that can be used as a service name in an O2 address, e.g. to announce a new local service to that remote process. The name is on the heap and is "owned" by the process_info record associated with the TCP_SOCKET socket for remote processes. Also, the local process has its name owned by the TCP_SERVER socket. The process name is *copied* and used as the key for a service_entry_ptr to represent the service in the o2_path_tree. The process name is freed by o2_remove_remote_process(). OSC Service Name: Allocation, References, Freeing ------------------------------------------------- o2_osc_port_new() creates a tcp service or incoming udp port for OSC messages that are redirected to an O2 service. The service_entry record "owns" the servier name which is on the heap. This name is shared by osc.service_name in the osc_info record. For UDP, there are no other references, and the osc.service_name is not freed when the UDP socket is removed (unless this is the last provider of the service, in which case the service_entry record and is removed and its key is freed). For TCP, the osc.service_name is *copied* to the OSC_TCP_SERVER_SOCKET and shared with any OSC_TCP_SOCKET that was accepted from the server socket. These sockets and their shared osc service name are freed when the OSC service is removed. PATTERN_NODE and PATTERN_HANDLER keys at top level ---------------------------------------------------- If a node_entry (tag == PATTERN_NODE) or handler_entry (tag == PATTERN_HANDLER) exists as a service provider, i.e. in the services list of a services_entry record, the key field is not really needed because the service lookup takes you to the services list, and the first element there offers the service. E.g. if there's a (local) handler for /service1/bar, then we lookup "service1" in the o2_path_tree, which takes us to a services_entry, the first element on its services list should be a node_entry. There, we do a hash lookup of "bar" to get to a handler_entry. The key is set to NULL for node_entry and handler_entry records that represent services and are directly pointed to from a services list. Byte Order ---------- Messages are constructed and delivered in local host byte order. When messages are sent to another process, the bytes are swapped if necessary to be in network byte order. Messages arriving by TCP or UDP are therefore in network byte order and are converted to host byte order if necessary. */ /** * TO DO: * see each error return has the right error code * tests that generate error messages */ #include #include "o2_internal.h" #include "o2_discovery.h" #include "o2_message.h" #include "o2_send.h" #include "o2_sched.h" #include "o2_clock.h" #ifndef WIN32 #include #endif #ifndef O2_NO_DEBUG char *o2_debug_prefix = "O2:"; int o2_debug = 0; void o2_debug_flags(const char *flags) { o2_debug = 0; if (strchr(flags, 'c')) o2_debug |= O2_DBc_FLAG; if (strchr(flags, 'r')) o2_debug |= O2_DBr_FLAG; if (strchr(flags, 's')) o2_debug |= O2_DBs_FLAG; if (strchr(flags, 'R')) o2_debug |= O2_DBR_FLAG; if (strchr(flags, 'S')) o2_debug |= O2_DBS_FLAG; if (strchr(flags, 'k')) o2_debug |= O2_DBk_FLAG; if (strchr(flags, 'd')) o2_debug |= O2_DBd_FLAG; if (strchr(flags, 't')) o2_debug |= O2_DBt_FLAG; if (strchr(flags, 'T')) o2_debug |= O2_DBT_FLAG; if (strchr(flags, 'm')) o2_debug |= O2_DBm_FLAG; if (strchr(flags, 'o')) o2_debug |= O2_DBo_FLAG; if (strchr(flags, 'O')) o2_debug |= O2_DBO_FLAG; if (strchr(flags, 'g')) o2_debug |= O2_DBg_FLAGS; if (strchr(flags, 'a')) o2_debug |= O2_DBa_FLAGS; } void o2_dbg_msg(const char *src, o2_msg_data_ptr msg, const char *extra_label, const char *extra_data) { printf("%s %s at %gs (local %gs)", o2_debug_prefix, src, o2_time_get(), o2_local_time()); if (extra_label) printf(" %s: %s ", extra_label, extra_data); printf("\n "); o2_msg_data_print(msg); printf("\n"); } #endif void *((*o2_malloc)(size_t size)) = &malloc; void ((*o2_free)(void *)) = &free; // also used to detect initialization: const char *o2_application_name = NULL; // these times are set when poll is called to avoid the need to // call o2_time_get() repeatedly o2_time o2_local_now = 0.0; o2_time o2_global_now = 0.0; #ifndef O2_NO_DEBUG void *o2_dbg_malloc(size_t size, char *file, int line) { O2_DBm(printf("%s malloc %lld in %s:%d", o2_debug_prefix, (long long) size, file, line)); fflush(stdout); void *obj = (*o2_malloc)(size); O2_DBm(printf(" -> %p\n", obj)); return obj; } void o2_dbg_free(void *obj, char *file, int line) { O2_DBm(printf("%s free in %s:%d <- %p\n", o2_debug_prefix, file, line, obj)); (*free)(obj); } /** * Similar to calloc, but this uses the malloc and free functions * provided to O2 through a call to o2_memory(). * @param[in] n The number of objects to allocate. * @param[in] size The size of each object in bytes. * * @return The address of newly allocated and zeroed memory, or NULL. */ void *o2_dbg_calloc(size_t n, size_t s, char *file, int line) { void *loc = o2_dbg_malloc(n * s, file, line); if (loc) { memset(loc, 0, n * s); } return loc; } #else void *o2_calloc(size_t n, size_t s) { void *loc = O2_MALLOC(n * s); if (loc) { memset(loc, 0, n * s); } return loc; } #endif int o2_initialize(const char *application_name) { int err; if (o2_application_name) return O2_ALREADY_RUNNING; if (!application_name) return O2_BAD_NAME; o2_argv_initialize(); // Initialize the hash tables o2_node_initialize(&o2_full_path_table, NULL); o2_node_initialize(&o2_path_tree, NULL); // Initialize the application name. o2_application_name = o2_heapify(application_name); if (!o2_application_name) { err = O2_NO_MEMORY; goto cleanup; } // Initialize discovery, tcp, and udp sockets. if ((err = o2_sockets_initialize())) goto cleanup; o2_service_new("_o2"); o2_method_new("/_o2/dy", "ssii", &o2_discovery_handler, NULL, FALSE, FALSE); // "/sv/" service messages are sent by tcp as ordinary O2 messages, so they // are addressed by full name (IP:PORT). We cannot call them /_o2/sv: char address[32]; o2_service_new(o2_process->proc.name); snprintf(address, 32, "/%s/sv", o2_process->proc.name); o2_method_new(address, NULL, &o2_services_handler, NULL, FALSE, FALSE); snprintf(address, 32, "/%s/cs/cs", o2_process->proc.name); o2_method_new(address, "s", &o2_clocksynced_handler, NULL, FALSE, FALSE); o2_method_new("/_o2/ds", NULL, &o2_discovery_send_handler, NULL, FALSE, FALSE); o2_time_initialize(); o2_sched_initialize(); o2_clock_initialize(); // start sending discovery messages o2_discovery_send_handler(NULL, "", NULL, 0, NULL); // start sending clock sync messages o2_ping_send_handler(NULL, "", NULL, 0, NULL); return O2_SUCCESS; cleanup: o2_finish(); return err; } int o2_memory(void *((*malloc)(size_t size)), void ((*free)(void *))) { o2_malloc = malloc; o2_free = free; return O2_SUCCESS; } o2_time o2_set_discovery_period(o2_time period) { o2_time old = o2_discovery_period; if (period < 0.1) period = 0.1; o2_discovery_period = period; return old; } void o2_notify_others(const char *service_name, int added) { // when we add or remove a service, we must tell all other // processes about it. To find all other processes, use the o2_fds_info // table since all but a few of the entries are connections to processes for (int i = 0; i < o2_fds_info.length; i++) { process_info_ptr info = GET_PROCESS(i); if (info->tag == TCP_SOCKET) { char address[32]; snprintf(address, 32, "!%s/sv", info->proc.name); o2_send_cmd(address, 0.0, "ssB", o2_process->proc.name, service_name, added); O2_DBd(printf("%s o2_notify_others sent %s to %s (%s)\n", o2_debug_prefix, service_name, info->proc.name, added ? "added" : "removed")); } } } /** find service in services offered by proc, if any */ o2_info_ptr o2_proc_service_find(process_info_ptr proc, services_entry_ptr *services) { if (!*services) return FALSE; for (int i = 0; i < (*services)->services.length; i++) { o2_info_ptr service = GET_SERVICE((*services)->services, i); if (service->tag == TCP_SOCKET) { if ((process_info_ptr) service == proc) { return service; } } else { // not TCP_SOCKET so must be local if (o2_process == proc) { return service; // local service already exists } } } return NULL; } /* adds a service provider - a service is added to the list of services in * a service_entry struct. * 1) create the service_entry struct if none exists * 2) put the service onto process's list of service names * 3) add new service to the list */ int o2_service_provider_new(o2string service_name, o2_info_ptr service, process_info_ptr process) { services_entry_ptr *services = (services_entry_ptr *) o2_lookup(&o2_path_tree, service_name); services_entry_ptr s; // 1) if no entry, create an empty one if (!*services) { s = O2_CALLOC(1, sizeof(services_entry)); s->tag = SERVICES; s->key = o2_heapify(service_name); s->next = NULL; DA_INIT(s->services, o2_entry_ptr, 1); o2_add_entry_at(&o2_path_tree, (o2_entry_ptr *) services, (o2_entry_ptr) s); } else { // if this service already exists for process, don't add it again if (o2_proc_service_find(process, services) != NULL) { return O2_SERVICE_EXISTS; } s = *services; } // Now we know it's safe to add a local service and we have a // place to put it // 2) add the service name to the process so we can enumerate // local services DA_APPEND(process->proc.services, o2string, s->key); // 3) find insert location: either at front or at back of services->services DA_EXPAND(s->services, o2_entry_ptr); int index = s->services.length - 1; if (index > 0) { // see if we should go first o2string our_ip_port = process->proc.name; // find the top entry o2_info_ptr top_entry = GET_SERVICE(s->services, 0); o2string top_ip_port = (top_entry->tag == TCP_SOCKET ? ((process_info_ptr) top_entry)->proc.name : o2_process->proc.name); if (strcmp(our_ip_port, top_ip_port) > 0) { DA_SET(s->services, o2_info_ptr, index, top_entry); index = 0; // put new service at the top of the list } } DA_SET(s->services, o2_info_ptr, index, service); // special case for osc: need service name if (service->tag == OSC_REMOTE_SERVICE) { ((osc_info_ptr) service)->service_name = s->key; } return O2_SUCCESS; } int o2_service_new(const char *service_name) { if (!o2_application_name) { return O2_NOT_INITIALIZED; } // find services_node if any char padded_name[NAME_BUF_LEN]; if (strchr(service_name, '/')) return O2_BAD_SERVICE_NAME; o2_string_pad(padded_name, service_name); node_entry_ptr node = o2_node_new(NULL); if (!node) return O2_FAIL; int rslt = o2_service_provider_new(padded_name, (o2_info_ptr) node, o2_process); if (rslt != O2_SUCCESS) { O2_FREE(node); return rslt; } o2_notify_others(padded_name, TRUE); return O2_SUCCESS; } static void check_messages() { for (int i = 0; i < O2_SCHED_TABLE_LEN; i++) { for (o2_message_ptr msg = o2_ltsched.table[i]; msg; msg = msg->next) { assert(msg->allocated >= msg->length); } } } int o2_poll() { check_messages(); o2_local_now = o2_local_time(); if (o2_gtsched_started) { o2_global_now = o2_local_to_global(o2_local_now); } else { o2_global_now = -1.0; } o2_sched_poll(); // deal with the timestamped message o2_recv(); // receive and dispatch messages o2_deliver_pending(); return O2_SUCCESS; } int o2_stop_flag = FALSE; #ifdef WIN32 #define usleep(x) Sleep((x)/1000) #endif int o2_run(int rate) { if (rate <= 0) rate = 1000; // poll about every ms int sleep_usec = 1000000 / rate; o2_stop_flag = FALSE; while (!o2_stop_flag) { o2_poll(); usleep(sleep_usec); } return O2_SUCCESS; } int o2_status(const char *service) { if (!service || !*service || strchr(service, '/') || strchr(service, '!')) return O2_BAD_SERVICE_NAME; o2_info_ptr entry = o2_service_find(service); if (!entry) return O2_FAIL; switch (entry->tag) { case TCP_SOCKET: { process_info_ptr info = (process_info_ptr) entry; if (o2_clock_is_synchronized && info->proc.status == PROCESS_OK) { return O2_REMOTE; } else { return O2_REMOTE_NOTIME; } } case PATTERN_NODE: case PATTERN_HANDLER: return (o2_clock_is_synchronized ? O2_LOCAL : O2_LOCAL_NOTIME); case O2_BRIDGE_SERVICE: default: return O2_FAIL; // not implemented yet case OSC_REMOTE_SERVICE: // no timestamp synchronization with OSC if (o2_clock_is_synchronized) { return O2_TO_OSC; } else { return O2_TO_OSC_NOTIME; } } } #ifdef WIN32 int gettimeofday(struct timeval * tp, struct timezone * tzp) { // Note: some broken versions only have 8 trailing zero's, the correct // epoch has 9 trailing zero's static const uint64_t EPOCH = ((uint64_t)116444736000000000ULL); SYSTEMTIME system_time; FILETIME file_time; uint64_t time; GetSystemTime(&system_time); SystemTimeToFileTime(&system_time, &file_time); time = ((uint64_t)file_time.dwLowDateTime); time += ((uint64_t)file_time.dwHighDateTime) << 32; tp->tv_sec = (long)((time - EPOCH) / 10000000L); tp->tv_usec = (long)(system_time.wMilliseconds * 1000); return 0; } #endif static char o2_error_msg[100]; static char *error_strings[] = { "O2_SUCCESS", "O2_FAIL", "O2_SERVICE_CONFLICT", "O2_NO_SERVICE", "O2_NO_MEMORY", "O2_ALREADY_RUNNING", "O2_BAD_NAME", "O2_BAD_TYPE", "O2_BAD_ARGS", "O2_TCP_HUP", "O2_HOSTNAME_TO_NETADDR_FAIL", "O2_TCP_CONNECT_FAIL", "O2_NO_CLOCK", "O2_NO_HANDLER", "O2_INVALID_MSG", "O2_SEND_FAIL" }; const char *o2_error_to_string(int i) { if (i < 1 && i >= O2_SEND_FAIL) { sprintf(o2_error_msg, "O2 error %s", error_strings[-i]); } else { sprintf(o2_error_msg, "O2 error, code is %d", i); } return o2_error_msg; } int o2_finish() { if (o2_socket_delete_flag) { // we were counting on o2_recv() to clean up some sockets, but // it hasn't been called o2_free_deleted_sockets(); } // Close all the sockets. for (int i = 0 ; i < o2_fds.length; i++) { o2_remove_remote_process(GET_PROCESS(i)); } o2_free_deleted_sockets(); // deletes process_info structs DA_FINISH(o2_fds); DA_FINISH(o2_fds_info); o2_node_finish(&o2_path_tree); o2_node_finish(&o2_full_path_table); o2_argv_finish(); o2_sched_finish(&o2_gtsched); o2_sched_finish(&o2_ltsched); o2_discovery_finish(); if (o2_application_name) O2_FREE((void *) o2_application_name); o2_application_name = NULL; return O2_SUCCESS; } o2-1.0/src/o2_send.h0000644000175000017500000000250513072261166014423 0ustar zmoelnigzmoelnig// // o2_send.h // O2 // // Created by 弛张 on 2/4/16. // Copyright © 2016 弛张. All rights reserved. // #ifndef o2_send_h #define o2_send_h // if MSG_NOSIGNAL is an option for send(), then use it; // otherwise, give it a legal innocuous value as a flag: #ifndef MSG_NOSIGNAL #define MSG_NOSIGNAL 0 #endif void o2_deliver_pending(); services_entry_ptr *o2_services_find(const char *service_name); o2_info_ptr o2_msg_service(o2_msg_data_ptr msg); /** * \brief Use initial part of an O2 address to find an o2_service using * a hash table lookup. * * @param name points to the service name (do not include the * initial '!' or '/' from the O2 address). * * @return The pointer to the service, tag may be TCP_SOCKET * (remote process), PATTERN_NODE (local service), * PATTERN_HANDLER (local service with single handler), * or OSC_REMOTE_SERVICE (redirect to OSC server), * or NULL if name is not found. */ o2_info_ptr o2_service_find(const char *name); int o2_message_send_sched(o2_message_ptr msg, int schedulable); int o2_msg_data_send(o2_msg_data_ptr msg, int tcp_flag); int o2_send_remote(o2_msg_data_ptr msg, int tcp_flag, process_info_ptr info); int send_by_tcp_to_process(process_info_ptr proc, o2_msg_data_ptr msg); #endif /* o2_send_h */ o2-1.0/src/o2_clock.h0000644000175000017500000000070513072261166014565 0ustar zmoelnigzmoelnig// o2_clock.h -- header for internally shared clock declarations void o2_time_initialize(); void o2_clocksynced_handler(o2_msg_data_ptr msg, const char *types, o2_arg_ptr *argv, int argc, void *user_data); void o2_ping_send_handler(o2_msg_data_ptr msg, const char *types, o2_arg_ptr *argv, int argc, void *user_data); void o2_clock_initialize(); o2_time o2_local_to_global(o2_time local); o2-1.0/src/o2_interoperation.c0000644000175000017500000003606513072261166016537 0ustar zmoelnigzmoelnig// // o2_interoperation.c // o2 // // Created by ĺź›ĺź on 3/31/16. // /* Design notes: * We handle incoming OSC ports using * o2_osc_port_new(service_name, port_num, tcp_flag), which puts an * entry in the fds_info table that says incoming OSC messages are * handled by the service_name (which may or may not be local). Thus, * when an OSC message arrives, we use the incoming data to construct * a full O2 message. We can use the OSC message length plus the * service name length plus a timestamp length (plus some padding) to * determine how much message space to allocate. Then, we can receive * the data with some offset allowing us to prepend the timestamp and * service name. Finally, we just send the message, resulting in * either a local dispatch or forwarding to an O2 service. * * We handle outgoing OSC messages using * o2_osc_delegate(service_name, ip, port_num), which puts an * entry in the top-level hash table with the OSC socket. */ #include "o2.h" #include "o2_internal.h" #include "o2_message.h" #include "o2_sched.h" #include "o2_send.h" #include "o2_interoperation.h" #include "errno.h" static o2_message_ptr osc_to_o2(int32_t len, char *oscmsg, o2string service); static uint64_t osc_time_offset = 0; uint64_t o2_osc_time_offset(uint64_t offset) { uint64_t old = osc_time_offset; osc_time_offset = offset; return old; } #define TWO32 4294967296.0 o2_time o2_time_from_osc(uint64_t osctime) { #if IS_LITTLE_ENDIAN osctime = swap64(osctime); // message is byte swapped #endif osctime -= osc_time_offset; return osctime / TWO32; } uint64_t o2_time_to_osc(o2_time o2time) { uint64_t osctime = (uint64_t) (o2time * TWO32); return osctime + osc_time_offset; } /* create a port to receive OSC messages. * Messages are directed to service_name. * The service is not created by this call, but if the service * does not exist when an OSC message arrives, the message will be * dropped. * * Algorithm: Add a socket, put service name in info */ int o2_osc_port_new(const char *service_name, int port_num, int tcp_flag) { process_info_ptr info; if (tcp_flag) { RETURN_IF_ERROR(o2_make_tcp_recv_socket(OSC_TCP_SERVER_SOCKET, port_num, &o2_osc_tcp_accept_handler, &info)); } else { RETURN_IF_ERROR(o2_make_udp_recv_socket(OSC_SOCKET, &port_num, &info)); } info->osc.service_name = o2_heapify(service_name); return O2_SUCCESS; } int o2_osc_port_free(int port_num) { int result = O2_FAIL; o2string service_name_copy = NULL; for (int i = 0; i < o2_fds_info.length; i++) { process_info_ptr info = GET_PROCESS(i); if ((info->tag == OSC_TCP_SERVER_SOCKET || info->tag == OSC_TCP_SOCKET || info->tag == OSC_SOCKET) && info->port == port_num) { // we need to delete the osc_service_name, but it is shared // by any OSC_TCP_SOCKET record, and it seems wrong for them // to get a dangling pointer, so we'll just remember the // string and free it after all the o2_fds_info records are // removed. if (info->osc.service_name) { assert(service_name_copy == NULL || service_name_copy == info->osc.service_name); service_name_copy = info->osc.service_name; info->osc.service_name = NULL; } o2_socket_mark_to_free(info); result = O2_SUCCESS; // actual found and removed a port } } if (service_name_copy) O2_FREE((void *)service_name_copy); return O2_SUCCESS; } // messages to this service are forwarded as OSC messages // does the service exist as a local service? If so, fail. // make an osc_info record for this delegation of service // if tcp_flag, make a tcp connection with tag OSC_TCP_SOCKET // and set the tcp_socket_info to the process_info_ptr you get // for the tcp connection. // if udp, then set the udp address info in the osc_info // add osc_info as the service int o2_osc_delegate(const char *service_name, const char *ip, int port_num, int tcp_flag) { if (!service_name || strchr(service_name, '/')) return O2_BAD_SERVICE_NAME; osc_info_ptr osc = (osc_info_ptr) O2_MALLOC(sizeof(osc_info)); osc->tag = OSC_REMOTE_SERVICE; char padded_name[NAME_BUF_LEN]; o2_string_pad(padded_name, service_name); int rslt = o2_service_provider_new(padded_name, (o2_info_ptr) osc, o2_process); if (rslt != O2_SUCCESS) { O2_FREE(osc); return rslt; } rslt = O2_SUCCESS; if (streql(ip, "")) ip = "localhost"; char port[24]; // can't overrun even with 64-bit int sprintf(port, "%d", port_num); struct addrinfo hints; memset(&hints, 0, sizeof(struct addrinfo)); hints.ai_family = PF_INET; struct addrinfo *aiptr = NULL; struct sockaddr_in remote_addr; osc->port = port_num; if (tcp_flag) { process_info_ptr info; RETURN_IF_ERROR(o2_make_tcp_recv_socket( OSC_TCP_CLIENT, 0, &o2_osc_delegate_handler, &info)); // make the connection hints.ai_socktype = SOCK_STREAM; hints.ai_protocol = IPPROTO_TCP; if (getaddrinfo(ip, port, &hints, &aiptr)) { goto hostname_to_netaddr_fail; } memcpy(&remote_addr, aiptr->ai_addr, sizeof(remote_addr)); remote_addr.sin_port = htons((short) port_num); SOCKET sock = DA_LAST(o2_fds, struct pollfd)->fd; osc->tcp_socket_info = info; if (connect(sock, (struct sockaddr *) &remote_addr, sizeof(remote_addr)) == -1) { perror("OSC Server connect error!"); o2_fds_info.length--; o2_fds.length--; rslt = O2_TCP_CONNECT_FAIL; O2_FREE(info); goto fail_and_exit; } info->osc.service_name = o2_heapify(service_name); o2_disable_sigpipe(sock); } else { hints.ai_socktype = SOCK_DGRAM; hints.ai_protocol = IPPROTO_UDP; if (getaddrinfo(ip, port, &hints, &aiptr)) { goto hostname_to_netaddr_fail; } memcpy(&remote_addr, aiptr->ai_addr, sizeof(remote_addr)); if (remote_addr.sin_port == 0) { remote_addr.sin_port = htons((short) port_num); } memcpy(&osc->udp_sa, &remote_addr, sizeof(remote_addr)); osc->tcp_socket_info = NULL; } rslt = O2_SUCCESS; goto just_exit; hostname_to_netaddr_fail: rslt = O2_HOSTNAME_TO_NETADDR_FAIL; fail_and_exit: O2_FREE(osc); just_exit: if (aiptr) freeaddrinfo(aiptr); return rslt; } static o2_message_ptr osc_bundle_to_o2(int32_t len, char *oscmsg, o2string service) { // osc bundle has the form #bundle, timestamp, messages // It is assumed that all embedded messages in an OSC bundle // are destined for the same service (info->osc_service_name). // Bundle translation is going to unpack and repack the embedded // messages: not the most efficient, but simpler. o2_time ts = o2_time_from_osc(*((uint64_t *) (oscmsg + 8))); char *end_of_msg = oscmsg + len; char *embedded = oscmsg + 20; // skip #bundle & timestamp & length o2_message_ptr o2msg = NULL; o2_message_ptr msg_list = NULL; o2_message_ptr last = NULL; while (embedded < end_of_msg) { int embedded_len = ((int32_t *) embedded)[-1]; #if IS_LITTLE_ENDIAN embedded_len = swap32(embedded_len); #endif if (PTR(embedded) + embedded_len <= end_of_msg) { o2msg = osc_to_o2(embedded_len, embedded, service); } if (!o2msg) { o2_message_list_free(msg_list); return NULL; } o2msg->next = NULL; // make sure link is initialized // remember embedded messages on list if (last == NULL) { // first element goes at head of list msg_list = o2msg; } else { last->next = o2msg; } last = o2msg; embedded += embedded_len + sizeof(int32_t); } // add each element to a message o2_send_start(); while (msg_list) { o2_message_ptr next = msg_list->next; o2_add_message(msg_list); o2_message_free(msg_list); msg_list = next; } return o2_service_message_finish(ts, service, "", TRUE); } // convert an osc message in network byte order to o2 message in host order static o2_message_ptr osc_to_o2(int32_t len, char *oscmsg, o2string service) { // osc message has the form: address, types, data // o2 message has the form: timestamp, address, types, data // o2 address must have a info->u.osc_service_name prefix // since we need more space, allocate a new message for o2 and // copy the data to it if (strcmp(oscmsg, "#bundle") == 0) { // it's a bundle return osc_bundle_to_o2(len, oscmsg, service); } else { // normal message int service_len = (int) strlen(service); // length in data part will be timestamp + slash (1) + service name + // o2 data; add another 7 bytes for padding after address int o2len = sizeof(double) + 8 + service_len + len; o2_message_ptr o2msg = o2_alloc_size_message(o2len); o2msg->data.timestamp = 0.0; // deliver immediately o2msg->data.address[0] = '/'; // slash before service name strcpy(o2msg->data.address + 1, service); // how many bytes in OSC address? int addr_len = (int) strlen(oscmsg); // compute address of byte after the O2 address string char *o2_ptr = o2msg->data.address + 1 + service_len; // zero fill to word boundary int32_t *fill_ptr = (int32_t *) WORD_ALIGN_PTR(o2_ptr + addr_len); *fill_ptr = 0; // copy in OSC address string, possibly overwriting some of the fill memcpy(o2_ptr, oscmsg, addr_len); o2_ptr = PTR(fill_ptr + 1); // get location after O2 address // copy type string and OSC message data char *osc_ptr = WORD_ALIGN_PTR(oscmsg + addr_len + 4); o2len = (int) (oscmsg + len - osc_ptr); // how much payload to copy memcpy(o2_ptr, osc_ptr, o2len); o2msg->length = (int32_t) (o2_ptr + o2len - PTR(&(o2msg->data))); #if IS_LITTLE_ENDIAN o2_msg_swap_endian(&(o2msg->data), FALSE); #endif return o2msg; } } // forward an OSC message to an O2 service int o2_deliver_osc(process_info_ptr info) { char *msg_data = (char *) &(info->message->data); // OSC address starts here O2_DBO(printf("%s os_deliver_osc got OSC message %s length %d for service %s\n", o2_debug_prefix, msg_data, info->message->length, info->osc.service_name)); o2_message_ptr o2msg = osc_to_o2(info->message->length, msg_data, info->osc.service_name); o2_message_free(info->message); if (!o2msg) { return O2_FAIL; } // if this came by UDP, tag is OSC_SOCKET, and tcp_flag should be false o2msg->tcp_flag = (info->tag != OSC_SOCKET); if (o2_message_send_sched(o2msg, TRUE)) { // failure to deliver message will NOT // cause the connection to be closed; only the current message // will be dropped O2_DBO(printf("%s os_deliver_osc: message %s forward to %s failed\n", o2_debug_prefix, msg_data, info->osc.service_name)); } return O2_SUCCESS; } // convert O2 message to OSC message which is appended to msg_data.array // for liblo compatibility, timestamps of embedded bundles are at least // as late as the containing, or parent, bundle's timestamp. // static int msg_data_to_osc_data(osc_info_ptr service, o2_msg_data_ptr msg, o2_time min_time) { // build new message in msg_data if (IS_BUNDLE(msg)) { if (msg->timestamp > min_time) min_time = msg->timestamp; o2_add_bundle_head(o2_time_to_osc(min_time)); /* FOR_EACH_EMBEDDED(msg, int32_t *len_ptr = o2_msg_len_ptr(); len = MSG_DATA_LENGTH(embedded); if ((PTR(embedded) + len > end_of_msg) || !msg_data_to_osc_data(service, embedded, mintime)) { return O2_FAIL; } o2_set_msg_length(len_ptr)) */ char *end_of_msg = PTR(msg) + MSG_DATA_LENGTH(msg); o2_msg_data_ptr embedded = (o2_msg_data_ptr) ((msg)->address + o2_strsize((msg)->address) + sizeof(int32_t)); while (PTR(embedded) < end_of_msg) { int32_t len; int32_t *len_ptr = o2_msg_len_ptr(); len = MSG_DATA_LENGTH(embedded); if ((PTR(embedded) + len > end_of_msg) || msg_data_to_osc_data(service, embedded, min_time) != O2_SUCCESS) { return O2_FAIL; } o2_set_msg_length(len_ptr); embedded = (o2_msg_data_ptr) (PTR(embedded) + len + sizeof(int32_t)); } } else { // Begin by converting to network byte order: #if IS_LITTLE_ENDIAN RETURN_IF_ERROR(o2_msg_swap_endian(msg, TRUE)); #endif // Copy address, eliminating service name prefix int service_len = (int) strlen(service->service_name) + 1; // include slash o2_add_string_or_symbol('s', msg->address + service_len); // Get the address of the rest of the message: char *types_ptr = msg->address + 4; while (types_ptr[-1]) types_ptr += 4; o2_add_raw_bytes((int32_t) (PTR(msg) + MSG_DATA_LENGTH(msg) - types_ptr), types_ptr); } return O2_SUCCESS; } // forward an O2 message to an OSC server int o2_send_osc(osc_info_ptr service, o2_msg_data_ptr msg) { o2_send_start(); RETURN_IF_ERROR(msg_data_to_osc_data(service, msg, 0.0)); int32_t osc_len; char *osc_msg = o2_msg_data_get(&osc_len); O2_DBO(printf("%s o2_send_osc sending OSC message %s length %d as " "service %s\n", o2_debug_prefix, osc_msg, osc_len, service->service_name)); O2_DBO(o2_dbg_msg("original O2 msg is", msg, NULL, NULL)); // Now we have an OSC message at msg->address. Send it. if (service->tcp_socket_info == NULL) { // must be UDP if (sendto(local_send_sock, osc_msg, osc_len, 0, (struct sockaddr *) &(service->udp_sa), sizeof(service->udp_sa)) < 0) { perror("o2_send_osc"); return O2_SEND_FAIL; } } else { // send by TCP SOCKET fd = DA_GET(o2_fds, struct pollfd, service->tcp_socket_info->fds_index)->fd; // send length int32_t len = htonl(osc_len); while (send(fd, (char *) &len, sizeof(int32_t), MSG_NOSIGNAL) < 0) { perror("o2_send_osc writing length"); if (errno != EAGAIN && errno != EINTR) goto close_socket; } // send message body while (send(fd, osc_msg, osc_len, MSG_NOSIGNAL) < 0) { perror("o2_send_osc writing data"); if (errno != EAGAIN && errno != EINTR) goto close_socket; } } return O2_SUCCESS; close_socket: o2_service_free((void *) service->service_name); return O2_FAIL; } o2-1.0/.gitignore0000644000175000017500000000012013072261166014111 0ustar zmoelnigzmoelnig*.cmake o2.xcodeproj/ o2.build/ CMakeFiles/ CMakeScripts/ Debug/ CMakeCache.txt o2-1.0/LICENSE0000644000175000017500000000206713072261166013142 0ustar zmoelnigzmoelnigThe MIT License (MIT) Copyright (c) 2016 rbdannenberg Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. o2-1.0/README.md0000644000175000017500000000142013072261166013404 0ustar zmoelnigzmoelnig# o2 O2 is a new communication protocol and implementation for music systems that aims to replace Open Sound Control (OSC). Many computer musicians routinely deal with problems of interconnection in local area networks, unreliable message delivery, and clock synchronization. O2 solves these problems, offering *named services*, automatic network *address discovery*, *clock synchronization*, and a *reliable message delivery option*, as well as *interoperability* with existing OSC libraries and applications. Aside from these new features, O2 owes much of its design to OSC and is mostly compatible with and similar to OSC. O2 addresses the problems of inter-process communication with a minimum of complexity. [O2 web pages with documentation](https://rbdannenberg.github.io/o2/) o2-1.0/CMakeLists.txt0000644000175000017500000002615213072261166014676 0ustar zmoelnigzmoelnigcmake_minimum_required(VERSION 2.8.0) project(o2) set(BUILD_TESTS ON CACHE BOOL "Compile regression and other tests") set(BUILD_TESTS_WITH_LIBLO OFF CACHE BOOL "Compile tests that use liblo, requiring liblo library (only enabled if BUILD_TESTS is ON)") set(BUILD_MIDI_EXAMPLE OFF CACHE BOOL "Compile midiclient & midiserver, requiring portmidi library") # O2 intentionally writes outside of declared array bounds (and # carefully insures that space is allocated beyond array bounds, # especially for message data, which is declared char[4], but can # be arbitrariily long -- well, at least up to O2_MAX_MSG_SIZE) # Newer compilers will try to enforce char[4] in memcpy and strcpy # unless we turn off this behavior with the following macro definition: add_definitions("-D_FORTIFY_SOURCE=0") if(WIN32) add_definitions("-D_CRT_SECURE_NO_WARNINGS -D_WINSOCK_DEPRECATED_NO_WARNINGS -DIS_BIG_ENDIAN=0") include(static.cmake) set(EXTRA_LIBS winmm.lib ws2_32.lib Iphlpapi.lib) endif(WIN32) if(APPLE) set(FRAMEWORK_PATH ${CMAKE_OSX_SYSROOT}/System/Library/Frameworks) set(EXTRA_LIBS "${FRAMEWORK_PATH}/CoreAudio.framework") endif(APPLE) #set(CMAKE_CXX_FLAGS "-stdlib=libc++") #set(CMAKE_EXE_LINKER_FLAGS "-stdlib=libc++") # o2 set(O2_SRC src/o2_dynamic.c src/o2_dynamic.h src/o2.c src/o2.h src/o2_internal.h src/o2_discovery.c src/o2_discovery.h src/o2_message.c src/o2_message.h src/o2_sched.c src/o2_sched.h src/o2_search.c src/o2_search.h src/o2_send.c src/o2_send.h src/o2_socket.c src/o2_socket.h src/o2_clock.c src/o2_clock.h # src/o2_debug.c src/o2_debug.h src/o2_interoperation.c src/o2_interoperation.h ) add_library(o2_static STATIC ${O2_SRC}) #target_include_directories(o2_static PRIVATE ${no include directories}) ####################### # BUILD CONFIGURATION # ####################### set(LIBRARIES o2_static ${EXTRA_LIBS} CACHE INTERNAL "") if(BUILD_TESTS) message(STATUS "Building test programs") set(BUILD_STATIC_LIB TRUE CACHE BOOL "Build a static lib -- currently the only thing supported, turning OFF probably gives you a static library without a static runtime library option.") # DEAL WITH WINDOWS OPTIONS: DEATH BY A THOUSAND CUTS if(WIN32) if(USE_STATIC_LIBS) # release will use static runtime library foreach(flag_var CMAKE_CXX_FLAGS_RELEASE CMAKE_C_FLAGS_RELEASE CMAKE_CXX_FLAGS CMAKE_CXX_FLAGS_MINSIZEREL CMAKE_CXX_FLAGS_RELWITHDEBINFO CMAKE_CFLAGS CMAKE_C_FLAGS_MINSIZEREL CMAKE_C_FLAGS_RELWITHDEBINFO) if(${flag_var} MATCHES "/MD") string(REGEX REPLACE "/MD" "/MT" ${flag_var} "${${flag_var}}") endif(${flag_var} MATCHES "/MD") endforeach(flag_var) message(STATUS "Note: overriding CMAKE_*_FLAGS_* to use Visual C static multithread library") set(VERBOSE_WARNINGS 0 CACHE BOOL "Do not use _CRT_SECURE_NO_WARNINGS to disable suggestions to use 'secure' versions of strcpy, etc.") endif(USE_STATIC_LIBS) endif(WIN32) # EXECUTABLE # Create executables add_executable(dispatchtest test/dispatchtest.c) target_include_directories(dispatchtest PRIVATE ${CMAKE_SOURCE_DIR}/src) target_link_libraries(dispatchtest ${LIBRARIES}) add_executable(typestest test/typestest.c) target_include_directories(typestest PRIVATE ${CMAKE_SOURCE_DIR}/src) target_link_libraries(typestest ${LIBRARIES}) add_executable(coercetest test/coercetest.c) target_include_directories(coercetest PRIVATE ${CMAKE_SOURCE_DIR}/src) target_link_libraries(coercetest ${LIBRARIES}) add_executable(longtest test/longtest.c) target_include_directories(longtest PRIVATE ${CMAKE_SOURCE_DIR}/src) target_link_libraries(longtest ${LIBRARIES}) add_executable(arraytest test/arraytest.c) target_include_directories(arraytest PRIVATE ${CMAKE_SOURCE_DIR}/src) target_link_libraries(arraytest ${LIBRARIES}) add_executable(o2client test/o2client.c) target_include_directories(o2client PRIVATE ${CMAKE_SOURCE_DIR}/src) target_link_libraries(o2client ${LIBRARIES}) add_executable(o2server test/o2server.c) target_include_directories(o2server PRIVATE ${CMAKE_SOURCE_DIR}/src) target_link_libraries(o2server ${LIBRARIES}) add_executable(statusclient test/statusclient.c) target_include_directories(statusclient PRIVATE ${CMAKE_SOURCE_DIR}/src) target_link_libraries(statusclient ${LIBRARIES}) add_executable(statusserver test/statusserver.c) target_include_directories(statusserver PRIVATE ${CMAKE_SOURCE_DIR}/src) target_link_libraries(statusserver ${LIBRARIES}) add_executable(tcpclient test/tcpclient.c) target_include_directories(tcpclient PRIVATE ${CMAKE_SOURCE_DIR}/src) target_link_libraries(tcpclient ${LIBRARIES}) add_executable(tcpserver test/tcpserver.c) target_include_directories(tcpserver PRIVATE ${CMAKE_SOURCE_DIR}/src) target_link_libraries(tcpserver ${LIBRARIES}) add_executable(clockslave test/clockslave.c) target_include_directories(clockslave PRIVATE ${CMAKE_SOURCE_DIR}/src) target_link_libraries(clockslave ${LIBRARIES}) add_executable(clockmaster test/clockmaster.c) target_include_directories(clockmaster PRIVATE ${CMAKE_SOURCE_DIR}/src) target_link_libraries(clockmaster ${LIBRARIES}) add_executable(oscsendtest test/oscsendtest.c) target_include_directories(oscsendtest PRIVATE ${CMAKE_SOURCE_DIR}/src) target_link_libraries(oscsendtest ${LIBRARIES}) add_executable(oscrecvtest test/oscrecvtest.c) target_include_directories(oscrecvtest PRIVATE ${CMAKE_SOURCE_DIR}/src) target_link_libraries(oscrecvtest ${LIBRARIES}) add_executable(oscanytest test/oscanytest.c) target_include_directories(oscanytest PRIVATE ${CMAKE_SOURCE_DIR}/src) target_link_libraries(oscanytest ${LIBRARIES}) add_executable(bundletest test/bundletest.c) target_include_directories(bundletest PRIVATE ${CMAKE_SOURCE_DIR}/src) target_link_libraries(bundletest ${LIBRARIES}) add_executable(oscbndlsend test/oscbndlsend.c) target_include_directories(oscbndlsend PRIVATE ${CMAKE_SOURCE_DIR}/src) target_link_libraries(oscbndlsend ${LIBRARIES}) add_executable(oscbndlrecv test/oscbndlrecv.c) target_include_directories(oscbndlrecv PRIVATE ${CMAKE_SOURCE_DIR}/src) target_link_libraries(oscbndlrecv ${LIBRARIES}) endif(BUILD_TESTS) if(UNIX) if(APPLE) else(APPLE) # linux set(CMAKE_C_FLAGS "-std=gnu99") endif(APPLE) if(BUILD_MIDI_EXAMPLE) # Use PortMidi Library set(PORTMIDI_DBG_LIB PORTMIDI_DBG_LIB-NOTFOUND) set(PORTMIDI_OPT_LIB PORTMIDI_OPT_LIB-NOTFOUND) if(APPLE) set(COREMIDI_LIB "${FRAMEWORK_PATH}/CoreMIDI.framework") set(EXTRA_NEEDED_LIBS ${COREAUDIO_LIB} ${COREFOUNDATION_LIB} ${COREMIDI_LIB} ${CORESERVICES_LIB}) else(APPLE) # LINUX set(PTHREAD_LIB pthread) set(EXTRA_NEEDED_LIBS asound ${PTHREAD_LIB}) endif(APPLE) set(PORTMIDI_BASE_PATH ../portmedia/portmidi CACHE STRING "Where is portmidi?") set(PORTMIDI_PATH ${PORTMIDI_BASE_PATH}/pm_common CACHE INTERNAL "Where is portmidi.h?" FORCE) message(STATUS "PORTMIDI_BASE_PATH is " ${PORTMIDI_BASE_PATH}) if(USE_STATIC_LIBS) if(UNIX) find_library(PORTMIDI_DBG_LIB portmidi_s ${PORTMIDI_BASE_PATH} ${PORTMIDI_BASE_PATH}/Debug ${PORTMIDI_PATH} ${PORTMIDI_PATH}/Debug) else(UNIX) # always use dll for windows debug find_library(PORTMIDI_DBG_LIB portmidi HINTS ${PORTMIDI_BASE_PATH} ${PORTMIDI_BASE_PATH}/Debug ${PORTMIDI_PATH} ${PORTMIDI_PATH}/Debug) endif(UNIX) message(STATUS "*** in USE_STATIC_LIBS, USE_MIDI ${USE_MIDI} PORTMIDI_DBG_LIB ${PORTMIDI_DBG_LIB}") else(USE_STATIC_LIBS) find_library(PORTMIDI_DBG_LIB portmidi HINTS ${PORTMIDI_BASE_PATH} ${PORTMIDI_BASE_PATH}/Debug ${PORTMIDI_BASE_PATH}/x64/Debug ${PORTMIDI_PATH} ${PORTMIDI_PATH}/Debug ${PORTMIDI_PATH}/x64/Debug) endif(USE_STATIC_LIBS) add_executable(midiclient test/midiclient.c test/cmtio.c test/cmtio.h) target_include_directories(midiclient PRIVATE ${CMAKE_SOURCE_DIR}/src) target_link_libraries(midiclient ${LIBRARIES}) add_executable(midiserver test/midiserver.c) target_include_directories(midiserver PRIVATE ${CMAKE_SOURCE_DIR}/src ${PORTMIDI_PATH} ${PORTMIDI_PATH}/../porttime) target_link_libraries(midiserver ${LIBRARIES} ${PORTMIDI_DBG_LIB}) endif(BUILD_MIDI_EXAMPLE) endif(UNIX) message(STATUS LIBRARIES=${LIBRARIES}) # this is some networking test code, not part of O2 #add_executable(broadcastclient test/broadcastclient.c) #add_executable(broadcastserver test/broadcastserver.c) #add_executable(tcppollclient test/tcppollclient.c) #add_executable(tcppollserver test/tcppollserver.c) # I don't know if this is necessary. If it is, it should be duplicated # for o2client and o2server: #if(WIN32) #set_target_properties(o2test PROPERTIES #LINK_FLAGS "/SUBSYSTEM:WINDOWS") #set_property(TARGET o2test PROPERTY WIN32_EXECUTABLE TRUE) #endif(WIN32) ########################################################## # liblo was used for some performance comparisons, but it # is disabled/commented out to remove the liblo path and # library dependencies from this CMakeLists.txt file ########################################################## if(BUILD_TESTS) if(BUILD_TESTS_WITH_LIBLO) set(LIBLO_PATH "${CMAKE_SOURCE_DIR}/../liblo-0.28" CACHE PATH "Where to find liblo_64s.a, the liblo library.") set(LIBLO_LIB LIBLO_LIB-NOTFOUND CACHE FILEPATH "The liblo library; should be set automatically if LIBLO_PATH is correct and liblo_64s.a exists.") find_library(LIBLO_LIB lo_s64 ${LIBLO_PATH}) # where to find liblo include files: set(LIBLO_INCLUDE_PATH ${LIBLO_PATH} CACHE PATH "where to find liblo include files") message(STATUS "LIBLO_LIB is ${LIBLO_LIB}") # Create executables add_executable(lo_benchmk_server test/lo_benchmk_server.c) target_include_directories(lo_benchmk_server PRIVATE ${LIBLO_INCLUDE_PATH}) target_link_libraries(lo_benchmk_server ${LIBLO_LIB} ${EXTRA_NEEDED_LIBS}) add_executable(lo_benchmk_client test/lo_benchmk_client.c) target_include_directories(lo_benchmk_client PRIVATE ${LIBLO_INCLUDE_PATH}) target_link_libraries(lo_benchmk_client ${LIBLO_LIB} ${EXTRA_NEEDED_LIBS}) add_executable(lo_oscrecv test/lo_oscrecv.c) target_include_directories(lo_oscrecv PRIVATE ${LIBLO_INCLUDE_PATH}) target_link_libraries(lo_oscrecv ${LIBLO_LIB} ${EXTRA_NEEDED_LIBS}) add_executable(lo_oscsend test/lo_oscsend.c) target_include_directories(lo_oscsend PRIVATE ${LIBLO_INCLUDE_PATH}) target_link_libraries(lo_oscsend ${LIBLO_LIB} ${EXTRA_NEEDED_LIBS}) add_executable(lo_bndlsend test/lo_bndlsend.c) target_include_directories(lo_bndlsend PRIVATE ${LIBLO_INCLUDE_PATH}) target_link_libraries(lo_bndlsend ${LIBLO_LIB} ${EXTRA_NEEDED_LIBS}) add_executable(lo_bndlrecv test/lo_bndlrecv.c) target_include_directories(lo_bndlrecv PRIVATE ${LIBLO_INCLUDE_PATH}) target_link_libraries(lo_bndlrecv ${LIBLO_LIB} ${EXTRA_NEEDED_LIBS}) endif(BUILD_TESTS_WITH_LIBLO) endif(BUILD_TESTS) o2-1.0/doc/0000755000175000017500000000000013072261166012675 5ustar zmoelnigzmoelnigo2-1.0/doc/logo.jpg0000644000175000017500000005025613072261166014347 0ustar zmoelnigzmoelnigJFIFHHtExifMM*>F(iNHHР8Photoshop 3.08BIM8BIM%ُ B~ICC_PROFILEappl mntrRGB XYZ   acspAPPLappl-appl descodscmxcprt8wtptLrXYZ`gXYZtbXYZrTRCchad,bTRCgTRCdescGeneric RGB ProfileGeneric RGB Profilemluc skSK(daDK.caES$viVN$ptBR&"ukUA*HfrFU(rhuHU(zhTWnbNO&csCZ"heIL itIT(>roRO$fdeDE,koKRsvSE&zhCNjaJPelGR"ptPO&nlNL(DesES&thTH$ltrTR"fiFI(hrHR(plPL,ruRU".arEG&PenUS&vVaeobecn RGB profilGenerel RGB-beskrivelsePerfil RGB genricCu hnh RGB ChungPerfil RGB Genrico030;L=89 ?@>D09; RGBProfil gnrique RVBltalnos RGB profilu( RGB r_icϏGenerisk RGB-profilObecn RGB profil RGB Profilo RGB genericoProfil RGB genericAllgemeines RGB-Profil| RGB \ |fn RGB cϏeNN, RGB 000000  RGBPerfil RGB genricoAlgemeen RGB-profielB#D%L RGB 1H'DGenel RGB ProfiliYleinen RGB-profiiliGeneri ki RGB profilUniwersalny profil RGB1I89 ?@>D8;L RGBEDA *91JA RGB 'D9'EGeneric RGB ProfiletextCopyright 2007 Apple Inc., all rights reserved.XYZ RXYZ tM=XYZ Zus4XYZ (6curvsf32 B&l" }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w!1AQaq"2B #3Rbr $4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyzC  C - ?((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((_j^ChwM"?6"{ 4ŚfGPQBƺ(*MS$YW~џx&rc5ߴK;v!=Or  W9A'׶~ ݐ"Mz #RGB !5½_{|KwwˆnpwgK"7FR? zqkt -QYQ@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@((((((((((((((((B@<@ E|of뺒dyd2+zxsKYoxC}eVYvu9fQbK-l.-?Fp_!uy3"x_TiIme}ۆ1-kC_>v>.ϰ)^Gz\J(|((((((((((((((((((((((((((((((((^^Ziֲ_̖)y$"*K1k?uOυmRMHHO푻 rlB:u}nymG zug:x|Oo}V۔!硐}ے:_~?CIiss@%Qޓ?/pVy纞KibK31O$UfELSk}~~g噷b1W|W (((((((((nl#煃$GVaҠWэ3_|>aҼkzxp.ˏ|7rƾ<1)u j6l8x ~O/xڲk~8%NUe#NܟJ]>_qGVhW ?C:I aDd N} s+-׍s6" )]Q\'PQEQEQEQEQEQEQEQEQEQEQEQEQEQEQE((((((((((((((Ds-woTuf'$Ԟ!]!y$nð$~Keǭ|XLm;L𣧙&820r~8fcW^0TW><_O/vը8ipxr>w WO| +ЍDZx:] (`(((((u/bwt} $r?>IͳO0T.gLMEzP_~'䭇|wmm?ݑtۄ%ªWx&y+:-(/;>åTS*M>kԴ@5 9]Fp rj6WMr+7|scYunIJTfF*Ava<\{WGg6#U~?hSiͭrтF8]HKqYI?}|g4i7֎b6hd^$eaGJC#l&a|<=תI_ .Z_yQH#"#nFYH E}gk|JlGkH+rJ^ʲ}We\%OiI47~oc%\r^HyXdc'1t񍿠䵰'ѣL4(H(((((((((((((((((((((((((((I$p,Iu$/hZJ:}G' 9d 򌮦3==ຳ2 FU}h3|IכC&#ÚdE\837PӞ(F8T]FܟW~!&|(ݳv71R肊Zlψ }.fKRBξOW[Bk=j'IR` =dhCt=3_rN'ܾZ}wѣYv>Ţ+`+?xÓ|MOy  Q mL.Z#8)JAףfupu㈤_꟩ǏCJTl3zJG]5`1|5Zj6[]BH[hw(5gc#^jGf^T~:UAQEz W7Y`Eb?fs\zݗHc_5kZLE)ׅ9<~җlx:ʤv껯cZ?~&x2ĶvU Lo_+*QYf~FjATzQXQ@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@(((((((((((((g4xsP.mZVl{OE~}di:/H~EWQ@ 4DPI$;^1E &wy.*YOi\AQ 4):ؙߡ׃Oэ\C48D^Ic>-?SY X A-Ј?<"#G57zr"` \j}ImH-b0U@UP:Wهtbp\۷}^D~3>"7Am"?SPKWaAՎzT}J@:+^.boG"Ӭ9ܫA.?ܐ+~2]OIҵ78oexn#Yc`xWʾdi%ׄWӿԜp˼Xͨi8yu?痌W5/':V7IۗmZkI>~he#ם&-@sCQUh8NT,Es^n.}㸃<:GE}Э|?4ȃ0ԡk{{#d4ΧDv\c?&T$m}젚0لκ^=;IKq^!ĎrD:ͺ~ W]ϋ*Oxdj2{O},5|xiA~_-|w+ZX[^7lW9hQ.}K_fYe|%WCIW]t}~W|waosᖭ05iӥ|~ M71f8;'ĤuV8&.i{ogm4a5=pcZ9FeaR ߎkNhB9](LŠ(*68(TFId]O⾱ fB~붣2[>}G٧߀~A. [ SFxUU(UM_AWffUjA+Ewg3x|!-ޯ( >j W:`x\)PҟPu5<$Ս{qJOeICQxMlC۾вbj">>nG@95`brl*B0$?̫\_(XŠ(TV}ws?/_q+*\m zZ%Cakx+øe.~Q_ͧEPEPEPEPEPEPEPEPEPEPEPEPEP(((((((((((( oO;ȧXEC+ڵ~ :4<5PRPhhirjgUQ_EPzW k>?_5 }N& zW—sbRko>錻%U=#K1_1rWZv>ہ0xQ?+EW}vNյ/ஏ1[;dPBʹ/JA-'t8l퐕UmO4$拍Z#oX-ȃ5B#W'e1a! l{?x0jA JTZ+3XЮUӥ[Kќ{ 5<?ږ|Д,VLWiOc/G_pu7p6?ZS_x8muG* %>Ģ+hO_<c]JB&|J(DېOۓN6?<7ںBq0@+_}jc7Z?qSN+2Q_VxEP^>0o8]_S<-2K>~a+, `<  g[]XZ+ۑ}]]~|fzl@N]ؒ$NK;I|BՃ&}#/ߗ oo[KQE~$~<kSF>ؓ^A4JImXz(zl ~mE+ -?(M"\{S +غ<}He?BI|Gcֿ}7O(ۏ̟(GéGWUUshA^}_E? )̖7#^ Z'W}Ÿ$iQE_V-zo%QK1O*G-Rs./]pUj :߂(((((((((((((((((((((((((zنDG*}]O xVФ]cg( 1_Z~־ mV&LA&#LU]MbX*UZ~ྯ.ꂊ(d -՜Td?F_jۥHYqRQ:FW߱ 'j 13 r/[`!Y}k}T1RW _E~~S%O蒂>-Y-C%v)HZQdW߶KV/ i=AF fu;-fcWźrFA9. BIꕟ~|bdu|LK#'Ad ]3T6s|[H ޾5)/χQٓFV<Gt `% E@``z-,TYzSN+3[t0`VBݏA5db[JI+Մ'rO8齃]6=~crIzeSB,Ba^ڠW `~./kgXk^SVZhZ|deʪ 5^vEO3P$#;;_?~/_Kw0#qq"/M,%l":*+wufo]I.MCĺj`9X!<rp=Ɗ+*y=v N5JA_4~ѿVCC[+Rv6$Qy_\`bSdm\̖hAHj)ԑ`[O>|r?*/4O70:'/ 칅D(`zP!h(+/M]uZlA?ߔ?|_ǃSeάkp|Wf*_(- >gp #(?` ( ( ( ( ( ( ( ( ( ( ( ( (?(((((((((((((< |wK8q}9CkXWe)$d)?J56U ~[ dZ|X&p_xu*U^ Z^53x*u)L>b+G1Ed)'5oD$8*'C+< qf8x^앟8(*VKWG4w^>\#dl$~Sa5KW +㏤ ( ( ( ( ( ( ( ( ( ( ( ( (?(((((((((((((xnE8S{Z]&QRM=?CSG.t-Mhr0;}듯O!sj=J>]hϥjnmث)c'>%Gjw_g?:ҵ qdSkt'NKk-u.o#9olWT}Pkt`ڿ9W)J>WUpwDلD#5 r?=|x'j2QuwDrOTx+NFW Zv=H/'GCDA cj聽P:X|f^E^0fKS5 S=Gæ+̠݌' */3- s~&彗м $r$$'z0$*1SJZ4#*OP+c3c>6~N߇KmK{Ysg9C,av>Q nP57w 2{G+tAc(aT%m9tO>aQUy+]6=H:tޛpz|%hK@-$_Qe? m55cY1GC >K@-$ >![oI?瘣y?ew_q_ۏZρ5J[[i/"!i& `IPKIcls],ȌVP$#0+. }O4j2j(¾\xzG. $u y߆Zčv;;de(cxOLvo,I c~k h?RE5㯇># a 0)ϡݸcRWݩۣ/=ᚘVS_8(`((((((((((P 8@ ^nU6Xycw K]WMi =7Ev,` ~i\u )N.ӻ9 ʣUJ躿^FBQ@f{]Wrg鱊I-(((((((((((((((((((((((((((((((O^ ]bZ]eӶ?3)~̚$Aזa{}_(Y] k} A*pUv%R(e=Ax2,Sc؎E~_jxi>36U<;n?J+o~^-q C_;_idP{ygR?/Zg+zM?<3hYw4((((((((@M2ãI6!?>}kQ1_xL'/|oX,I]Ƿp+gwz/?# G_]h1c!M0c_# ^|ڥhP k_mƧN;-߫6?F8Zӗ.梨)mıFEӅQ@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@(((((((((((((((((GRE b3^k⟄\NHWU4SVg~0c%<5pcڬ#wZ|tXc'j~iϛpޕ_~:+/T >p߲Ma{|D˥sG"c;_U\~4Tg7iU9%G+Lz3j+e0_e[߲g#ι ?R㬭_8=˿]{zǠ ^*+ϯ>_J_/h8|M/pIL5 KT~gUO^pY+0^xSZeexf{|ޔ(op T]#_v6$ξw(qJ$7t+DAju|ncŘV*4-\`k]z_#<ı鶉ьfpQE|Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@(((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((o2-1.0/doc/logo.pptx0000644000175000017500000013536613072261166014570 0ustar zmoelnigzmoelnigPK![ [Content_Types].xml (̗R0:uh#:/<\y D$,(o t"eMvoaӹLU0kMM" b/FN2"6.{Ă(Z >B*|h,h p+w1~lh rnb0$΂2 k(fʸ7 @fWBZc,0!1(2Bg*J*="ufvjaHJfV<:@$> "/MCTH] Ox^&+ӌf7e=9c}.%|`?_`ֶ- 꿯6zN.xQbZV ț`5 gIJ ]{h~h\B<ȢPw|2^>JZЦTʽ;3PYzsfP/:5PVX,e(L"R~NdG(jPWZBR:N) zj/2QeM_D4nXVFm|dD ̀]WÙhlXTd6EfHW0Sl +^< Nhs=ROEdz`)^.4;<q7;i2 ӜDUL_'ʮ]V{HڳR$8edPmqlWcN)3cBx3ىGʃECZ٥xV{jꇘrĥKFe]T&o&M%|]H ȧ(5kfOvdu2Vt8 2s̀9aE !4W|.J >n rlppnJqx~wu~MÍzP(,vu]>zS)zehGPH(!2$gAh2 Z'5?+n E ''L[:uD =  }+YƇЊ{3`P|Ʈ$X~T3Z,fiEFl:^j'r]/ 0 Eq_XAfIlB -*sQ6pf #URXa ֊;B4`Acl>1Z8dW"o8JS %T*6wX@PK!ђ7,ppt/slideLayouts/_rels/slideLayout6.xml.rels 0Dnz^DEd$dߛc0ovkœ5ԲA|v= 88OİoB#EYÐs)f YH8]H"S";UQi΀)NVC:Kv:gc"T(3rTzy.jY6knPK!4͹,ppt/slideMasters/_rels/slideMaster1.xml.relsj AŘ5=ŽFwDڱ0H`s|}wmRV//O; %Er©x|8a'dvħ(ˡqn8Pj{a=_*mzt凨fiS3̀bIΒ9Kƀ\g]^{T3ZbWcqH麝NKÖ-is4n+6K2(Т+BUV!:lmb6!YSdۘmHox.$ǔC2xFgo PK!ђ7,ppt/slideLayouts/_rels/slideLayout8.xml.rels 0Dnz^DEd$dߛc0ovkœ5ԲA|v= 88OİoB#EYÐs)f YH8]H"S";UQi΀)NVC:Kv:gc"T(3rTzy.jY6knPK!ђ7,ppt/slideLayouts/_rels/slideLayout9.xml.rels 0Dnz^DEd$dߛc0ovkœ5ԲA|v= 88OİoB#EYÐs)f YH8]H"S";UQi΀)NVC:Kv:gc"T(3rTzy.jY6knPK!ђ7-ppt/slideLayouts/_rels/slideLayout11.xml.rels 0Dnz^DEd$dߛc0ovkœ5ԲA|v= 88OİoB#EYÐs)f YH8]H"S";UQi΀)NVC:Kv:gc"T(3rTzy.jY6knPK!ђ7-ppt/slideLayouts/_rels/slideLayout10.xml.rels 0Dnz^DEd$dߛc0ovkœ5ԲA|v= 88OİoB#EYÐs)f YH8]H"S";UQi΀)NVC:Kv:gc"T(3rTzy.jY6knPK!ђ7,ppt/slideLayouts/_rels/slideLayout7.xml.rels 0Dnz^DEd$dߛc0ovkœ5ԲA|v= 88OİoB#EYÐs)f YH8]H"S";UQi΀)NVC:Kv:gc"T(3rTzy.jY6knPK!ђ7,ppt/slideLayouts/_rels/slideLayout2.xml.rels 0Dnz^DEd$dߛc0ovkœ5ԲA|v= 88OİoB#EYÐs)f YH8]H"S";UQi΀)NVC:Kv:gc"T(3rTzy.jY6knPK!ђ7,ppt/slideLayouts/_rels/slideLayout3.xml.rels 0Dnz^DEd$dߛc0ovkœ5ԲA|v= 88OİoB#EYÐs)f YH8]H"S";UQi΀)NVC:Kv:gc"T(3rTzy.jY6knPK!ђ7,ppt/slideLayouts/_rels/slideLayout4.xml.rels 0Dnz^DEd$dߛc0ovkœ5ԲA|v= 88OİoB#EYÐs)f YH8]H"S";UQi΀)NVC:Kv:gc"T(3rTzy.jY6knPK!ђ7,ppt/slideLayouts/_rels/slideLayout5.xml.rels 0Dnz^DEd$dߛc0ovkœ5ԲA|v= 88OİoB#EYÐs)f YH8]H"S";UQi΀)NVC:Kv:gc"T(3rTzy.jY6knPK!ђ7,ppt/slideLayouts/_rels/slideLayout1.xml.rels 0Dnz^DEd$dߛc0ovkœ5ԲA|v= 88OİoB#EYÐs)f YH8]H"S";UQi΀)NVC:Kv:gc"T(3rTzy.jY6knPK! Z "ppt/slideLayouts/slideLayout11.xmlWn6w kE%., $޳ D]C8}R8,^6;琺~RіZޕk!Z^z fCkO[矮e DQZk)qZnxCk[qQa ?ŃS+;.kժ$tɦ@eXlZ֜~7-#dt\˝zd T >Ғ`z%ImRePo͢v{/PY(r~Y2?$V] A"pN Q;g'V;f)t=7l0`pE5]vkPUjf:MB\\'Qo-4VSm?~M~)W%z^1 "/#AM:&{GAU -U,kBzk8VFk F1bMXI>!-JVR4Py Hȍ=bg3m!(fY๎*&tY*B(#Tw(ɇ SƮ%,b[vPB+;10[j^[ J8)F~r]уTk7Bv>|)|:%bIT&䵕UH*_(le5{/?K]ϦI;6O_ A{؞anijY$i{p;xʲa#FucDF9tV,o2^e # VnGORυv6lءDv䅡ƹkςY.67H8nZ8}ۈ/؟ r;¹NȞGANx̾fDP}0\Dݹ᥾v{nwb8 4Jj/xi>0[m  'zΚ<.QPK!MX.!ppt/slideMasters/slideMaster1.xmlZ]nF~w G?)΅q9> K9^7߉:x|9TF`_T@Zn Sj4L,pE`V('<VLV ttyl})Jvf;̹q<h1buV&׵Qg4*QWUsCU`%j|MmZC  9N^/8 <Yrmi& PVd+Rɋ =Dd&"v:-]69Sل2k?T6 FmnUq|V!-E$8#!FH,^#h>WRx/xR8L쮝r)|{;1f1W>ar <եS QW,i&E{ϵu |_gͩ@'Mn+ nXgќ67]<(B[[cYNh[S/ Q`xP!l'R z6b,_%fsN1 `eA;:01\LjgD&zy_N '/߹ac&2JIPDGEݯUنSߟ}ˇNJQ:u}uOcgtaFD//hMISCJ EV.E64G#kd{;Cxw=o<gx~ >Q,Du9orS͎^U~*8~Mj9NlA؏)Qn,vд1\*pKh89=4k^98UX@g$E3jq(K_4#~3 1<+4fehpso:cwRO56 jUT7BRVYUW[e>>ʂZYPB,fʆCphe3fuaGYh zmʠ"\ B/oQ#- A tިX< lzkRN8;+Fb з@9COy,󨻮<-m tz[w0w@5g5%mB)AWmh~% )7H{IruB?:$L`lhƲ4jT={LJ(?0Wf:˕1 #h*?<T6 k TUl D>_uuEN\G8MQet0x۹d8]2bQdV%TDrˢim1[ѫ_BR֪B nZL1W<ܨXG0ȹ~6s?ffUs\{>a) F+S 3&>6x^(kӒ P`WɫI+ԾfyWU T,D ر<7&^0صiǣq/f_70U%+W$ގ)"ۥ} UX8p2kY N勦a`_ 3hfNhnU/G5 @ oF8]Na8^VġFر~toFmuXϟ}k]Exۢito<E$[c7Y(gQhB?&zOxi&nvߵeɺ]VvM˦Fvuo/NJ h1E8΄5Nn7|o>|PK!];K !ppt/slideLayouts/slideLayout2.xmlVmn8@@h+!.,Z,&A-%rIڵ[赺IvHIia54f8w5C["/"M 1?(pӤߦ_o{0j׆ή}hpjһ{=E0zψ% g46iwkhZ/yz`44GJl0 P4-3dNt!m)=aOdMTDp?PĶ쑺6mk VOnYT¥ME]edKUT:=^ O˃$g%Ic[B@V{5_zQhΟ@1+YvS[0r~ F?|nswD;F;XE$(t"V@$In6x?C24|Ѭ=oM&F~GVԲ-?, }eΨCed3f zS?%>M F'>H37jj>r(vc?00p5 iɼ_gU{DW ZaGt2 M(sdγafqEt<_/$u| ̄5%W9vHΗRG8'/Dhȧ&7XlA{p0. [LL@=PK!Z!ppt/slideLayouts/slideLayout3.xmlXmn6_wߊ-Y-M:{FcaRn^=Ξo(v)f @H9|f|M!5M^#9Mip;bh+3&-owg?pZ'.ٶZ)6&a#sT MkNd^ lbv8(X^xyjS~Q5"` e^7k !m oYf\1{:Q suRm[9+?z^H=j}#<##`s}0x4ĒBg,fd"h[ 2Ҷ1ݷ'@[uݹ͕0J?6FYOru/Zg2_/zE~GG߿ќ@wLD9ph?"%"GA8^Q'-at͸ʶD#rLTFV x^  &QFY/~CSĔN>e` MۍDZ,%yi} 5a;u6yP\Y44^`$JϡM2aC`f{ {w[ {7|Y ᒓ>bD yVBC;:x}Pz'Xvv6:-9LTw ҒE߷ aM=5sLW*Nӻjqc׏luskc܍˞Q4΅?lv24*/,_I~R&e1Y+c2*| P +&1Cb'2/H32ժ{ċVߛ8h I;lzY>N,Vc ñmM_ wIې%_/@jܟ~\68vP9o<$[cǟY0ga`'|M?xI*>uWhX_䩬jNҪe]A"{qp>hE^57p4ո@S}9PK!h.P "ppt/slideLayouts/slideLayout10.xmlVn8}_`>+dI؅K@wbk(~Nd6 8@3gù|8Q^x(X}7ެCJ\t^L/Al5Zexmn PdC+.DCkxW Ya ?]PH+D^T^/Oe h[I9_mXZs Z#>4-{Y;Л@d T Ls@ ^[3լ%ơ޽ͪzw++ Z݋ &8U~A N8*%m 77mDpZQZS WW \7Ԧ@};+Ԋ(&wiqƕ^V3ϱpZoVPᕞqtɌ3ih4zY2pѐ-#d`g V wB>)t1 C\#dpj.h\fi#B!m҅ |"PjnXQ"\sx'7Lo=YJ9|TxVEs֓0ǚ>8VhŮwQw)4OpU`^ҷ-6ӊmJ&L*m>g."I{O8Gi$(Vk`wj~_;C MZp#\);m%ӗ4 s?GLx o8#qߢKaQbD %J}AD3iЈT6ٱ4uent02e|la̔6nnv{pS4.507?>PK!Y{i!ppt/slideLayouts/slideLayout4.xmlX]n6~/;곢,b. d`=#Qco^=ΞCJtYML}|!O?,+gwZ<= SV3kE_NT \"QY3)qD6#'!5+9~u|׍ gEQfeԲ FZsb*Uvs4/`ijlBsT  Y-A~%)'D94\ϸ^rTJB7rL{# ˂W8&'L)YJh6فfh,_7E9RRU-+}f`25/^af%v%õ/5/SM\XR)r"WhB@mp)VQMjCJ0D}G<2$C$/%$ImP"O D:ڒ) J ᱥe"CdMA u D *,Wj^Yk.t[H]Q?$M@fNz:mpBѫ{pk v`M6|`a >6ه5a }I9FgD a6.A:ēj3h{IG$d% BYc6rvac\;.u-Y]Ӝ~j6# Ui枱aー^¶n  [d %IKjmKQ6 1Mq'usmߋ. ?lw&I{u9*ˊ9'7su-m[ǎwaP@}&6>3:z]zu_saQi2F& Ww[o \ٕfQGIdG^xڣ `u ey ?ۏA2b.սp~l9}KȾx/}1#{a8.(xaqo*Ȼ|vQg$c^8 { a1eAQ'uӽLPJ򏸹Y@sSt Q+PK!m?!ppt/slideLayouts/slideLayout6.xmlVn0;XwִZZml <Ξc'$ZU;}rP3JW!ʉ(+~3C`^b&8yGׯeYybg:#ok̃@->rUc&( kD^?qŽ__l63Av5 (J.|I40ϔQZSF=LCo ɚrfvF+E}wJJ9JZG/h'Z3 '7]$6@%;+8 " y%3d;: DÉ:8 = 5pZ EU6F5e V*#kV]OOxIN t,5n7Xy.OzYa~q|hE!j盻_'Ъ;FHc*}E1GAa0'~/8I`2? q&9QԵeۂaYWD -6挈:ho W\ {mc6,Y+.[ӦMrg?uCG&{1 PK!U8!ppt/slideLayouts/slideLayout5.xmlYnF}/ gFS,,ۨX+ e+Yn ~N3KDZC:d:N21L):C-.i}h)嚐Q(р `ʚ=ah\Taaw6aǜߧ$ `x\a-HFkoI^.ץ9e9hvukZ]\PI%"2*?ہ"0]?Ӱ-0Gnߺvǟz"8yXrz:  o_.u{hV1c8Ym8\@|䱾3|Nf^7"$Mb.^\ᥫvhh3DyNh6v4*0\3 FLcxB{pnm`]۵?|+U9V{;D)a_-%Oз݁1ܱ^q5=c9;t3^Xn/Tn<`p`*K"J6"uݯN)/X"7,E[fhzNh= Zp7)V դuPaCy:#B]]PK!#M_2!ppt/slideLayouts/slideLayout9.xmlXn8Ҿs'* :mt$D:¬F}y=ljt26_~d)YsQ%E>4E <8ɟƧǩ,YZ|hlye|2m$W!K)˰׫%XuQ<Ș Yڳ}$7bDVe "x$_-h)hmk$zD5,P ,fiLrCɕ9K2f%dQpwQޭIbj ^E#> ۟4 7 ]]W$/N46/[k0%/TVt ,+˝+x,yC4RC܌x.ˣe:,L+9 딂FOPJ)d c/Ckw6[Ss *13oLW4>Y'|d(A݂f.  :DXN۵ n2xHYėEE 4\ uœ|߭,&›ϘU՘1P >b(;O 'lH&{P-Lq}"Y{  $NB* ܝy|ojc(c䴨zb\g4D@5%xb0ϧߍY,VM̍,mopeIxknjsnPE.%eͷ &Mq$/6}6lcit%5"w 9. 0$C}lŖ\^ܨ Řvsӏ`a? _P/y3(0:_B1K#o5\oW~ษB"z ,ƲF9кp(Y.h~' BbE_ ډmBB}Q]s5X͐r֋>^ݚGOhqP¿6r%5\"0Fq, fkE~]ٽPeZo9Jhs & nT'cYB-)/R(~P ,[5ɠ$TBRV[a^^VRgdUW6.3^f!~ͻftMA(Q8V: ybtlZïn~,ŒLO-%(J\?(fYXY0y2=\?9փS]`|1*TR崱fVp١zbA;q@5Uy?#vɃbݠ>ٓBߏPK!I\'T!ppt/slideLayouts/slideLayout8.xmlXnFw׎= YUl{w<hҾV8$=3 b\43|?sa]hEyo @LX}tlF)S W?tYuޒ [ e-N h /rEn9XJϻgy, ZӜ^dU٪.l5Ш"Mڲӵ`Wy2ST2@CRI9R.WjR=pnQJ°/ZRi< +S;Ы:~xwvgْC*'l~ܻV3OWtn)S[ի P¡,H>7`,9a"^_=M?=lo.p VY´#h ʛ;5 )%2QJN|{F|=SQ'xPG[>8JA|RI=z9N⽆fkSm|z':;dO߃گ?|?2[Ojc*_W"^%R(/nqA?kptRU@ͮsx9/8GAm:ؿ670 7)*%K*L8}n 81crho\-. Ox}-jL,nY]D<4۠|<M}Ӈbm\qQA[KKN[X_}bU}m yz_ o00M7ǾyAt=tG_@ {q©-mb`I%l..VXEU'+l:+c7'Цz~;(ԌMJ$J pu?TK\%"uחOWPK !m)??docProps/thumbnail.jpegJFIFHHtExifMM*>F(iNHH8Photoshop 3.08BIM8BIM%ُ B~ICC_PROFILEappl mntrRGB XYZ   acspAPPLappl-appl descodscmxcprt8wtptLrXYZ`gXYZtbXYZrTRCchad,bTRCgTRCdescGeneric RGB ProfileGeneric RGB Profilemluc skSK(daDK.caES$viVN$ptBR&"ukUA*HfrFU(rhuHU(zhTWnbNO&csCZ"heIL itIT(>roRO$fdeDE,koKRsvSE&zhCNjaJPelGR"ptPO&nlNL(DesES&thTH$ltrTR"fiFI(hrHR(plPL,ruRU".arEG&PenUS&vVaeobecn RGB profilGenerel RGB-beskrivelsePerfil RGB genricCu hnh RGB ChungPerfil RGB Genrico030;L=89 ?@>D09; RGBProfil gnrique RVBltalnos RGB profilu( RGB r_icϏGenerisk RGB-profilObecn RGB profil RGB Profilo RGB genericoProfil RGB genericAllgemeines RGB-Profil| RGB \ |fn RGB cϏeNN, RGB 000000  RGBPerfil RGB genricoAlgemeen RGB-profielB#D%L RGB 1H'DGenel RGB ProfiliYleinen RGB-profiiliGeneri ki RGB profilUniwersalny profil RGB1I89 ?@>D8;L RGBEDA *91JA RGB 'D9'EGeneric RGB ProfiletextCopyright 2007 Apple Inc., all rights reserved.XYZ RXYZ tM=XYZ Zus4XYZ (6curvsf32 B&l }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w!1AQaq"2B #3Rbr $4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyzCC ?( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( (^/<-dڗÚrP׵["H]5֡qo!Ab (&n>`X&&:0oFRc0*~)-ӡM[_#~ڿ^D~5B%[ԼX'b9GD@E}1. fpR}r4r,®Ux86n#mcWRy "ɰ|Mu'e%@ ^SwaRO^o~kkG~-vبY\uZ''k|fЭ_^'q'Myد C2i.#myGIZ,Gv4 C_7Џu}_>5/|Ck6wr#Sm|'Ͳf^c;U~ mJ*W6{v>eyĤF[v;€ ( ( (?( ( (>vQ 7<@%ͱ|ZT%9V͆-:GIuMfNݢ+yOp'W0|8MCbwquy\UJIh׬)|?'G3sbzvqֶxƍ6^ڼRn2Q9~0|erZŲK7Wsw=Bbr3i]ճ3FoW'2ӭJci?nˡ.TjsUIW~xĹK)T,Wc}gMFz5j4gMj#>(/5\6.|OI,Cj7 /Wx,XlaᨮRA;i^/xv//.\EWZRo_?u@[/5 2Nq-wvAsn 0FMeZE9Q^E:U¥9Ǵ5(y4iJZ#VJj)NTAԢӿgm-5/V]]R}J8gR!Ol 7IgRXZ}өwJmQB׶q [rIS[ٝUҭzQJGs|A Ksu) '5H,u z~9<(n U1rl ZodW)y9F?YsG xTTYOOdzĥ xb4"s>ѯ€ ( ( (( ( (oo!?.u7Xt߇Q[v!n,4XD:S-S3:WgNViZt0pVJTpҌRoڞOʕ|pgRS 8pjPZ*)EƵx.*Ͼ sZΣ/y̟zki(,DTB>˰0X5& JN4҂0ݗۓlWb8E\^3[7R"IUVoyNs诲I+$]'8P@}TX_>koxw÷qd6 ޳JHot+*/8Uc)c:7U2ܪ1gK :֍s~,zTXa5?0\5HQLF"/eR o^+>_m%/>-<۟[xoĺi˴<9659Bj'TṴjטa*\xKYu:<&/J)[>t!Xдx3L5o=g,5 vAayKY%.mmbC}UX1.J38SEFҍ:5jѕY;ScN3qUøz 6izԜk*)SqZΥ8Յ5wRQw*[X\SZ[KЂ$hfpT?V 5Ӝ\dRե'tN=+sBI(MESOFvi~~P[ֽ|}־чMR& +kɛ\yGO&a⟃KĜ!r5|#pWLVYJ?ax8^XHa ϊL>Cu78 ⴽ7h5Դ8FD#bׯ-#UF>g~6q52ZRQ֭Syg9rrsr*xoh4%4x¹ tpk(Ѧ.J8tŧRJof5/|2xzVGÿji^3LohWPQ"OP,R@G,86b0g^X xSᆨ5JjkyGYV70rʙ} EXR2#ʣQѡ%I7Λi9G!$D#A*{0= bM}4i%ѦSOM5]iOگÏۥ$~!x_K;m۵*$A m4TU(x)Ę":bY˚u^N92+*r<6Gx88FኣJ)(ӎ*+JKhBBu妢J|ߴ_^|%מ9qͣ9{I3\_f[]*=Ņ$BmJ|;S畦ѧZX,ΥXA-!G:p/کbIxPQK{x:`W8RN;rQUjvNW|w9T.%uc`prB6<iVe1+o^>I&%g|>¶,X'BԂf=k_r~fZ/1Qˮ:*cęYcg Fa)XZTi'~rqPfURVץOV[Wǿ^m9C$Cxp[iXjFͨ_#o+Ђ'eS EZ|ET9sZ*iY.SĜ6fVN,M|-ݨ:jj$^֤7dxwĚ&>s㜶zYf5kįLEVKe#pRͱ_֠ ޷zwHH@du$WAoA+|,ѯa)=^@ 6vg4^I7]k`H<B*5x!ĪV/f ԣ5*yM)JX)[k7 _^ ZSM?<5X\`<Τ%gjr<zNuӡ3/nxUݽǏzg!vN|_>aUյ߲-J[_Ǽ. OtZSX5iNi|Ut"Qis>hԝomWeS\ˍCF/gLm5Ք3ʡ{Îcb!uӏ !m/U ʥ3*s)A7۫5P@P@( ( (q7?joi^z|Fc[|Wamr+}f}Rz rX/3?TW.WIߒxZ7:yK/|ٸK|ƃn8UdZ,]E@ [h͍ʹ%TqF2(\2R[N(PmN)Ӛ8}qbcZXORt޺t v(w3:Xddub4hQP$[ hcPZIn.!4,1tpLN;8U9rV'C(ЋlMJxz0V:qI^i-:??xoHӧR-Cf?"ǦEs\(q5/ܱX#nYI$չZO Ma0t)I69IJ1mF?IK4=|q;7A1uR|w6F0{I`8w*pgzT黧}yq&8؜M|GZdjEs+}aOv,? 4}/aexER Gk4FM+?ƿcp ,%K5hMKh/Ljm6Pw|C~grQ,RUH^egUn~G%y-`'įC6᥌ ޷{,_:/xCFfmPzv1p'fw,>TR2̳5MM{*NWUqj*j 8/ Tu1`ʹ}U:xjZs4*i^{xǞ2ŏ_qasǥ:<ZDYZ{.eBSrn]B4iPmgRswJe֫'zwVO!Ab1RG)JZ'm!i thQ!r+O('/uox+L]ڇ|KrYJOݹ f̑ʑ3`k ҞKI(.˱x:4*N5}R8/)Eu=L.oee4ܱ.NZ7mNbծc1F1Qh"(UEQUPA_wޭ%P@P@( ( (s ~>7zuOIu\XjD$bNXZ7ȶs^q<{+]%f!Msa~cYVx4+fy-j)7-UtigR\=9O:b7 X̰V,1.njt)^8mJ4%?gWhW_ ∾!Ke} }ki/+A]Qk NOK Xjhbsx88asu'Y`1ʢTjW(U*R?kET qRcETe R9 m5R29JQUc*U9ir:p:ݦxD!Dzas[(qcujwؼsik [|UYe\ay.YS:)a[ZZau)QE*Jtܡ/3<")f1nXYbJ Q*^ SYTR0|c ;d^;|)-ۯx_~r,V^"z4ᵹ $0y; VK֧*Vc5:0兣4WQҩVT37y6YXUC&jq{bATh3'Zqg%>=@4!w{j~/-kxC 8OVxs9W?7o6F_מ'kh-qVOڍMulFn%5he?pW(\nm +pu(VcXSQRܒxxʛjiqq?&O2帜TqGx78)ŵj.yAqZIK>.}ZaះIqzI:5K6sGhS]NUK}CPѦE _/xž.~xTЩ0y+Js.JxWU&S +*ڰWְa*< |s(꿓造 ( (( ( (Xk}jfgs6ť/ouisoq:I0;9Fp8INq%x2VqZM41e 3(I)FQ(jhv6?u~>&7-nO^ mIZ̃Z3'M#P5+T $oKŸSUG0!Rg4n^&uaM'Ú94gW Tr ՜VSڴ.5 u Mp3HucZCR/KcA?(.ofJs9hJ[ 7im<7~9SMX丑纺{˻[|]JZ{r^IewGfgbk6qI%ʭVK%d+b%ZnnoWe^ԉ. XdY k{]&dIbuUj֧RXF*:(T88FII٦)BQ%(N8Q_4eEFQi8-8SM\IH|yQ}Awb[ԓ7K¾mW#Wmex /O^Kb]i\?/ǯ9)_ l xP??BzÝN!W-(s9G:Ƌ|D.oŚLjѓS6Q:l:Z=EԐ:²9Xkx?xvu'eXqo0ok*jQ:麞T"<4ӈAfy3Ӎ?}dӚSq'/?$y像пCXVO Nj|]qsKgmKVUҴf3[Ay{oxYkUgIeZVZbq)>j8*R^GgVQth>wsx#0S3rtn/v.~jZeC|2o_X@Z[+{o5+P\jy{8DWg kcUcN"c)ӊ:T)Ҍ)1cfev)rܾp<(ѡJ?f1R99TRWJܤWvP@P@( ( ( sBѼMj:t-gDխeԴF+Il\[̭*pp VQ:UN5)U9SN$  IJ2Ri":uΕZpJ%N:ԧ48NN3e'M4?i&>Oxyvt2\:ԮִK~bխQKyVjq$PjHҜ L<(i qK=SRB h`rJKV)?a^Us(T4d؉є&rÇ?b| G䶻wމMT5 L7^cs_ʽR{K6#tc 2k_+2 nQa3,UZ1QERRTQM4?f9^cbe0X&;ҝ)8prjSf7(IkI4z (k{k⵴kXi]qEi$wbQUH+Pb(a0cWN Q[ʥZ8GR]|F.<6lN")PNuT4R?98'|P_eVx{KBxZ&+"x4S(+e#5dM'mY¶!ԩִ$4rӒLuH&U10R~> fRDR BFzx:r16愩КᇁxR O6n ^:Ϩjwn5 ]K,U"Bi晆sfv&|9KHӧtҥS`Hv*eZX<|K-HRŚL}vJN }K^[R[yl1YvwI$_l| n9UՓݹ;,9OEGv{|=WKJFzQ[%kr'3+xwǿ5e'k-3c?v I,ÅT_ڞ2OSԕxq1`*kq B= MXy i& ^D웈T*Yl[/oku1Qk: M.mX ?cwJ6p9Q{OnS9!XnR~^כ Fg' xSZ?ů}%o$d'x@VMgwJFmvycJYIX_;\Yx '-_gSkO˅v]-GneIoK ?B%۸e]b?];Tv_DLjܕ# kT{a(KKc-~Gq7q_mIBKaɆzQMy/~Q&COefg:4;+[Z~W{@P@P@P( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( PK! muppt/theme/theme1.xmlYoE#?N4TcЦb;jf7qAZKkEP ])CZy>c^;N:$BR6ZH򈦣fp߽ paS &D׶}*T1ITnf+m.-3»! V(FKG7aK˵Ri'pHCZeU(0xL!=85BNd tY3y"~'*@ K/AK[Wf.ي\r@tlA9iظS7fqNݩ !xjmltBgdnVk _ѿ2cFZmJ lkkeo@:o5o@6^XkxMf:n 9ᅯ|ç(Ȇ2Cy\t +"5m@P=$rfHυd(h{WϟWϟ<|vGN~ou97p: ߿?ew~ TԢ?ٓ_|7=mUx&D|3ĸ8D?ƴ*$Nţb}{Ze^?p b;݌9kqeᦞBsqo6NvNSَcé#;~@:Ppɇ ݧ>84Ag fjqzH < sh '>}*ᷰ}F&":RAGqԉ>;&tw$qB[*rcd>lq<h+|!8{8>ܥ#Ǥi7cuMj;:޽p[<7Nuy}EDmz=1Vot{C{'6[d|H #لKX. j9s$,᧮d62HpUq/lV2Dpp4^e@b;jGvxERjdD+Z\ɕoo2Y]lucil˚bs@K`dv7DpDw",& Mr#1 3\anbWЌ=#cd H;gA SAt5Z[,EG`cuy5@!Κ3 hR1Q3k VcN8e vm ͫ4=JޜNV.-fp^BoibOW?+oċ5~1%5p|74aڎ5jaDqc,3!T~ jCoE /5}ɤUYjfX/xZ{lm">'&ʝΩŋ$;gڎͥ"{DahXCL`G)>xށ1dO=akIIڬgd>"珒 [BSIE6h-\\EjY /-\JeRͧ>[oZWK e {Y2{P|mހ2uzrăOѫg/,:6MnPK!6dppt/viewProps.xmlRN0#4IrApR wlRĶgy_3E%xD`50oT8 +f QnXj$!^zh_xW̩M?n}g/PK! docProps/app.xml (TK0Wrj`Ujšt۳LUǎl];7K(ڜen͎輲fMǓ)laѧ M)5N lmQYB56ҏmSY@;p[U[[l>L>r| hJ,GK,E\ÿ-mSK m:W Yo֕^L'>V DبYo6P&X_}DbT.,Ѱ]mٻ{a+<8ֱ;Jg @ $Z%g//tlVZߋ+#*=?`V*Ey?wcuSb%%ʺրb/Q†w3 , X[.ojr_RW4 !F>;"⅒2V\uG@Q64'A!۠+e{E_mnoe~FaIX̝jiXkGwDIxB_<@xPK!4MBjdocProps/core.xml (QO0MP:]˞\BtF[mX#{ p{=~{J>n` J DRUzY-) cJZ+(,뫜J[0N ?oaP+gNh;03s'] L)P`vkfҿZ]cc=Բ"fZk0_-$ 4$L&#>|97x!FPK!5e!&(ppt/printerSettings/printerSettings1.binZKo@_6$iH*(" ROo)Yow3vHfٝ7ah׍vui)_>Q]U!8QofzoioW'\q,c~~2(j3DZBP'L_1BOOOwut7zHwC\6P zX59뷚#YDHqMut #.1<^| [9$3w fgǀB{5j4Y.^obXϋʼnV!-cGHjH< "ۚX{WI J\y vk\`YL1c \>fHtq U>*2B)`89H9E(IHxYܿ5{zd촤_P{ xI> 9G¤F&x HeĄ0FT| 4ɂ %p6!ktb۳|̞ՏoJ+ $KLrcfsdmUOFY>3V#_&sV4{rCI :7a Q No{e)A|RMI5wiOŌ4gr<T1ZZ KbfI=26kWL;/xQZ՚($vBh`'XZo'b;vCv*mK;OyI oIř>U Afw>HڑzB䝾nw$QW4Q9ӌ)VSXLtZCNJ`'(`\6IG0"2MY30y/b2/xu y`u̪U E/6%y^m(U2H2+)7sWt=;NSI F@Lp mJ6^wpx+ Icf[EnX2P?,-^]'PK-![ [Content_Types].xmlPK-!&  _rels/.relsPK-!c\#7 Hppt/slides/_rels/slide1.xml.relsPK-!5'rGppt/_rels/presentation.xml.relsPK-!~JX  ppt/presentation.xmlPK-!A- = ppt/slides/slide1.xmlPK-!ђ7,ppt/slideLayouts/_rels/slideLayout6.xml.relsPK-!4͹,ppt/slideMasters/_rels/slideMaster1.xml.relsPK-!ђ7,ppt/slideLayouts/_rels/slideLayout8.xml.relsPK-!ђ7,ppt/slideLayouts/_rels/slideLayout9.xml.relsPK-!ђ7-ppt/slideLayouts/_rels/slideLayout11.xml.relsPK-!ђ7-'ppt/slideLayouts/_rels/slideLayout10.xml.relsPK-!ђ7,0ppt/slideLayouts/_rels/slideLayout7.xml.relsPK-!ђ7,8ppt/slideLayouts/_rels/slideLayout2.xml.relsPK-!ђ7,@ppt/slideLayouts/_rels/slideLayout3.xml.relsPK-!ђ7,Hppt/slideLayouts/_rels/slideLayout4.xml.relsPK-!ђ7,Pppt/slideLayouts/_rels/slideLayout5.xml.relsPK-!ђ7,Xppt/slideLayouts/_rels/slideLayout1.xml.relsPK-! Z "`ppt/slideLayouts/slideLayout11.xmlPK-!MX.!"ppt/slideMasters/slideMaster1.xmlPK-!",!*ppt/slideLayouts/slideLayout1.xmlPK-!];K !/ppt/slideLayouts/slideLayout2.xmlPK-!Z!3ppt/slideLayouts/slideLayout3.xmlPK-!h.P "8ppt/slideLayouts/slideLayout10.xmlPK-!Y{i!<ppt/slideLayouts/slideLayout4.xmlPK-!m?!Appt/slideLayouts/slideLayout6.xmlPK-!U8!Eppt/slideLayouts/slideLayout5.xmlPK-!#M_2!3Kppt/slideLayouts/slideLayout9.xmlPK-!._^R f!Pppt/slideLayouts/slideLayout7.xmlPK-!I\'T!Sppt/slideLayouts/slideLayout8.xmlPK- !m)??bYdocProps/thumbnail.jpegPK-! muppt/theme/theme1.xmlPK-!6d֠ppt/viewProps.xmlPK-!ppt/tableStyles.xmlPK-!\GNuppt/presProps.xmlPK-! docProps/app.xmlPK-!4MBj5docProps/core.xmlPK-!5e!&(֪ppt/printerSettings/printerSettings1.binPK&& =o2-1.0/doc/design.txt0000644000175000017500000002164313072261166014715 0ustar zmoelnigzmoelnigPerformance ----------- On Roger's MacBook Pro, optimized client/server takes 64 microseconds for round trip, or 32 microseconds for a send through localhost. 2nd try: liblo 33us per send with -O3 optimization, liblo is 23us per message with -O3 and non-blocking receive, liblo is 17.5us O2 takes 40s for 1M messages round trip -> 40us per round trip or 20us for a send through localhost With non-blocking poll(), O2 takes 14us per message With poll() and 1ms timeout, O2 takes 24us per message in debug version With blocking_hack which set timeout to -1, O2 takes 21us per message June 8: 1M messages in 48.95s -> 49us/round-trip on 3GHz Core i7 but on my 2.4GHz Core i7 OS X 10.7.5, round-trip was 29us, so that's 15us per message. Message Handling ---------------- For non-pattern messages, just use a hash table for whole pattern including service name. For pattern messages, use a tree and match each node. Each node in the tree is a dictionary of keys to either handler or another node. The dictionary should be a hash table with linked overflow to simplify deletions. The dictionary should have between 2 and 3 times as many locations as data items. Discovery Protocol ------------------ New processes broadcast to 5 ports in sequence, initially every 0.33s but increase the interval by 10% each time until a maximum interval of 4s is reached. This gives a message every 40ms on average when there are 100 processes. Also gives 2 tries on 5 ports within first 2s. In addition to sending broadcast messages, we send messages to localhost because broadcast messages (at least on OS X) are not received on the same machine. The locolhost messages are sent at the same times as the broadcast messages and to the same ports, except that there is no need to send localhost messages to a process's own discovery port. When a discovery message arrives, we check: 1) if the application name does not match, ignore the message 2) if the IP:PORT string is already in the master_table (as a process) do no further processing 3) if the IP:PORT string (process name) in the message is lower than the receiver's name, do not process the message, but send a discovery message back to the sender. 4) Otherwise, create a process descriptor in master_table and initiate a connection to the process. Connection is made via TCP. Since we want to share the clock synchronization states, the connecting process must send clock state as well as its name (because it cannot know if any previous discovery message was sent or received) and UDP port. The connected-to process must have gotten a discovery message to the connecting process (otherwise the connection would not take place), but we do not know when the message was sent and the clock synchronization state might have changed in the interim, so there's no use sending clock state with discovery messages. Instead, the connected-to process will send a status message to the connecting process. Summary: discovery messages need only send IP and TCP server ports. Processes are named by strings of the form "128.2.1.50:54321" and the higher name (using strcmp()) connects to the lower name. After the TCP connection is established, *both* processes send their endianness (as "l" or "b"), IP, TCP port, UDP port, and clock synchronization state. Connection Walkthrough ---------------------- Processes are connected two ways: connect and accept. For the connect case, the sequence starts with the receipt of a discovery message, handled by: - o2_discovery_handler: A process entry is created by - add_remote_process with state PROCESS_CONNECTING. The tcp socket is then created using - make_tcp_connection which makes a socket with - make_tcp_recv_socket and is connected to the remote process. After connection, the status becomes PROCESS_CONNECTED. We send the !_o2/in initialization message using - send_by_tcp_to_process. We then send services to the remote process. When we made the socket, the handler was set to - tcp_initial_handler, which calls - o2_discovery_init_handler. This function updates the process entry with tcp_port (TODO: not needed?), the udp_port, the process status (either PROCESS_OK or PROCESS_NO_CLOCK, depending on the clock sync information from the incoming message), and the udp_sa socket address which is used to send UDP messages. For the accept case, we just add the newly accepted socket and set the handler to - tcp_initial_handler. At this point, there is no process entry. The tcp_initial_handler ensures the incoming !_o2/in message is complete and passes it off to - o2_discovery_init_handler. This function does a lookup on the process entry and discovers it is not there, so it makes one with - add_remote_process. The process entry is intialized and the status becomes either PROCESS_OK or PROCESS_NO_CLOCK. We send the !_o2/in initialization message using - send_by_tcp_to_process. We then send services to the remote process. Services -------- After making a connection, the connecting process sends all local services. If a new service is added, all connected processes are sent information about the new service. Similarly if a service is removed, all connected processes are sent a "remove service" message. Process State Protocol ---------------------- Internally, remote process descriptors go through a sequence of states: * PROCESS_DISCOVERED - a discovery message has revealed this process. * PROCESS_CONNECTING - a connection request has been issued. * PROCESS_CONNECTED - the connection has been completed, waiting for the initial status message. * PROCESS_NO_CLOCK - the process status message has arrived, and the process does not yet have clock sync. * PROCESS_OK - the process status message has arrived and the process has clock sync. The local process (o2_process) is initialized with the state PROCESS_NO_CLOCK and when it either becomes the master or clock synchronization is achieved the state changes to PROCESS_OK. Initially, processes have no clock and no services. Status information is obtained on a per-service basis using o2_status("service-name") which initially will return O2_FAIL. Periodically, the clock_sync "thread" (which is just a scheduled handler that reschedules itself periodically to do clock sync activities) checks the status of the "_cs" service. When it exists locally, clock sync is achieve implicitly. When it exists remotely, clock synchronization starts, and after some time, it will be established. For local services, the status values are: * O2_LOCAL_NOTIME - the service is local, but we cannot send scheduled messages because we do not have a synchronized clock * O2_LOCAL - the service is local, and timed messages will work For remote services, the status values are: * O2_REMOTE_NOTIME - the service is remote, but we cannot send scheduled messages because we do not have a synchronized clock * O2_REMOTE - the service is remote, and timed messages will work To implement the remote status, we need to know if the *remote* process has clock synchronization. Therefore, when clock synchronization is achieved, a process will send a message to all connected processes "register" the fact that they are synchronized. When connecting to a new process, the discovery info will include clock synchronization status. Messages -------- !_o2/dy "sssii" b_or_l_endian application_name ip tcp_port udp_port o2_discovery_handler(): message arrives by UDP broadcast or a send to localhost; this is a notification that another O2 process exists. The tcp_port is the server port listening for connections. The udp_port is the discovery port. !_o2/in "ssiii" b_or_l_endian ip tcp_port_number udp_port_number clocksync o2_initial_handler(): message arrives via tcp to initialize connection between two processes. clocksync is true (1) if the process is already synchronized to the master clock !_o2/sv "s..." process_name service1 service2 ... o2_services_handler(): message arrives via tcp to announce the initial list or an additional service !_o2/sd "ss" process_name service o2_service_delete_handler(): message arrives via tcp to announce the service has been deleted from the sending process !_o2/ds "" o2_discovery_send_handler(): (no arguments) send next discovery message via broadcast and send to localhost !_o2/ps "" o2_ping_send_handler(): (no arguments) send next ping message to clock service (_cs) !_cs/get "is" serial_no reply_to o2_ping_handler(): sends serial_no and master clock time back to sender by appending "/get-reply" to the reply_to argument to form a reply path. /get-reply "it" serial_no master_time o2_ping_reply_handler(): receive the time read from the master clock (via udp) in response to a !_cs/get message. o2-1.0/doc/logo-small.png0000644000175000017500000004010313072261166015447 0ustar zmoelnigzmoelnigPNG  IHDRs iCCPGeneric RGB8ˍU]hU>sg#$Sl4t? % V46nI6"dΘ83OEP|1Ŀ (>/ % (>P苦;3ie|{g蹪X-2s=+WQ+]L6O w[C{_F qb Uvz?Zb1@/zcs>~if,ӈUSjF 1_Mjbuݠpamhmçϙ>a\+5%QKFkm}ۖ?ޚD\!~6,-7SثŜvķ5Z;[rmS5{yDyH}r9|-ăFAJjI.[/]mK 7KRDrYQO-Q||6 (0 MXd(@h2_f<:”_δ*d>e\c?~,7?& ك^2Iq2"y@g|U IDATxieu}{khHR 1H(R"5H1X,9DId+8(C/I&ڲA!$ CC5=gq_U7@Tu^{9oʺA B@@ӛ!$@j "^iFZ\^^[X,8.Md\(R9TDt)bU{v7+@|ż ~v\T9+  /+ &ƯS*v k;f/3ӯh @Bp4 !$(&`c/*NNA?+ \оO|?q|}y Nj S܀:p{{W\/ݿc~ؚ&P>pB@^yNaK>& YB01 FlĽ,s ? /s^)ZI1O f Saӥ`Toa.?1s4e jO}\y BefMa&~f6Ł\\Zjz-fl4I8xl|a|_2:a D.wϔj1N#S`90aa;pb?|_?Z38N1/6&35;}$EBD`2]T"ܦ mHܾ) CnTg#sw.7=o+g\Lqm,6fR)ewkw(6=}f;O=(@frj66dʀlo]2%d k]"ܦ;(Zw~ډ%ĜK.-S<&JQSmb y&2^FM/ d(;=t>AE^jd+ڜf)ϱ_X _=C^:0M1GMIDr3c=g@3(O 1}/ PN#lR8KW"m]NhfgX'kD.Ke nQ`bv1w3ȤYHGcZ3`%ƨ"(.-/bo@#2@7s nB|{9`-J*>r#_~lmġ -,)rTnQUSS2a)Qe2@ZrQ \D=/F{MhjKt|LQ4D" yGd1JM;|fy~R0&S*]H-fݲƂ24ayo򼾾ԣ]6 1UU!(YP/KCj Uoႂ[)^ZzkyˁJڅOeD\kO|dP 4wt@A&SnMmBN w5UAsFuyjy3KKO\ro=(q d*<&D۬r򖋻A`<$AXǗ%-Ӡ]t5 gk'p}w0S]jSYs*Ԛj{;z;v*e ƤX7oz4,*XҰQbgS2> X $to| '`-Yn5 W"2 0&UL(kye]n_g~h L jp#rc]4*dhU8ٳؤ 8flV LSW/Ѹo#*nT0Mry]?~rVn0wmTʓG>ooӶG[M$00T]Eמl)\yNn2zdr2 jlqMẈeSh,xnψO?d0.Yn"tPl@1#"䑻eŋ Ě  wc`DpP=KE9SXvvR(M3_>~5cPuXu ff{C/8r٤IY'ݦB[0 \zeEr֓[P(݁(X]ÂlOFN1xne\0e'}:f斛Hdw.3BF0RHwl4Td<+;Ek0B;9eX㧎L0uF㎻ޚ:3;ՠWVeJF&=G;oMÙv|YZr{L-_~ooZ 0[}ؤA Λ2Hb6P&*W:ڊU{Tr uRnc]3X`IsR cB&3ealB^}' 30抲" g4 r)UfWeϲEal3h3Fy5 ^z;.kLl*M:Ko9-3wGӸzNY}غl@xQ6'*!䪍xolګv͔g( }&l=ة>ǭ7777od !D(nf{ɫr=RrЊa9RtyĻM$U8mNI) 楔 g_S"xHw} ;҄U\+4*SFýX,-? 4jMгNNPD~̛u_祐yZoc:Phs:DV,Pu@Wy k@6w"vI`iӿpaΓr&-L7?qu{,D>;`=1 \@g3B@bSO7}ϿeFU{#H]E4Ȣͻn8#ܩ촲h`m"ݶH)$UN8m(WWV* C],d oʚf8Q{xg^w. CּL Pzjx4gzw2Z_yS?|<TcHp)K:*e㌕O흩NG\7 %)4[MN"bCV@dI};zWWtAδMqE±Cm:݌%D.LN^JOʡةuCfH@T &'_;{7~ƕH `@Gn̘iz[a"IzjF*so\0X8TnrtM,;ba41*Eo+v C6 ϹZ-Fq:Ud<Ғ~ްb`Hɐxᕬ)sԦˁXXO.!i>9)#-.t_5"!albBT%MBt1ʹD0PjbC|,95,z"ϜJW,Н[4c"s 6Y:LND 0 t!߭=N {tQ:OLV[L98l@Y$:tKz'=7h>raj9<3fĆ D ]M~?5zKK'%rG[/1//.x !u5I Dx#P˖km@ YZ<+ʅtjy) =3Y` -ش]\t' h&n^rgo2N9JA&(E "Ck3@ )4)|o hT㜒eWȣkFg,{6 -6~#6Iϊ O-lVIGN5A۵11 DxsÿK&!$!3BȤmP$7X8QNouWb 7\и[=! j+'gw^64Bʷ\5ieYXC`FY\]?VEPlx/NfX>9Nh ^Z_^.mRpKay}Gnw0 )EʃGEp'=ONCQ<ь{5WJ`*ZtJ7Gi͛s|y|) y6Xcq}7.kCOȺKdėeQX`hiοڲUL쉓˓N=sj [FU.;y F[ Յ??Ԡh\.l }/W|*vX@ yg\w'oڤp3,&Z@O폿Ye,%%Wm(Hҕӕ{:DrB-OPbCw?(n=Y `t CL5lLB%@sTebkӖ(եwv ,X#Cak]_Rכ7;v{:ݽً:ݳW^}_?L(A7E*DI8 p:w:ϞCx`}MF , \I>uWsSSVE!9#@hx4 9^fsȀBT 2,(<wSJ$Z fl"rf7+9{Pfvv8숽yΰꩨ#YbQuzeu}k{>G!v( d@+P'_i~}}15&ȝ{q)U$d͗@rhfk˓mOL$[w?_~Y^7v'T)p^ Lyld o|mqoGg{U3W~,z!{ ;vjlRo{Ui2iIj^@A gTA"ky'Nwa˗k!+EXVG2~酢0*s! ۽w)Qudy/ jRozvvNqY\Ym9x\M6-,Y=Ogh;qjn+♛?+?6=kOO˟I_|?fw `֪Z l'`lPnVd<@2"UX{Voe^ Y.Op[MSN-Sk3CNKaq̱_wZdF!!! ?37g?v:*3f8|=>n]f4͆nKw]!zl9"(Bqz9y-fU(49 (l# (s, sBh> $!QͣKSĎE+3IFGØ,D3ZYßqoo4j &wNy}`Χv.3bH;,XxCESHv:Kf#MSF [ce@z3g7e{R֘VMmx2gme;(e(۠fYXfJ7,4^lf>|أK.ېT7N u⧿hQ2T [B 4:so. iA>U G>گOl_wXo&Ƃ0Mŧvl.cB'<(erW왍ӝcؿڷOj)nL%UN'nҧUΟY8z#|"}6GOۮ|;`!شoQ}{C9,PĎŢf<%~MɡjC !0&ɡbaBfMJIMn<5r)ݤԤG;zjr&xpzNgR;j*&dh$Utr]WؘWTkj9| \]˳}kn̜Q̓|׾K?CR dPBSao}XW)#Tt{?wC+k"qZ1d;>?l :V0I+qnW([™vat{fe<([(U t<3g{Uo$1&}-,ɧycs~&.Rkgr.o=V"$`&[e]iWuzMUSnggoy{nK0h;Nm>r:E>b8z,N+rq~1Qg P~+_[/B䙣ȳ\XHya )*4CNK'3]ۋU5޹#e+u hqPl LYOGTc63o΄n}yNt! 1~N"҉p@.KW2V-H31ɇ{y覫+PB& eļ'lc ^BH}3 ]!3SE6|ްBs 'NP d* AI$0 Gp)0W[|3Nme8!_@O3d;7!: ^ܞ$9--/?kl,ȥ4ض@2f2%ØX2elHhWy:'̦['\f~UV=X;w +Ii)Ύi'O0c>Dx^wT) ˛v3D +7^=;uY~n n^}َ+vt'&7dCa9uG{w0L1@;"F@vGhNwN fCVٚlgCx Ȳ(:G?p3u;1f< bbB6vy LCp01mePՏդqGMy{~%%;!n*NN[UMY9vF]!<<տ+p:R@6d0,`*p([p@oGHwz-sq0Sn;7lVE|"=RFEz<¡-3X7*5) 7YW ٱcC%ܨ(•{gn|y*[Mdu ,t{{v?ygW'm& =W/nc9`"z6 Q&u gCFH*WW?p|53,f{e6:7 jc,m-6Nq܄&YΦv܈c oJDl>.Bʜ"/\:m~fZLDr@ˎ*H z7_5wSzd&U(@LC6j1rbQ1:phqXek-jZZ* No@cFs2P|gO}}Aq3g_c4F0Lң/qPP Bv4S !sهN13)j0ƲNѷdy7^~aN5_޹#9akNmUFj˝ oMipEss}kHӪ(ʰ?󩇗cIE2-Ҷ:Z dҙ!-n~WwC6EvnԎCv>fj{:=E@CQƢ-d?b&??;hTss2J{{. ˢc[: A1;O8㕗ta sjh#KN#/FNey-%EZܩ^&a=M&hl􊲪B)QpfkO-/u~ۉe *E("CFM[Niv9MX&˫q^ Zg NU*Iw 8:~UջPџ M.*S@=֘"ρ!%Wv5޸j$DuE"FZ(XF"F+-LсqmDۑG\pVegh4ZQ[h,= ߺ?}{v̩pC|iF&LĴ`:й.Dvm$26 T\t%kb[d{r{ߨaQ \d9UAOnr|?O:xEQˤ`'z EqSg_/wo}үՒ*HP=CZ)gų~sh3[$^xT+Ty6MnwkgҚ J C]Hd2ߞٛ=;ĨL CDi,ÝftM6 e|f|ћJ*Za42h7>?~2y2W[ [[kI ep= _5E69 ӝ7)YH!<1Ej+" 1H@v!,^&M^9<儔FW0 T*-3$UP Wt5o$W]vfZ{9x^FQ};&JR@y q\ObwRg+"9\C%Y9`^%-V:O n4.{z4MeOyi]*xtd B(,|v<㽣{v Fp`]@VT?- *<.<bD1Fof+znIRBD<娱:&@,eh2MX'9\4G, bRG)_,w+)S`AQ )BgT|7.k~7Nm7ZZ# p}i-\Go8CO:-]tl6Iz܌shdM@LA %Ԑ yCjDjFԬ6da۵h4VkڒL*,4f/> R +O˓@ x_Ȳ|>/37)fY60-y\̛RpVINHc=ni<2FJk5RSKl'8GqlBt:`%CTЏfƙ$qE2h5 e3W8W:9甜kc&"+b$DQG[K((~j ʳn޲̇;!dTװ 2}nOՏbR89 Uꀰjt80Y0r<bR¬M2rZ6/|bA ,:`LIENDB`o2-1.0/doc/Doxyfile0000644000175000017500000032273413072261166014416 0ustar zmoelnigzmoelnig# Doxyfile 1.8.11 # This file describes the settings to be used by the documentation system # doxygen (www.doxygen.org) for a project. # # All text after a double hash (##) is considered a comment and is placed in # front of the TAG it is preceding. # # All text after a single hash (#) is considered a comment and will be ignored. # The format is: # TAG = value [value, ...] # For lists, items can also be appended using: # TAG += value [value, ...] # Values that contain spaces should be placed between quotes (\" \"). #--------------------------------------------------------------------------- # Project related configuration options #--------------------------------------------------------------------------- # This tag specifies the encoding used for all characters in the config file # that follow. The default is UTF-8 which is also the encoding used for all text # before the first occurrence of this tag. Doxygen uses libiconv (or the iconv # built into libc) for the transcoding. See http://www.gnu.org/software/libiconv # for the list of possible encodings. # The default value is: UTF-8. DOXYFILE_ENCODING = UTF-8 # The PROJECT_NAME tag is a single word (or a sequence of words surrounded by # double-quotes, unless you are using Doxywizard) that should identify the # project for which the documentation is generated. This name is used in the # title of most generated pages and in a few other places. # The default value is: My Project. PROJECT_NAME = O2 # The PROJECT_NUMBER tag can be used to enter a project or revision number. This # could be handy for archiving the generated documentation or if some version # control system is used. PROJECT_NUMBER = 1.1 # Using the PROJECT_BRIEF tag one can provide an optional one line description # for a project that appears at the top of each page and should give viewer a # quick idea about the purpose of the project. Keep the description short. PROJECT_BRIEF = "Inter-process communication system for media applications" # With the PROJECT_LOGO tag one can specify a logo or an icon that is included # in the documentation. The maximum height of the logo should not exceed 55 # pixels and the maximum width should not exceed 200 pixels. Doxygen will copy # the logo to the output directory. PROJECT_LOGO = /Users/rbd/o2/doc/logo-small.png # The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path # into which the generated documentation will be written. If a relative path is # entered, it will be relative to the location where doxygen was started. If # left blank the current directory will be used. OUTPUT_DIRECTORY = /Users/rbd/o2/doc/doxygen # If the CREATE_SUBDIRS tag is set to YES then doxygen will create 4096 sub- # directories (in 2 levels) under the output directory of each output format and # will distribute the generated files over these directories. Enabling this # option can be useful when feeding doxygen a huge amount of source files, where # putting all generated files in the same directory would otherwise causes # performance problems for the file system. # The default value is: NO. CREATE_SUBDIRS = NO # If the ALLOW_UNICODE_NAMES tag is set to YES, doxygen will allow non-ASCII # characters to appear in the names of generated files. If set to NO, non-ASCII # characters will be escaped, for example _xE3_x81_x84 will be used for Unicode # U+3044. # The default value is: NO. ALLOW_UNICODE_NAMES = NO # The OUTPUT_LANGUAGE tag is used to specify the language in which all # documentation generated by doxygen is written. Doxygen will use this # information to generate all constant output in the proper language. # Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Catalan, Chinese, # Chinese-Traditional, Croatian, Czech, Danish, Dutch, English (United States), # Esperanto, Farsi (Persian), Finnish, French, German, Greek, Hungarian, # Indonesian, Italian, Japanese, Japanese-en (Japanese with English messages), # Korean, Korean-en (Korean with English messages), Latvian, Lithuanian, # Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, Romanian, Russian, # Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, Swedish, Turkish, # Ukrainian and Vietnamese. # The default value is: English. OUTPUT_LANGUAGE = English # If the BRIEF_MEMBER_DESC tag is set to YES, doxygen will include brief member # descriptions after the members that are listed in the file and class # documentation (similar to Javadoc). Set to NO to disable this. # The default value is: YES. BRIEF_MEMBER_DESC = YES # If the REPEAT_BRIEF tag is set to YES, doxygen will prepend the brief # description of a member or function before the detailed description # # Note: If both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the # brief descriptions will be completely suppressed. # The default value is: YES. REPEAT_BRIEF = YES # This tag implements a quasi-intelligent brief description abbreviator that is # used to form the text in various listings. Each string in this list, if found # as the leading text of the brief description, will be stripped from the text # and the result, after processing the whole list, is used as the annotated # text. Otherwise, the brief description is used as-is. If left blank, the # following values are used ($name is automatically replaced with the name of # the entity):The $name class, The $name widget, The $name file, is, provides, # specifies, contains, represents, a, an and the. ABBREVIATE_BRIEF = "The $name class" \ "The $name widget" \ "The $name file" \ is \ provides \ specifies \ contains \ represents \ a \ an \ the # If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then # doxygen will generate a detailed section even if there is only a brief # description. # The default value is: NO. ALWAYS_DETAILED_SEC = NO # If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all # inherited members of a class in the documentation of that class as if those # members were ordinary class members. Constructors, destructors and assignment # operators of the base classes will not be shown. # The default value is: NO. INLINE_INHERITED_MEMB = NO # If the FULL_PATH_NAMES tag is set to YES, doxygen will prepend the full path # before files name in the file list and in the header files. If set to NO the # shortest path that makes the file name unique will be used # The default value is: YES. FULL_PATH_NAMES = YES # The STRIP_FROM_PATH tag can be used to strip a user-defined part of the path. # Stripping is only done if one of the specified strings matches the left-hand # part of the path. The tag can be used to show relative paths in the file list. # If left blank the directory from which doxygen is run is used as the path to # strip. # # Note that you can specify absolute paths here, but also relative paths, which # will be relative from the directory where doxygen is started. # This tag requires that the tag FULL_PATH_NAMES is set to YES. STRIP_FROM_PATH = # The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the # path mentioned in the documentation of a class, which tells the reader which # header file to include in order to use a class. If left blank only the name of # the header file containing the class definition is used. Otherwise one should # specify the list of include paths that are normally passed to the compiler # using the -I flag. STRIP_FROM_INC_PATH = # If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but # less readable) file names. This can be useful is your file systems doesn't # support long names like on DOS, Mac, or CD-ROM. # The default value is: NO. SHORT_NAMES = NO # If the JAVADOC_AUTOBRIEF tag is set to YES then doxygen will interpret the # first line (until the first dot) of a Javadoc-style comment as the brief # description. If set to NO, the Javadoc-style will behave just like regular Qt- # style comments (thus requiring an explicit @brief command for a brief # description.) # The default value is: NO. JAVADOC_AUTOBRIEF = NO # If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first # line (until the first dot) of a Qt-style comment as the brief description. If # set to NO, the Qt-style will behave just like regular Qt-style comments (thus # requiring an explicit \brief command for a brief description.) # The default value is: NO. QT_AUTOBRIEF = NO # The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make doxygen treat a # multi-line C++ special comment block (i.e. a block of //! or /// comments) as # a brief description. This used to be the default behavior. The new default is # to treat a multi-line C++ comment block as a detailed description. Set this # tag to YES if you prefer the old behavior instead. # # Note that setting this tag to YES also means that rational rose comments are # not recognized any more. # The default value is: NO. MULTILINE_CPP_IS_BRIEF = NO # If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the # documentation from any documented member that it re-implements. # The default value is: YES. INHERIT_DOCS = YES # If the SEPARATE_MEMBER_PAGES tag is set to YES then doxygen will produce a new # page for each member. If set to NO, the documentation of a member will be part # of the file/class/namespace that contains it. # The default value is: NO. SEPARATE_MEMBER_PAGES = NO # The TAB_SIZE tag can be used to set the number of spaces in a tab. Doxygen # uses this value to replace tabs by spaces in code fragments. # Minimum value: 1, maximum value: 16, default value: 4. TAB_SIZE = 4 # This tag can be used to specify a number of aliases that act as commands in # the documentation. An alias has the form: # name=value # For example adding # "sideeffect=@par Side Effects:\n" # will allow you to put the command \sideeffect (or @sideeffect) in the # documentation, which will result in a user-defined paragraph with heading # "Side Effects:". You can put \n's in the value part of an alias to insert # newlines. ALIASES = # This tag can be used to specify a number of word-keyword mappings (TCL only). # A mapping has the form "name=value". For example adding "class=itcl::class" # will allow you to use the command class in the itcl::class meaning. TCL_SUBST = # Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources # only. Doxygen will then generate output that is more tailored for C. For # instance, some of the names that are used will be different. The list of all # members will be omitted, etc. # The default value is: NO. OPTIMIZE_OUTPUT_FOR_C = YES # Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java or # Python sources only. Doxygen will then generate output that is more tailored # for that language. For instance, namespaces will be presented as packages, # qualified scopes will look different, etc. # The default value is: NO. OPTIMIZE_OUTPUT_JAVA = NO # Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran # sources. Doxygen will then generate output that is tailored for Fortran. # The default value is: NO. OPTIMIZE_FOR_FORTRAN = NO # Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL # sources. Doxygen will then generate output that is tailored for VHDL. # The default value is: NO. OPTIMIZE_OUTPUT_VHDL = NO # Doxygen selects the parser to use depending on the extension of the files it # parses. With this tag you can assign which parser to use for a given # extension. Doxygen has a built-in mapping, but you can override or extend it # using this tag. The format is ext=language, where ext is a file extension, and # language is one of the parsers supported by doxygen: IDL, Java, Javascript, # C#, C, C++, D, PHP, Objective-C, Python, Fortran (fixed format Fortran: # FortranFixed, free formatted Fortran: FortranFree, unknown formatted Fortran: # Fortran. In the later case the parser tries to guess whether the code is fixed # or free formatted code, this is the default for Fortran type files), VHDL. For # instance to make doxygen treat .inc files as Fortran files (default is PHP), # and .f files as C (default is Fortran), use: inc=Fortran f=C. # # Note: For files without extension you can use no_extension as a placeholder. # # Note that for custom extensions you also need to set FILE_PATTERNS otherwise # the files are not read by doxygen. EXTENSION_MAPPING = # If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments # according to the Markdown format, which allows for more readable # documentation. See http://daringfireball.net/projects/markdown/ for details. # The output of markdown processing is further processed by doxygen, so you can # mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in # case of backward compatibilities issues. # The default value is: YES. MARKDOWN_SUPPORT = YES # When enabled doxygen tries to link words that correspond to documented # classes, or namespaces to their corresponding documentation. Such a link can # be prevented in individual cases by putting a % sign in front of the word or # globally by setting AUTOLINK_SUPPORT to NO. # The default value is: YES. AUTOLINK_SUPPORT = YES # If you use STL classes (i.e. std::string, std::vector, etc.) but do not want # to include (a tag file for) the STL sources as input, then you should set this # tag to YES in order to let doxygen match functions declarations and # definitions whose arguments contain STL classes (e.g. func(std::string); # versus func(std::string) {}). This also make the inheritance and collaboration # diagrams that involve STL classes more complete and accurate. # The default value is: NO. BUILTIN_STL_SUPPORT = NO # If you use Microsoft's C++/CLI language, you should set this option to YES to # enable parsing support. # The default value is: NO. CPP_CLI_SUPPORT = NO # Set the SIP_SUPPORT tag to YES if your project consists of sip (see: # http://www.riverbankcomputing.co.uk/software/sip/intro) sources only. Doxygen # will parse them like normal C++ but will assume all classes use public instead # of private inheritance when no explicit protection keyword is present. # The default value is: NO. SIP_SUPPORT = NO # For Microsoft's IDL there are propget and propput attributes to indicate # getter and setter methods for a property. Setting this option to YES will make # doxygen to replace the get and set methods by a property in the documentation. # This will only work if the methods are indeed getting or setting a simple # type. If this is not the case, or you want to show the methods anyway, you # should set this option to NO. # The default value is: YES. IDL_PROPERTY_SUPPORT = YES # If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC # tag is set to YES then doxygen will reuse the documentation of the first # member in the group (if any) for the other members of the group. By default # all members of a group must be documented explicitly. # The default value is: NO. DISTRIBUTE_GROUP_DOC = NO # If one adds a struct or class to a group and this option is enabled, then also # any nested class or struct is added to the same group. By default this option # is disabled and one has to add nested compounds explicitly via \ingroup. # The default value is: NO. GROUP_NESTED_COMPOUNDS = NO # Set the SUBGROUPING tag to YES to allow class member groups of the same type # (for instance a group of public functions) to be put as a subgroup of that # type (e.g. under the Public Functions section). Set it to NO to prevent # subgrouping. Alternatively, this can be done per class using the # \nosubgrouping command. # The default value is: YES. SUBGROUPING = YES # When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and unions # are shown inside the group in which they are included (e.g. using \ingroup) # instead of on a separate page (for HTML and Man pages) or section (for LaTeX # and RTF). # # Note that this feature does not work in combination with # SEPARATE_MEMBER_PAGES. # The default value is: NO. INLINE_GROUPED_CLASSES = NO # When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and unions # with only public data fields or simple typedef fields will be shown inline in # the documentation of the scope in which they are defined (i.e. file, # namespace, or group documentation), provided this scope is documented. If set # to NO, structs, classes, and unions are shown on a separate page (for HTML and # Man pages) or section (for LaTeX and RTF). # The default value is: NO. INLINE_SIMPLE_STRUCTS = NO # When TYPEDEF_HIDES_STRUCT tag is enabled, a typedef of a struct, union, or # enum is documented as struct, union, or enum with the name of the typedef. So # typedef struct TypeS {} TypeT, will appear in the documentation as a struct # with name TypeT. When disabled the typedef will appear as a member of a file, # namespace, or class. And the struct will be named TypeS. This can typically be # useful for C code in case the coding convention dictates that all compound # types are typedef'ed and only the typedef is referenced, never the tag name. # The default value is: NO. TYPEDEF_HIDES_STRUCT = NO # The size of the symbol lookup cache can be set using LOOKUP_CACHE_SIZE. This # cache is used to resolve symbols given their name and scope. Since this can be # an expensive process and often the same symbol appears multiple times in the # code, doxygen keeps a cache of pre-resolved symbols. If the cache is too small # doxygen will become slower. If the cache is too large, memory is wasted. The # cache size is given by this formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range # is 0..9, the default is 0, corresponding to a cache size of 2^16=65536 # symbols. At the end of a run doxygen will report the cache usage and suggest # the optimal cache size from a speed point of view. # Minimum value: 0, maximum value: 9, default value: 0. LOOKUP_CACHE_SIZE = 0 #--------------------------------------------------------------------------- # Build related configuration options #--------------------------------------------------------------------------- # If the EXTRACT_ALL tag is set to YES, doxygen will assume all entities in # documentation are documented, even if no documentation was available. Private # class members and static file members will be hidden unless the # EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES. # Note: This will also disable the warnings about undocumented members that are # normally produced when WARNINGS is set to YES. # The default value is: NO. EXTRACT_ALL = NO # If the EXTRACT_PRIVATE tag is set to YES, all private members of a class will # be included in the documentation. # The default value is: NO. EXTRACT_PRIVATE = NO # If the EXTRACT_PACKAGE tag is set to YES, all members with package or internal # scope will be included in the documentation. # The default value is: NO. EXTRACT_PACKAGE = NO # If the EXTRACT_STATIC tag is set to YES, all static members of a file will be # included in the documentation. # The default value is: NO. EXTRACT_STATIC = NO # If the EXTRACT_LOCAL_CLASSES tag is set to YES, classes (and structs) defined # locally in source files will be included in the documentation. If set to NO, # only classes defined in header files are included. Does not have any effect # for Java sources. # The default value is: YES. EXTRACT_LOCAL_CLASSES = YES # This flag is only useful for Objective-C code. If set to YES, local methods, # which are defined in the implementation section but not in the interface are # included in the documentation. If set to NO, only methods in the interface are # included. # The default value is: NO. EXTRACT_LOCAL_METHODS = NO # If this flag is set to YES, the members of anonymous namespaces will be # extracted and appear in the documentation as a namespace called # 'anonymous_namespace{file}', where file will be replaced with the base name of # the file that contains the anonymous namespace. By default anonymous namespace # are hidden. # The default value is: NO. EXTRACT_ANON_NSPACES = NO # If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all # undocumented members inside documented classes or files. If set to NO these # members will be included in the various overviews, but no documentation # section is generated. This option has no effect if EXTRACT_ALL is enabled. # The default value is: NO. HIDE_UNDOC_MEMBERS = NO # If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all # undocumented classes that are normally visible in the class hierarchy. If set # to NO, these classes will be included in the various overviews. This option # has no effect if EXTRACT_ALL is enabled. # The default value is: NO. HIDE_UNDOC_CLASSES = NO # If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend # (class|struct|union) declarations. If set to NO, these declarations will be # included in the documentation. # The default value is: NO. HIDE_FRIEND_COMPOUNDS = NO # If the HIDE_IN_BODY_DOCS tag is set to YES, doxygen will hide any # documentation blocks found inside the body of a function. If set to NO, these # blocks will be appended to the function's detailed documentation block. # The default value is: NO. HIDE_IN_BODY_DOCS = NO # The INTERNAL_DOCS tag determines if documentation that is typed after a # \internal command is included. If the tag is set to NO then the documentation # will be excluded. Set it to YES to include the internal documentation. # The default value is: NO. INTERNAL_DOCS = NO # If the CASE_SENSE_NAMES tag is set to NO then doxygen will only generate file # names in lower-case letters. If set to YES, upper-case letters are also # allowed. This is useful if you have classes or files whose names only differ # in case and if your file system supports case sensitive file names. Windows # and Mac users are advised to set this option to NO. # The default value is: system dependent. CASE_SENSE_NAMES = NO # If the HIDE_SCOPE_NAMES tag is set to NO then doxygen will show members with # their full class and namespace scopes in the documentation. If set to YES, the # scope will be hidden. # The default value is: NO. HIDE_SCOPE_NAMES = YES # If the HIDE_COMPOUND_REFERENCE tag is set to NO (default) then doxygen will # append additional text to a page's title, such as Class Reference. If set to # YES the compound reference will be hidden. # The default value is: NO. HIDE_COMPOUND_REFERENCE= NO # If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of # the files that are included by a file in the documentation of that file. # The default value is: YES. SHOW_INCLUDE_FILES = YES # If the SHOW_GROUPED_MEMB_INC tag is set to YES then Doxygen will add for each # grouped member an include statement to the documentation, telling the reader # which file to include in order to use the member. # The default value is: NO. SHOW_GROUPED_MEMB_INC = NO # If the FORCE_LOCAL_INCLUDES tag is set to YES then doxygen will list include # files with double quotes in the documentation rather than with sharp brackets. # The default value is: NO. FORCE_LOCAL_INCLUDES = NO # If the INLINE_INFO tag is set to YES then a tag [inline] is inserted in the # documentation for inline members. # The default value is: YES. INLINE_INFO = YES # If the SORT_MEMBER_DOCS tag is set to YES then doxygen will sort the # (detailed) documentation of file and class members alphabetically by member # name. If set to NO, the members will appear in declaration order. # The default value is: YES. SORT_MEMBER_DOCS = YES # If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the brief # descriptions of file, namespace and class members alphabetically by member # name. If set to NO, the members will appear in declaration order. Note that # this will also influence the order of the classes in the class list. # The default value is: NO. SORT_BRIEF_DOCS = NO # If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the # (brief and detailed) documentation of class members so that constructors and # destructors are listed first. If set to NO the constructors will appear in the # respective orders defined by SORT_BRIEF_DOCS and SORT_MEMBER_DOCS. # Note: If SORT_BRIEF_DOCS is set to NO this option is ignored for sorting brief # member documentation. # Note: If SORT_MEMBER_DOCS is set to NO this option is ignored for sorting # detailed member documentation. # The default value is: NO. SORT_MEMBERS_CTORS_1ST = NO # If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the hierarchy # of group names into alphabetical order. If set to NO the group names will # appear in their defined order. # The default value is: NO. SORT_GROUP_NAMES = NO # If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be sorted by # fully-qualified names, including namespaces. If set to NO, the class list will # be sorted only by class name, not including the namespace part. # Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. # Note: This option applies only to the class list, not to the alphabetical # list. # The default value is: NO. SORT_BY_SCOPE_NAME = NO # If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to do proper # type resolution of all parameters of a function it will reject a match between # the prototype and the implementation of a member function even if there is # only one candidate or it is obvious which candidate to choose by doing a # simple string match. By disabling STRICT_PROTO_MATCHING doxygen will still # accept a match between prototype and implementation in such cases. # The default value is: NO. STRICT_PROTO_MATCHING = NO # The GENERATE_TODOLIST tag can be used to enable (YES) or disable (NO) the todo # list. This list is created by putting \todo commands in the documentation. # The default value is: YES. GENERATE_TODOLIST = YES # The GENERATE_TESTLIST tag can be used to enable (YES) or disable (NO) the test # list. This list is created by putting \test commands in the documentation. # The default value is: YES. GENERATE_TESTLIST = YES # The GENERATE_BUGLIST tag can be used to enable (YES) or disable (NO) the bug # list. This list is created by putting \bug commands in the documentation. # The default value is: YES. GENERATE_BUGLIST = YES # The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or disable (NO) # the deprecated list. This list is created by putting \deprecated commands in # the documentation. # The default value is: YES. GENERATE_DEPRECATEDLIST= YES # The ENABLED_SECTIONS tag can be used to enable conditional documentation # sections, marked by \if ... \endif and \cond # ... \endcond blocks. ENABLED_SECTIONS = NONE # The MAX_INITIALIZER_LINES tag determines the maximum number of lines that the # initial value of a variable or macro / define can have for it to appear in the # documentation. If the initializer consists of more lines than specified here # it will be hidden. Use a value of 0 to hide initializers completely. The # appearance of the value of individual variables and macros / defines can be # controlled using \showinitializer or \hideinitializer command in the # documentation regardless of this setting. # Minimum value: 0, maximum value: 10000, default value: 30. MAX_INITIALIZER_LINES = 30 # Set the SHOW_USED_FILES tag to NO to disable the list of files generated at # the bottom of the documentation of classes and structs. If set to YES, the # list will mention the files that were used to generate the documentation. # The default value is: YES. SHOW_USED_FILES = YES # Set the SHOW_FILES tag to NO to disable the generation of the Files page. This # will remove the Files entry from the Quick Index and from the Folder Tree View # (if specified). # The default value is: YES. SHOW_FILES = YES # Set the SHOW_NAMESPACES tag to NO to disable the generation of the Namespaces # page. This will remove the Namespaces entry from the Quick Index and from the # Folder Tree View (if specified). # The default value is: YES. SHOW_NAMESPACES = YES # The FILE_VERSION_FILTER tag can be used to specify a program or script that # doxygen should invoke to get the current version for each file (typically from # the version control system). Doxygen will invoke the program by executing (via # popen()) the command command input-file, where command is the value of the # FILE_VERSION_FILTER tag, and input-file is the name of an input file provided # by doxygen. Whatever the program writes to standard output is used as the file # version. For an example see the documentation. FILE_VERSION_FILTER = # The LAYOUT_FILE tag can be used to specify a layout file which will be parsed # by doxygen. The layout file controls the global structure of the generated # output files in an output format independent way. To create the layout file # that represents doxygen's defaults, run doxygen with the -l option. You can # optionally specify a file name after the option, if omitted DoxygenLayout.xml # will be used as the name of the layout file. # # Note that if you run doxygen from a directory containing a file called # DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE # tag is left empty. LAYOUT_FILE = # The CITE_BIB_FILES tag can be used to specify one or more bib files containing # the reference definitions. This must be a list of .bib files. The .bib # extension is automatically appended if omitted. This requires the bibtex tool # to be installed. See also http://en.wikipedia.org/wiki/BibTeX for more info. # For LaTeX the style of the bibliography can be controlled using # LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the # search path. See also \cite for info how to create references. CITE_BIB_FILES = #--------------------------------------------------------------------------- # Configuration options related to warning and progress messages #--------------------------------------------------------------------------- # The QUIET tag can be used to turn on/off the messages that are generated to # standard output by doxygen. If QUIET is set to YES this implies that the # messages are off. # The default value is: NO. QUIET = NO # The WARNINGS tag can be used to turn on/off the warning messages that are # generated to standard error (stderr) by doxygen. If WARNINGS is set to YES # this implies that the warnings are on. # # Tip: Turn warnings on while writing the documentation. # The default value is: YES. WARNINGS = YES # If the WARN_IF_UNDOCUMENTED tag is set to YES then doxygen will generate # warnings for undocumented members. If EXTRACT_ALL is set to YES then this flag # will automatically be disabled. # The default value is: YES. WARN_IF_UNDOCUMENTED = YES # If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for # potential errors in the documentation, such as not documenting some parameters # in a documented function, or documenting parameters that don't exist or using # markup commands wrongly. # The default value is: YES. WARN_IF_DOC_ERROR = YES # This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that # are documented, but have no documentation for their parameters or return # value. If set to NO, doxygen will only warn about wrong or incomplete # parameter documentation, but not about the absence of documentation. # The default value is: NO. WARN_NO_PARAMDOC = NO # If the WARN_AS_ERROR tag is set to YES then doxygen will immediately stop when # a warning is encountered. # The default value is: NO. WARN_AS_ERROR = NO # The WARN_FORMAT tag determines the format of the warning messages that doxygen # can produce. The string should contain the $file, $line, and $text tags, which # will be replaced by the file and line number from which the warning originated # and the warning text. Optionally the format may contain $version, which will # be replaced by the version of the file (if it could be obtained via # FILE_VERSION_FILTER) # The default value is: $file:$line: $text. WARN_FORMAT = "$file:$line: $text" # The WARN_LOGFILE tag can be used to specify a file to which warning and error # messages should be written. If left blank the output is written to standard # error (stderr). WARN_LOGFILE = #--------------------------------------------------------------------------- # Configuration options related to the input files #--------------------------------------------------------------------------- # The INPUT tag is used to specify the files and/or directories that contain # documented source files. You may enter file names like myfile.cpp or # directories like /usr/src/myproject. Separate the files or directories with # spaces. See also FILE_PATTERNS and EXTENSION_MAPPING # Note: If this tag is empty the current directory is searched. INPUT = /Users/rbd/o2/src/o2.h # This tag can be used to specify the character encoding of the source files # that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses # libiconv (or the iconv built into libc) for the transcoding. See the libiconv # documentation (see: http://www.gnu.org/software/libiconv) for the list of # possible encodings. # The default value is: UTF-8. INPUT_ENCODING = UTF-8 # If the value of the INPUT tag contains directories, you can use the # FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and # *.h) to filter out the source-files in the directories. # # Note that for custom extensions or not directly supported extensions you also # need to set EXTENSION_MAPPING for the extension otherwise the files are not # read by doxygen. # # If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cpp, # *.c++, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h, # *.hh, *.hxx, *.hpp, *.h++, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, *.inc, # *.m, *.markdown, *.md, *.mm, *.dox, *.py, *.pyw, *.f90, *.f, *.for, *.tcl, # *.vhd, *.vhdl, *.ucf, *.qsf, *.as and *.js. FILE_PATTERNS = *.c \ *.cc \ *.cxx \ *.cpp \ *.c++ \ *.java \ *.ii \ *.ixx \ *.ipp \ *.i++ \ *.inl \ *.idl \ *.ddl \ *.odl \ *.h \ *.hh \ *.hxx \ *.hpp \ *.h++ \ *.cs \ *.d \ *.php \ *.php4 \ *.php5 \ *.phtml \ *.inc \ *.m \ *.markdown \ *.md \ *.mm \ *.dox \ *.py \ *.pyw \ *.f90 \ *.f \ *.for \ *.tcl \ *.vhd \ *.vhdl \ *.ucf \ *.qsf \ *.as \ *.js # The RECURSIVE tag can be used to specify whether or not subdirectories should # be searched for input files as well. # The default value is: NO. RECURSIVE = NO # The EXCLUDE tag can be used to specify files and/or directories that should be # excluded from the INPUT source files. This way you can easily exclude a # subdirectory from a directory tree whose root is specified with the INPUT tag. # # Note that relative paths are relative to the directory from which doxygen is # run. EXCLUDE = # The EXCLUDE_SYMLINKS tag can be used to select whether or not files or # directories that are symbolic links (a Unix file system feature) are excluded # from the input. # The default value is: NO. EXCLUDE_SYMLINKS = NO # If the value of the INPUT tag contains directories, you can use the # EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude # certain files from those directories. # # Note that the wildcards are matched against the file with absolute path, so to # exclude all test directories for example use the pattern */test/* EXCLUDE_PATTERNS = # The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names # (namespaces, classes, functions, etc.) that should be excluded from the # output. The symbol name can be a fully qualified name, a word, or if the # wildcard * is used, a substring. Examples: ANamespace, AClass, # AClass::ANamespace, ANamespace::*Test # # Note that the wildcards are matched against the file with absolute path, so to # exclude all test directories use the pattern */test/* EXCLUDE_SYMBOLS = # The EXAMPLE_PATH tag can be used to specify one or more files or directories # that contain example code fragments that are included (see the \include # command). EXAMPLE_PATH = # If the value of the EXAMPLE_PATH tag contains directories, you can use the # EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and # *.h) to filter out the source-files in the directories. If left blank all # files are included. EXAMPLE_PATTERNS = * # If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be # searched for input files to be used with the \include or \dontinclude commands # irrespective of the value of the RECURSIVE tag. # The default value is: NO. EXAMPLE_RECURSIVE = NO # The IMAGE_PATH tag can be used to specify one or more files or directories # that contain images that are to be included in the documentation (see the # \image command). IMAGE_PATH = # The INPUT_FILTER tag can be used to specify a program that doxygen should # invoke to filter for each input file. Doxygen will invoke the filter program # by executing (via popen()) the command: # # # # where is the value of the INPUT_FILTER tag, and is the # name of an input file. Doxygen will then use the output that the filter # program writes to standard output. If FILTER_PATTERNS is specified, this tag # will be ignored. # # Note that the filter must not add or remove lines; it is applied before the # code is scanned, but not when the output code is generated. If lines are added # or removed, the anchors will not be placed correctly. # # Note that for custom extensions or not directly supported extensions you also # need to set EXTENSION_MAPPING for the extension otherwise the files are not # properly processed by doxygen. INPUT_FILTER = # The FILTER_PATTERNS tag can be used to specify filters on a per file pattern # basis. Doxygen will compare the file name with each pattern and apply the # filter if there is a match. The filters are a list of the form: pattern=filter # (like *.cpp=my_cpp_filter). See INPUT_FILTER for further information on how # filters are used. If the FILTER_PATTERNS tag is empty or if none of the # patterns match the file name, INPUT_FILTER is applied. # # Note that for custom extensions or not directly supported extensions you also # need to set EXTENSION_MAPPING for the extension otherwise the files are not # properly processed by doxygen. FILTER_PATTERNS = # If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using # INPUT_FILTER) will also be used to filter the input files that are used for # producing the source files to browse (i.e. when SOURCE_BROWSER is set to YES). # The default value is: NO. FILTER_SOURCE_FILES = NO # The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file # pattern. A pattern will override the setting for FILTER_PATTERN (if any) and # it is also possible to disable source filtering for a specific pattern using # *.ext= (so without naming a filter). # This tag requires that the tag FILTER_SOURCE_FILES is set to YES. FILTER_SOURCE_PATTERNS = # If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that # is part of the input, its contents will be placed on the main page # (index.html). This can be useful if you have a project on for instance GitHub # and want to reuse the introduction page also for the doxygen output. USE_MDFILE_AS_MAINPAGE = #--------------------------------------------------------------------------- # Configuration options related to source browsing #--------------------------------------------------------------------------- # If the SOURCE_BROWSER tag is set to YES then a list of source files will be # generated. Documented entities will be cross-referenced with these sources. # # Note: To get rid of all source code in the generated output, make sure that # also VERBATIM_HEADERS is set to NO. # The default value is: NO. SOURCE_BROWSER = NO # Setting the INLINE_SOURCES tag to YES will include the body of functions, # classes and enums directly into the documentation. # The default value is: NO. INLINE_SOURCES = NO # Setting the STRIP_CODE_COMMENTS tag to YES will instruct doxygen to hide any # special comment blocks from generated source code fragments. Normal C, C++ and # Fortran comments will always remain visible. # The default value is: YES. STRIP_CODE_COMMENTS = YES # If the REFERENCED_BY_RELATION tag is set to YES then for each documented # function all documented functions referencing it will be listed. # The default value is: NO. REFERENCED_BY_RELATION = NO # If the REFERENCES_RELATION tag is set to YES then for each documented function # all documented entities called/used by that function will be listed. # The default value is: NO. REFERENCES_RELATION = NO # If the REFERENCES_LINK_SOURCE tag is set to YES and SOURCE_BROWSER tag is set # to YES then the hyperlinks from functions in REFERENCES_RELATION and # REFERENCED_BY_RELATION lists will link to the source code. Otherwise they will # link to the documentation. # The default value is: YES. REFERENCES_LINK_SOURCE = YES # If SOURCE_TOOLTIPS is enabled (the default) then hovering a hyperlink in the # source code will show a tooltip with additional information such as prototype, # brief description and links to the definition and documentation. Since this # will make the HTML file larger and loading of large files a bit slower, you # can opt to disable this feature. # The default value is: YES. # This tag requires that the tag SOURCE_BROWSER is set to YES. SOURCE_TOOLTIPS = YES # If the USE_HTAGS tag is set to YES then the references to source code will # point to the HTML generated by the htags(1) tool instead of doxygen built-in # source browser. The htags tool is part of GNU's global source tagging system # (see http://www.gnu.org/software/global/global.html). You will need version # 4.8.6 or higher. # # To use it do the following: # - Install the latest version of global # - Enable SOURCE_BROWSER and USE_HTAGS in the config file # - Make sure the INPUT points to the root of the source tree # - Run doxygen as normal # # Doxygen will invoke htags (and that will in turn invoke gtags), so these # tools must be available from the command line (i.e. in the search path). # # The result: instead of the source browser generated by doxygen, the links to # source code will now point to the output of htags. # The default value is: NO. # This tag requires that the tag SOURCE_BROWSER is set to YES. USE_HTAGS = NO # If the VERBATIM_HEADERS tag is set the YES then doxygen will generate a # verbatim copy of the header file for each class for which an include is # specified. Set to NO to disable this. # See also: Section \class. # The default value is: YES. VERBATIM_HEADERS = YES # If the CLANG_ASSISTED_PARSING tag is set to YES then doxygen will use the # clang parser (see: http://clang.llvm.org/) for more accurate parsing at the # cost of reduced performance. This can be particularly helpful with template # rich C++ code for which doxygen's built-in parser lacks the necessary type # information. # Note: The availability of this option depends on whether or not doxygen was # generated with the -Duse-libclang=ON option for CMake. # The default value is: NO. CLANG_ASSISTED_PARSING = NO # If clang assisted parsing is enabled you can provide the compiler with command # line options that you would normally use when invoking the compiler. Note that # the include paths will already be set by doxygen for the files and directories # specified with INPUT and INCLUDE_PATH. # This tag requires that the tag CLANG_ASSISTED_PARSING is set to YES. CLANG_OPTIONS = #--------------------------------------------------------------------------- # Configuration options related to the alphabetical class index #--------------------------------------------------------------------------- # If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index of all # compounds will be generated. Enable this if the project contains a lot of # classes, structs, unions or interfaces. # The default value is: YES. ALPHABETICAL_INDEX = YES # The COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns in # which the alphabetical index list will be split. # Minimum value: 1, maximum value: 20, default value: 5. # This tag requires that the tag ALPHABETICAL_INDEX is set to YES. COLS_IN_ALPHA_INDEX = 5 # In case all classes in a project start with a common prefix, all classes will # be put under the same header in the alphabetical index. The IGNORE_PREFIX tag # can be used to specify a prefix (or a list of prefixes) that should be ignored # while generating the index headers. # This tag requires that the tag ALPHABETICAL_INDEX is set to YES. IGNORE_PREFIX = #--------------------------------------------------------------------------- # Configuration options related to the HTML output #--------------------------------------------------------------------------- # If the GENERATE_HTML tag is set to YES, doxygen will generate HTML output # The default value is: YES. GENERATE_HTML = YES # The HTML_OUTPUT tag is used to specify where the HTML docs will be put. If a # relative path is entered the value of OUTPUT_DIRECTORY will be put in front of # it. # The default directory is: html. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_OUTPUT = html # The HTML_FILE_EXTENSION tag can be used to specify the file extension for each # generated HTML page (for example: .htm, .php, .asp). # The default value is: .html. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_FILE_EXTENSION = .html # The HTML_HEADER tag can be used to specify a user-defined HTML header file for # each generated HTML page. If the tag is left blank doxygen will generate a # standard header. # # To get valid HTML the header file that includes any scripts and style sheets # that doxygen needs, which is dependent on the configuration options used (e.g. # the setting GENERATE_TREEVIEW). It is highly recommended to start with a # default header using # doxygen -w html new_header.html new_footer.html new_stylesheet.css # YourConfigFile # and then modify the file new_header.html. See also section "Doxygen usage" # for information on how to generate the default header that doxygen normally # uses. # Note: The header is subject to change so you typically have to regenerate the # default header when upgrading to a newer version of doxygen. For a description # of the possible markers and block names see the documentation. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_HEADER = # The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each # generated HTML page. If the tag is left blank doxygen will generate a standard # footer. See HTML_HEADER for more information on how to generate a default # footer and what special commands can be used inside the footer. See also # section "Doxygen usage" for information on how to generate the default footer # that doxygen normally uses. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_FOOTER = # The HTML_STYLESHEET tag can be used to specify a user-defined cascading style # sheet that is used by each HTML page. It can be used to fine-tune the look of # the HTML output. If left blank doxygen will generate a default style sheet. # See also section "Doxygen usage" for information on how to generate the style # sheet that doxygen normally uses. # Note: It is recommended to use HTML_EXTRA_STYLESHEET instead of this tag, as # it is more robust and this tag (HTML_STYLESHEET) will in the future become # obsolete. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_STYLESHEET = # The HTML_EXTRA_STYLESHEET tag can be used to specify additional user-defined # cascading style sheets that are included after the standard style sheets # created by doxygen. Using this option one can overrule certain style aspects. # This is preferred over using HTML_STYLESHEET since it does not replace the # standard style sheet and is therefore more robust against future updates. # Doxygen will copy the style sheet files to the output directory. # Note: The order of the extra style sheet files is of importance (e.g. the last # style sheet in the list overrules the setting of the previous ones in the # list). For an example see the documentation. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_EXTRA_STYLESHEET = # The HTML_EXTRA_FILES tag can be used to specify one or more extra images or # other source files which should be copied to the HTML output directory. Note # that these files will be copied to the base HTML output directory. Use the # $relpath^ marker in the HTML_HEADER and/or HTML_FOOTER files to load these # files. In the HTML_STYLESHEET file, use the file name only. Also note that the # files will be copied as-is; there are no commands or markers available. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_EXTRA_FILES = # The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen # will adjust the colors in the style sheet and background images according to # this color. Hue is specified as an angle on a colorwheel, see # http://en.wikipedia.org/wiki/Hue for more information. For instance the value # 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300 # purple, and 360 is red again. # Minimum value: 0, maximum value: 359, default value: 220. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_COLORSTYLE_HUE = 220 # The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors # in the HTML output. For a value of 0 the output will use grayscales only. A # value of 255 will produce the most vivid colors. # Minimum value: 0, maximum value: 255, default value: 100. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_COLORSTYLE_SAT = 100 # The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to the # luminance component of the colors in the HTML output. Values below 100 # gradually make the output lighter, whereas values above 100 make the output # darker. The value divided by 100 is the actual gamma applied, so 80 represents # a gamma of 0.8, The value 220 represents a gamma of 2.2, and 100 does not # change the gamma. # Minimum value: 40, maximum value: 240, default value: 80. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_COLORSTYLE_GAMMA = 80 # If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML # page will contain the date and time when the page was generated. Setting this # to YES can help to show when doxygen was last run and thus if the # documentation is up to date. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_TIMESTAMP = NO # If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML # documentation will contain sections that can be hidden and shown after the # page has loaded. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_DYNAMIC_SECTIONS = NO # With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries # shown in the various tree structured indices initially; the user can expand # and collapse entries dynamically later on. Doxygen will expand the tree to # such a level that at most the specified number of entries are visible (unless # a fully collapsed tree already exceeds this amount). So setting the number of # entries 1 will produce a full collapsed tree by default. 0 is a special value # representing an infinite number of entries and will result in a full expanded # tree by default. # Minimum value: 0, maximum value: 9999, default value: 100. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_INDEX_NUM_ENTRIES = 100 # If the GENERATE_DOCSET tag is set to YES, additional index files will be # generated that can be used as input for Apple's Xcode 3 integrated development # environment (see: http://developer.apple.com/tools/xcode/), introduced with # OSX 10.5 (Leopard). To create a documentation set, doxygen will generate a # Makefile in the HTML output directory. Running make will produce the docset in # that directory and running make install will install the docset in # ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at # startup. See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html # for more information. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. GENERATE_DOCSET = NO # This tag determines the name of the docset feed. A documentation feed provides # an umbrella under which multiple documentation sets from a single provider # (such as a company or product suite) can be grouped. # The default value is: Doxygen generated docs. # This tag requires that the tag GENERATE_DOCSET is set to YES. DOCSET_FEEDNAME = "Doxygen generated docs" # This tag specifies a string that should uniquely identify the documentation # set bundle. This should be a reverse domain-name style string, e.g. # com.mycompany.MyDocSet. Doxygen will append .docset to the name. # The default value is: org.doxygen.Project. # This tag requires that the tag GENERATE_DOCSET is set to YES. DOCSET_BUNDLE_ID = org.doxygen.Project # The DOCSET_PUBLISHER_ID tag specifies a string that should uniquely identify # the documentation publisher. This should be a reverse domain-name style # string, e.g. com.mycompany.MyDocSet.documentation. # The default value is: org.doxygen.Publisher. # This tag requires that the tag GENERATE_DOCSET is set to YES. DOCSET_PUBLISHER_ID = org.doxygen.Publisher # The DOCSET_PUBLISHER_NAME tag identifies the documentation publisher. # The default value is: Publisher. # This tag requires that the tag GENERATE_DOCSET is set to YES. DOCSET_PUBLISHER_NAME = Publisher # If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three # additional HTML index files: index.hhp, index.hhc, and index.hhk. The # index.hhp is a project file that can be read by Microsoft's HTML Help Workshop # (see: http://www.microsoft.com/en-us/download/details.aspx?id=21138) on # Windows. # # The HTML Help Workshop contains a compiler that can convert all HTML output # generated by doxygen into a single compiled HTML file (.chm). Compiled HTML # files are now used as the Windows 98 help format, and will replace the old # Windows help format (.hlp) on all Windows platforms in the future. Compressed # HTML files also contain an index, a table of contents, and you can search for # words in the documentation. The HTML workshop also contains a viewer for # compressed HTML files. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. GENERATE_HTMLHELP = NO # The CHM_FILE tag can be used to specify the file name of the resulting .chm # file. You can add a path in front of the file if the result should not be # written to the html output directory. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. CHM_FILE = # The HHC_LOCATION tag can be used to specify the location (absolute path # including file name) of the HTML help compiler (hhc.exe). If non-empty, # doxygen will try to run the HTML help compiler on the generated index.hhp. # The file has to be specified with full path. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. HHC_LOCATION = # The GENERATE_CHI flag controls if a separate .chi index file is generated # (YES) or that it should be included in the master .chm file (NO). # The default value is: NO. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. GENERATE_CHI = NO # The CHM_INDEX_ENCODING is used to encode HtmlHelp index (hhk), content (hhc) # and project file content. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. CHM_INDEX_ENCODING = # The BINARY_TOC flag controls whether a binary table of contents is generated # (YES) or a normal table of contents (NO) in the .chm file. Furthermore it # enables the Previous and Next buttons. # The default value is: NO. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. BINARY_TOC = NO # The TOC_EXPAND flag can be set to YES to add extra items for group members to # the table of contents of the HTML help documentation and to the tree view. # The default value is: NO. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. TOC_EXPAND = NO # If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and # QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that # can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help # (.qch) of the generated HTML documentation. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. GENERATE_QHP = NO # If the QHG_LOCATION tag is specified, the QCH_FILE tag can be used to specify # the file name of the resulting .qch file. The path specified is relative to # the HTML output folder. # This tag requires that the tag GENERATE_QHP is set to YES. QCH_FILE = # The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help # Project output. For more information please see Qt Help Project / Namespace # (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#namespace). # The default value is: org.doxygen.Project. # This tag requires that the tag GENERATE_QHP is set to YES. QHP_NAMESPACE = org.doxygen.Project # The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt # Help Project output. For more information please see Qt Help Project / Virtual # Folders (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#virtual- # folders). # The default value is: doc. # This tag requires that the tag GENERATE_QHP is set to YES. QHP_VIRTUAL_FOLDER = doc # If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom # filter to add. For more information please see Qt Help Project / Custom # Filters (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom- # filters). # This tag requires that the tag GENERATE_QHP is set to YES. QHP_CUST_FILTER_NAME = # The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the # custom filter to add. For more information please see Qt Help Project / Custom # Filters (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom- # filters). # This tag requires that the tag GENERATE_QHP is set to YES. QHP_CUST_FILTER_ATTRS = # The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this # project's filter section matches. Qt Help Project / Filter Attributes (see: # http://qt-project.org/doc/qt-4.8/qthelpproject.html#filter-attributes). # This tag requires that the tag GENERATE_QHP is set to YES. QHP_SECT_FILTER_ATTRS = # The QHG_LOCATION tag can be used to specify the location of Qt's # qhelpgenerator. If non-empty doxygen will try to run qhelpgenerator on the # generated .qhp file. # This tag requires that the tag GENERATE_QHP is set to YES. QHG_LOCATION = # If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files will be # generated, together with the HTML files, they form an Eclipse help plugin. To # install this plugin and make it available under the help contents menu in # Eclipse, the contents of the directory containing the HTML and XML files needs # to be copied into the plugins directory of eclipse. The name of the directory # within the plugins directory should be the same as the ECLIPSE_DOC_ID value. # After copying Eclipse needs to be restarted before the help appears. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. GENERATE_ECLIPSEHELP = NO # A unique identifier for the Eclipse help plugin. When installing the plugin # the directory name containing the HTML and XML files should also have this # name. Each documentation set should have its own identifier. # The default value is: org.doxygen.Project. # This tag requires that the tag GENERATE_ECLIPSEHELP is set to YES. ECLIPSE_DOC_ID = org.doxygen.Project # If you want full control over the layout of the generated HTML pages it might # be necessary to disable the index and replace it with your own. The # DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) at top # of each HTML page. A value of NO enables the index and the value YES disables # it. Since the tabs in the index contain the same information as the navigation # tree, you can set this option to YES if you also set GENERATE_TREEVIEW to YES. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. DISABLE_INDEX = NO # The GENERATE_TREEVIEW tag is used to specify whether a tree-like index # structure should be generated to display hierarchical information. If the tag # value is set to YES, a side panel will be generated containing a tree-like # index structure (just like the one that is generated for HTML Help). For this # to work a browser that supports JavaScript, DHTML, CSS and frames is required # (i.e. any modern browser). Windows users are probably better off using the # HTML help feature. Via custom style sheets (see HTML_EXTRA_STYLESHEET) one can # further fine-tune the look of the index. As an example, the default style # sheet generated by doxygen has an example that shows how to put an image at # the root of the tree instead of the PROJECT_NAME. Since the tree basically has # the same information as the tab index, you could consider setting # DISABLE_INDEX to YES when enabling this option. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. GENERATE_TREEVIEW = NO # The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that # doxygen will group on one line in the generated HTML documentation. # # Note that a value of 0 will completely suppress the enum values from appearing # in the overview section. # Minimum value: 0, maximum value: 20, default value: 4. # This tag requires that the tag GENERATE_HTML is set to YES. ENUM_VALUES_PER_LINE = 4 # If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be used # to set the initial width (in pixels) of the frame in which the tree is shown. # Minimum value: 0, maximum value: 1500, default value: 250. # This tag requires that the tag GENERATE_HTML is set to YES. TREEVIEW_WIDTH = 250 # If the EXT_LINKS_IN_WINDOW option is set to YES, doxygen will open links to # external symbols imported via tag files in a separate window. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. EXT_LINKS_IN_WINDOW = NO # Use this tag to change the font size of LaTeX formulas included as images in # the HTML documentation. When you change the font size after a successful # doxygen run you need to manually remove any form_*.png images from the HTML # output directory to force them to be regenerated. # Minimum value: 8, maximum value: 50, default value: 10. # This tag requires that the tag GENERATE_HTML is set to YES. FORMULA_FONTSIZE = 10 # Use the FORMULA_TRANPARENT tag to determine whether or not the images # generated for formulas are transparent PNGs. Transparent PNGs are not # supported properly for IE 6.0, but are supported on all modern browsers. # # Note that when changing this option you need to delete any form_*.png files in # the HTML output directory before the changes have effect. # The default value is: YES. # This tag requires that the tag GENERATE_HTML is set to YES. FORMULA_TRANSPARENT = YES # Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see # http://www.mathjax.org) which uses client side Javascript for the rendering # instead of using pre-rendered bitmaps. Use this if you do not have LaTeX # installed or if you want to formulas look prettier in the HTML output. When # enabled you may also need to install MathJax separately and configure the path # to it using the MATHJAX_RELPATH option. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. USE_MATHJAX = NO # When MathJax is enabled you can set the default output format to be used for # the MathJax output. See the MathJax site (see: # http://docs.mathjax.org/en/latest/output.html) for more details. # Possible values are: HTML-CSS (which is slower, but has the best # compatibility), NativeMML (i.e. MathML) and SVG. # The default value is: HTML-CSS. # This tag requires that the tag USE_MATHJAX is set to YES. MATHJAX_FORMAT = HTML-CSS # When MathJax is enabled you need to specify the location relative to the HTML # output directory using the MATHJAX_RELPATH option. The destination directory # should contain the MathJax.js script. For instance, if the mathjax directory # is located at the same level as the HTML output directory, then # MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax # Content Delivery Network so you can quickly see the result without installing # MathJax. However, it is strongly recommended to install a local copy of # MathJax from http://www.mathjax.org before deployment. # The default value is: http://cdn.mathjax.org/mathjax/latest. # This tag requires that the tag USE_MATHJAX is set to YES. MATHJAX_RELPATH = http://cdn.mathjax.org/mathjax/latest # The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax # extension names that should be enabled during MathJax rendering. For example # MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols # This tag requires that the tag USE_MATHJAX is set to YES. MATHJAX_EXTENSIONS = # The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces # of code that will be used on startup of the MathJax code. See the MathJax site # (see: http://docs.mathjax.org/en/latest/output.html) for more details. For an # example see the documentation. # This tag requires that the tag USE_MATHJAX is set to YES. MATHJAX_CODEFILE = # When the SEARCHENGINE tag is enabled doxygen will generate a search box for # the HTML output. The underlying search engine uses javascript and DHTML and # should work on any modern browser. Note that when using HTML help # (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets (GENERATE_DOCSET) # there is already a search function so this one should typically be disabled. # For large projects the javascript based search engine can be slow, then # enabling SERVER_BASED_SEARCH may provide a better solution. It is possible to # search using the keyboard; to jump to the search box use + S # (what the is depends on the OS and browser, but it is typically # , /